@@ -17,9 +17,7 @@ package auth
17
17
18
18
import (
19
19
"crypto/rsa"
20
- "crypto/x509"
21
20
"encoding/json"
22
- "encoding/pem"
23
21
"errors"
24
22
"fmt"
25
23
"strings"
@@ -31,10 +29,12 @@ import (
31
29
"google.golang.org/api/transport"
32
30
)
33
31
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
+ )
38
38
39
39
var reservedClaims = []string {
40
40
"acr" , "amr" , "at_hash" , "aud" , "auth_time" , "azp" , "cnf" , "c_hash" ,
@@ -58,6 +58,25 @@ type Token struct {
58
58
Claims map [string ]interface {} `json:"-"`
59
59
}
60
60
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
+
61
80
// Client is the interface for the Firebase auth service.
62
81
//
63
82
// Client facilitates generating custom JWT tokens for Firebase clients, and verifying ID tokens issued
@@ -71,8 +90,8 @@ type Client struct {
71
90
}
72
91
73
92
type 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 )
76
95
}
77
96
78
97
// NewClient creates a new instance of the Firebase Auth Client.
@@ -94,7 +113,7 @@ func NewClient(ctx context.Context, c *internal.AuthConfig) (*Client, error) {
94
113
return nil , err
95
114
}
96
115
if svcAcct .PrivateKey != "" {
97
- pk , err = parseKey (svcAcct .PrivateKey )
116
+ pk , err = parsePrivateKey (svcAcct .PrivateKey )
98
117
if err != nil {
99
118
return nil , err
100
119
}
@@ -124,7 +143,7 @@ func NewClient(ctx context.Context, c *internal.AuthConfig) (*Client, error) {
124
143
125
144
return & Client {
126
145
is : is ,
127
- ks : newHTTPKeySource (googleCertURL , hc ),
146
+ ks : newHTTPKeySource (idTokenCertURL , hc ),
128
147
projectID : c .ProjectID ,
129
148
snr : snr ,
130
149
version : "Go/Admin/" + c .Version ,
@@ -135,14 +154,14 @@ func NewClient(ctx context.Context, c *internal.AuthConfig) (*Client, error) {
135
154
// JWT can be used in a Firebase client SDK to trigger an authentication flow. See
136
155
// https://firebase.google.com/docs/auth/admin/create-custom-tokens#sign_in_using_custom_tokens_on_clients
137
156
// 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 )
140
159
}
141
160
142
161
// CustomTokenWithClaims is similar to CustomToken, but in addition to the user ID, it also encodes
143
162
// 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 )
146
165
if err != nil {
147
166
return "" , err
148
167
}
@@ -164,6 +183,7 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac
164
183
}
165
184
166
185
now := clk .Now ().Unix ()
186
+ header := jwtHeader {Algorithm : "RS256" , Type : "JWT" }
167
187
payload := & customToken {
168
188
Iss : iss ,
169
189
Sub : iss ,
@@ -173,7 +193,7 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac
173
193
Exp : now + tokenExpSeconds ,
174
194
Claims : devClaims ,
175
195
}
176
- return encodeToken (c .snr , defaultHeader () , payload )
196
+ return encodeToken (ctx , c .snr , header , payload )
177
197
}
178
198
179
199
// RevokeRefreshTokens revokes all refresh tokens issued to a user.
@@ -196,50 +216,50 @@ func (c *Client) RevokeRefreshTokens(ctx context.Context, uid string) error {
196
216
// https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients for
197
217
// more details on how to obtain an ID token in a client app.
198
218
// 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 ) {
200
220
if c .projectID == "" {
201
221
return nil , errors .New ("project id not available" )
202
222
}
203
223
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" )
205
225
}
206
226
207
227
h := & jwtHeader {}
208
228
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 {
210
230
return nil , err
211
231
}
212
232
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"
217
237
issuer := issuerPrefix + c .projectID
218
238
219
239
var err error
220
240
if h .KeyID == "" {
221
241
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" )
223
243
} else {
224
244
err = fmt .Errorf ("ID token has no 'kid' header" )
225
245
}
226
246
} 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" ,
228
248
h .Algorithm , verifyTokenMsg )
229
249
} 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" ,
231
251
c .projectID , p .Audience , projectIDMsg , verifyTokenMsg )
232
252
} 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" ,
234
254
issuer , p .Issuer , projectIDMsg , verifyTokenMsg )
235
255
} else if p .IssuedAt > clk .Now ().Unix () {
236
256
err = fmt .Errorf ("ID token issued at future timestamp: %d" , p .IssuedAt )
237
257
} 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 )
239
259
} 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 )
241
261
} 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 )
243
263
}
244
264
245
265
if err != nil {
@@ -254,7 +274,7 @@ func (c *Client) VerifyIDToken(idToken string) (*Token, error) {
254
274
// VerifyIDTokenAndCheckRevoked verifies the signature and payload of the provided ID token and
255
275
// checks that it wasn't revoked. Uses VerifyIDToken() internally to verify the ID token JWT.
256
276
func (c * Client ) VerifyIDTokenAndCheckRevoked (ctx context.Context , idToken string ) (* Token , error ) {
257
- p , err := c .VerifyIDToken (idToken )
277
+ p , err := c .VerifyIDToken (ctx , idToken )
258
278
if err != nil {
259
279
return nil , err
260
280
}
@@ -269,23 +289,3 @@ func (c *Client) VerifyIDTokenAndCheckRevoked(ctx context.Context, idToken strin
269
289
}
270
290
return p , nil
271
291
}
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