Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Fetch id token from refresh token #665

Open
wants to merge 26 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
54b8114
Merge dev into master
google-oss-bot May 21, 2020
cef91ac
Merge dev into master
google-oss-bot Jun 16, 2020
77177c7
Merge dev into master
google-oss-bot Oct 22, 2020
a957589
Merge dev into master
google-oss-bot Jan 28, 2021
eb0d2a0
Merge dev into master
google-oss-bot Mar 24, 2021
05378ef
Merge dev into master
google-oss-bot Mar 29, 2021
4121c50
Merge dev into master
google-oss-bot Apr 14, 2021
928b104
Merge dev into master
google-oss-bot Jun 2, 2021
02cde4f
Merge dev into master
google-oss-bot Nov 4, 2021
6b40682
Merge dev into master
google-oss-bot Dec 15, 2021
e60757f
Merge dev into master
google-oss-bot Jan 20, 2022
bb055ed
Merge dev into master
google-oss-bot Apr 6, 2022
23a1f17
Merge dev into master
google-oss-bot Oct 6, 2022
1d24577
Merge dev into master
google-oss-bot Nov 10, 2022
61c6c04
Merge dev into master
google-oss-bot Apr 6, 2023
32af2b8
[chore] Release 4.12.0 (#561)
lahirumaramba Jun 22, 2023
02300a8
Revert "[chore] Release 4.12.0 (#561)" (#565)
lahirumaramba Jul 11, 2023
74c9bd5
Merge dev into master
google-oss-bot Jul 12, 2023
37c7936
Merge dev into master
google-oss-bot Sep 25, 2023
b04387e
Merge dev into master
google-oss-bot Nov 23, 2023
87b867c
Merge dev into master
google-oss-bot Apr 10, 2024
6a28190
Merge dev into master
google-oss-bot May 30, 2024
c3be6f2
Merge dev into master
google-oss-bot Oct 24, 2024
afeaa15
Merge dev into master
google-oss-bot Dec 5, 2024
46458d6
add: Fetch id token from refresh token
Dec 20, 2024
e9eddfd
use context
Dec 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 80 additions & 23 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ package auth

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"time"
Expand Down Expand Up @@ -146,30 +149,83 @@ func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error)
}
idToolkitV1Endpoint := fmt.Sprintf("%s/v1", baseURL)
idToolkitV2Endpoint := fmt.Sprintf("%s/v2", baseURL)
secureToolkitV1Endpoint := fmt.Sprintf("%s/v1", "https://securetoken.googleapis.com")
userManagementEndpoint := idToolkitV1Endpoint
providerConfigEndpoint := idToolkitV2Endpoint
tenantMgtEndpoint := idToolkitV2Endpoint
projectMgtEndpoint := idToolkitV2Endpoint

base := &baseClient{
userManagementEndpoint: userManagementEndpoint,
providerConfigEndpoint: providerConfigEndpoint,
tenantMgtEndpoint: tenantMgtEndpoint,
projectMgtEndpoint: projectMgtEndpoint,
projectID: conf.ProjectID,
httpClient: hc,
idTokenVerifier: idTokenVerifier,
cookieVerifier: cookieVerifier,
signer: signer,
clock: internal.SystemClock,
isEmulator: isEmulator,
secureToolkitV1Endpoint: secureToolkitV1Endpoint, // here
userManagementEndpoint: userManagementEndpoint,
providerConfigEndpoint: providerConfigEndpoint,
tenantMgtEndpoint: tenantMgtEndpoint,
projectMgtEndpoint: projectMgtEndpoint,
projectID: conf.ProjectID,
httpClient: hc,
idTokenVerifier: idTokenVerifier,
cookieVerifier: cookieVerifier,
signer: signer,
clock: internal.SystemClock,
isEmulator: isEmulator,
}
return &Client{
baseClient: base,
TenantManager: newTenantManager(hc, conf, base),
}, nil
}

type TokenResponse struct {
ExpiresIn string `json:"expires_in"`
TokenType string `json:"token_type"`
RefreshToken string `json:"refresh_token"`
IDToken string `json:"id_token"`
UserID string `json:"user_id"`
ProjectID string `json:"project_id"`
}

func fetchIdTokenByRefreshToken(ctx context.Context, apiKey, refreshToken, endpointBase string) (TokenResponse, error) {
endpoint := fmt.Sprintf("%s/token?key=%s", endpointBase, apiKey)

form := url.Values{}
form.Add("grant_type", "refresh_token")
form.Add("refresh_token", refreshToken)

req, err := http.NewRequestWithContext(ctx, "POST", endpoint, strings.NewReader(form.Encode()))
if err != nil {
return TokenResponse{}, err
}

req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return TokenResponse{}, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return TokenResponse{}, fmt.Errorf("fetchIdTokenByRefreshToken: %s", resp.Status)
}

var tokenResp TokenResponse
err = json.NewDecoder(resp.Body).Decode(&tokenResp)
if err != nil {
return TokenResponse{}, err
}

return tokenResp, nil
}

func (c *baseClient) ExchangeIdToken(ctx context.Context, apikey string, refreshToken string) (string, error) {
res, err := fetchIdTokenByRefreshToken(ctx, apikey, refreshToken, c.secureToolkitV1Endpoint)
if err != nil {
return "", err
}
return res.IDToken, nil
}

// CustomToken creates a signed custom authentication token with the specified user ID.
//
// The resulting JWT can be used in a Firebase client SDK to trigger an authentication flow. See
Expand Down Expand Up @@ -274,18 +330,19 @@ type FirebaseInfo struct {

// baseClient exposes the APIs common to both auth.Client and auth.TenantClient.
type baseClient struct {
userManagementEndpoint string
providerConfigEndpoint string
tenantMgtEndpoint string
projectMgtEndpoint string
projectID string
tenantID string
httpClient *internal.HTTPClient
idTokenVerifier *tokenVerifier
cookieVerifier *tokenVerifier
signer cryptoSigner
clock internal.Clock
isEmulator bool
secureToolkitV1Endpoint string
userManagementEndpoint string
providerConfigEndpoint string
tenantMgtEndpoint string
projectMgtEndpoint string
projectID string
tenantID string
httpClient *internal.HTTPClient
idTokenVerifier *tokenVerifier
cookieVerifier *tokenVerifier
signer cryptoSigner
clock internal.Clock
isEmulator bool
}

func (c *baseClient) withTenantID(tenantID string) *baseClient {
Expand Down
42 changes: 42 additions & 0 deletions integration/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,48 @@ func TestCustomTokenWithClaims(t *testing.T) {
}
}

func TestExchangeIdToken(t *testing.T) {
uid := "fetch_id_token_by_refresh_token"
ct, err := client.CustomToken(context.Background(), uid)
if err != nil {
t.Fatal(err)
}
idt, err := signInWithCustomToken(ct)
if err != nil {
t.Fatal(err)
}
defer deleteUser(uid)

vt, err := client.VerifyIDTokenAndCheckRevoked(context.Background(), idt)
if err != nil {
t.Fatal(err)
}
if vt.UID != uid {
t.Errorf("UID = %q; want UID = %q", vt.UID, uid)
}

apiKey, err := internal.APIKey()
if err != nil {
t.Errorf("internal.APIKey() = %v; want = <nil>", err)
}

time.Sleep(time.Second)
newIdt, err := client.ExchangeIdToken(context.Background(), apiKey, "mock-refresh-token")
if err != nil {
t.Fatal(err)
}

if idt == newIdt {
// The new ID token should be different from the old one.
t.Errorf("ID token = %q; want ID token != %q", newIdt, idt)
}

_, err = client.VerifyIDTokenAndCheckRevoked(context.Background(), newIdt)
if err != nil {
t.Errorf("VerifyIDTokenAndCheckRevoked(); err = %s; want err = <nil>", err)
}
}

func TestRevokeRefreshTokens(t *testing.T) {
uid := "user_revoked"
ct, err := client.CustomToken(context.Background(), uid)
Expand Down