@@ -17,9 +17,7 @@ package auth
1717
1818import (
1919 "crypto/rsa"
20- "crypto/x509"
2120 "encoding/json"
22- "encoding/pem"
2321 "errors"
2422 "fmt"
2523 "strings"
@@ -31,10 +29,12 @@ import (
3129 "google.golang.org/api/transport"
3230)
3331
34- const firebaseAudience = "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
35- const googleCertURL = "https://www.googleapis.com/robot/v1/metadata/x509/[email protected] " 36- const issuerPrefix = "https://securetoken.google.com/"
37- const tokenExpSeconds = 3600
32+ const (
33+ firebaseAudience = "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
34+ idTokenCertURL = "https://www.googleapis.com/robot/v1/metadata/x509/[email protected] " 35+ issuerPrefix = "https://securetoken.google.com/"
36+ tokenExpSeconds = 3600
37+ )
3838
3939var reservedClaims = []string {
4040 "acr" , "amr" , "at_hash" , "aud" , "auth_time" , "azp" , "cnf" , "c_hash" ,
@@ -58,6 +58,25 @@ type Token struct {
5858 Claims map [string ]interface {} `json:"-"`
5959}
6060
61+ func (t * Token ) decodeFrom (s string ) error {
62+ // Decode into a regular map to access custom claims.
63+ claims := make (map [string ]interface {})
64+ if err := decode (s , & claims ); err != nil {
65+ return err
66+ }
67+ // Now decode into Token to access the standard claims.
68+ if err := decode (s , t ); err != nil {
69+ return err
70+ }
71+
72+ // Delete standard claims from the custom claims maps.
73+ for _ , r := range []string {"iss" , "aud" , "exp" , "iat" , "sub" , "uid" } {
74+ delete (claims , r )
75+ }
76+ t .Claims = claims
77+ return nil
78+ }
79+
6180// Client is the interface for the Firebase auth service.
6281//
6382// Client facilitates generating custom JWT tokens for Firebase clients, and verifying ID tokens issued
@@ -71,8 +90,8 @@ type Client struct {
7190}
7291
7392type signer interface {
74- Email () (string , error )
75- Sign (b []byte ) ([]byte , error )
93+ Email (ctx context. Context ) (string , error )
94+ Sign (ctx context. Context , b []byte ) ([]byte , error )
7695}
7796
7897// NewClient creates a new instance of the Firebase Auth Client.
@@ -94,7 +113,7 @@ func NewClient(ctx context.Context, c *internal.AuthConfig) (*Client, error) {
94113 return nil , err
95114 }
96115 if svcAcct .PrivateKey != "" {
97- pk , err = parseKey (svcAcct .PrivateKey )
116+ pk , err = parsePrivateKey (svcAcct .PrivateKey )
98117 if err != nil {
99118 return nil , err
100119 }
@@ -124,7 +143,7 @@ func NewClient(ctx context.Context, c *internal.AuthConfig) (*Client, error) {
124143
125144 return & Client {
126145 is : is ,
127- ks : newHTTPKeySource (googleCertURL , hc ),
146+ ks : newHTTPKeySource (idTokenCertURL , hc ),
128147 projectID : c .ProjectID ,
129148 snr : snr ,
130149 version : "Go/Admin/" + c .Version ,
@@ -135,14 +154,14 @@ func NewClient(ctx context.Context, c *internal.AuthConfig) (*Client, error) {
135154// JWT can be used in a Firebase client SDK to trigger an authentication flow. See
136155// https://firebase.google.com/docs/auth/admin/create-custom-tokens#sign_in_using_custom_tokens_on_clients
137156// for more details on how to use custom tokens for client authentication.
138- func (c * Client ) CustomToken (uid string ) (string , error ) {
139- return c .CustomTokenWithClaims (uid , nil )
157+ func (c * Client ) CustomToken (ctx context. Context , uid string ) (string , error ) {
158+ return c .CustomTokenWithClaims (ctx , uid , nil )
140159}
141160
142161// CustomTokenWithClaims is similar to CustomToken, but in addition to the user ID, it also encodes
143162// all the key-value pairs in the provided map as claims in the resulting JWT.
144- func (c * Client ) CustomTokenWithClaims (uid string , devClaims map [string ]interface {}) (string , error ) {
145- iss , err := c .snr .Email ()
163+ func (c * Client ) CustomTokenWithClaims (ctx context. Context , uid string , devClaims map [string ]interface {}) (string , error ) {
164+ iss , err := c .snr .Email (ctx )
146165 if err != nil {
147166 return "" , err
148167 }
@@ -164,6 +183,7 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac
164183 }
165184
166185 now := clk .Now ().Unix ()
186+ header := jwtHeader {Algorithm : "RS256" , Type : "JWT" }
167187 payload := & customToken {
168188 Iss : iss ,
169189 Sub : iss ,
@@ -173,7 +193,7 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac
173193 Exp : now + tokenExpSeconds ,
174194 Claims : devClaims ,
175195 }
176- return encodeToken (c .snr , defaultHeader () , payload )
196+ return encodeToken (ctx , c .snr , header , payload )
177197}
178198
179199// RevokeRefreshTokens revokes all refresh tokens issued to a user.
@@ -196,50 +216,50 @@ func (c *Client) RevokeRefreshTokens(ctx context.Context, uid string) error {
196216// https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients for
197217// more details on how to obtain an ID token in a client app.
198218// This does not check whether or not the token has been revoked. See `VerifyIDTokenAndCheckRevoked` below.
199- func (c * Client ) VerifyIDToken (idToken string ) (* Token , error ) {
219+ func (c * Client ) VerifyIDToken (ctx context. Context , idToken string ) (* Token , error ) {
200220 if c .projectID == "" {
201221 return nil , errors .New ("project id not available" )
202222 }
203223 if idToken == "" {
204- return nil , fmt .Errorf ("ID token must be a non-empty string" )
224+ return nil , fmt .Errorf ("id token must be a non-empty string" )
205225 }
206226
207227 h := & jwtHeader {}
208228 p := & Token {}
209- if err := decodeToken (idToken , c .ks , h , p ); err != nil {
229+ if err := decodeToken (ctx , idToken , c .ks , h , p ); err != nil {
210230 return nil , err
211231 }
212232
213- projectIDMsg := "Make sure the ID token comes from the same Firebase project as the credential used to" +
214- " authenticate this SDK. "
215- verifyTokenMsg := "See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to " +
216- "retrieve a valid ID token. "
233+ projectIDMsg := "make sure the ID token comes from the same Firebase project as the credential used to" +
234+ " authenticate this SDK"
235+ verifyTokenMsg := "see https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to " +
236+ "retrieve a valid ID token"
217237 issuer := issuerPrefix + c .projectID
218238
219239 var err error
220240 if h .KeyID == "" {
221241 if p .Audience == firebaseAudience {
222- err = fmt .Errorf ("VerifyIDToken() expects an ID token, but was given a custom token" )
242+ err = fmt .Errorf ("expected an ID token but got a custom token" )
223243 } else {
224244 err = fmt .Errorf ("ID token has no 'kid' header" )
225245 }
226246 } else if h .Algorithm != "RS256" {
227- err = fmt .Errorf ("ID token has invalid incorrect algorithm. Expected 'RS256' but got %q. %s" ,
247+ err = fmt .Errorf ("ID token has invalid algorithm; expected 'RS256' but got %q; %s" ,
228248 h .Algorithm , verifyTokenMsg )
229249 } else if p .Audience != c .projectID {
230- err = fmt .Errorf ("ID token has invalid 'aud' (audience) claim. Expected %q but got %q. %s %s" ,
250+ err = fmt .Errorf ("ID token has invalid 'aud' (audience) claim; expected %q but got %q; %s; %s" ,
231251 c .projectID , p .Audience , projectIDMsg , verifyTokenMsg )
232252 } else if p .Issuer != issuer {
233- err = fmt .Errorf ("ID token has invalid 'iss' (issuer) claim. Expected %q but got %q. %s %s" ,
253+ err = fmt .Errorf ("ID token has invalid 'iss' (issuer) claim; expected %q but got %q; %s; %s" ,
234254 issuer , p .Issuer , projectIDMsg , verifyTokenMsg )
235255 } else if p .IssuedAt > clk .Now ().Unix () {
236256 err = fmt .Errorf ("ID token issued at future timestamp: %d" , p .IssuedAt )
237257 } else if p .Expires < clk .Now ().Unix () {
238- err = fmt .Errorf ("ID token has expired. Expired at: %d" , p .Expires )
258+ err = fmt .Errorf ("ID token has expired at: %d" , p .Expires )
239259 } else if p .Subject == "" {
240- err = fmt .Errorf ("ID token has empty 'sub' (subject) claim. %s" , verifyTokenMsg )
260+ err = fmt .Errorf ("ID token has empty 'sub' (subject) claim; %s" , verifyTokenMsg )
241261 } else if len (p .Subject ) > 128 {
242- err = fmt .Errorf ("ID token has a 'sub' (subject) claim longer than 128 characters. %s" , verifyTokenMsg )
262+ err = fmt .Errorf ("ID token has a 'sub' (subject) claim longer than 128 characters; %s" , verifyTokenMsg )
243263 }
244264
245265 if err != nil {
@@ -254,7 +274,7 @@ func (c *Client) VerifyIDToken(idToken string) (*Token, error) {
254274// VerifyIDTokenAndCheckRevoked verifies the signature and payload of the provided ID token and
255275// checks that it wasn't revoked. Uses VerifyIDToken() internally to verify the ID token JWT.
256276func (c * Client ) VerifyIDTokenAndCheckRevoked (ctx context.Context , idToken string ) (* Token , error ) {
257- p , err := c .VerifyIDToken (idToken )
277+ p , err := c .VerifyIDToken (ctx , idToken )
258278 if err != nil {
259279 return nil , err
260280 }
@@ -269,23 +289,3 @@ func (c *Client) VerifyIDTokenAndCheckRevoked(ctx context.Context, idToken strin
269289 }
270290 return p , nil
271291}
272-
273- func parseKey (key string ) (* rsa.PrivateKey , error ) {
274- block , _ := pem .Decode ([]byte (key ))
275- if block == nil {
276- return nil , fmt .Errorf ("no private key data found in: %v" , key )
277- }
278- k := block .Bytes
279- parsedKey , err := x509 .ParsePKCS8PrivateKey (k )
280- if err != nil {
281- parsedKey , err = x509 .ParsePKCS1PrivateKey (k )
282- if err != nil {
283- return nil , fmt .Errorf ("private key should be a PEM or plain PKSC1 or PKCS8; parse error: %v" , err )
284- }
285- }
286- parsed , ok := parsedKey .(* rsa.PrivateKey )
287- if ! ok {
288- return nil , errors .New ("private key is not an RSA key" )
289- }
290- return parsed , nil
291- }
0 commit comments