12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
14
15
- // Package auth contains functions for minting custom authentication tokens, and verifying Firebase ID tokens.
15
+ // Package auth contains functions for minting custom authentication tokens, verifying Firebase ID tokens,
16
+ // and managing users in a Firebase project.
16
17
package auth
17
18
18
19
import (
@@ -23,49 +24,29 @@ import (
23
24
24
25
"firebase.google.com/go/internal"
25
26
"google.golang.org/api/identitytoolkit/v3"
26
- "google.golang.org/api/option"
27
27
"google.golang.org/api/transport"
28
28
)
29
29
30
30
const (
31
31
firebaseAudience = "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
32
- idTokenCertURL = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com"
33
- issuerPrefix = "https://securetoken.google.com/"
34
- tokenExpSeconds = 3600
35
- clockSkewSeconds = 300
32
+ oneHourInSeconds = 3600
36
33
)
37
34
38
35
var reservedClaims = []string {
39
36
"acr" , "amr" , "at_hash" , "aud" , "auth_time" , "azp" , "cnf" , "c_hash" ,
40
37
"exp" , "firebase" , "iat" , "iss" , "jti" , "nbf" , "nonce" , "sub" ,
41
38
}
42
39
43
- // Token represents a decoded Firebase ID token.
44
- //
45
- // Token provides typed accessors to the common JWT fields such as Audience (aud) and Expiry (exp).
46
- // Additionally it provides a UID field, which indicates the user ID of the account to which this token
47
- // belongs. Any additional JWT claims can be accessed via the Claims map of Token.
48
- type Token struct {
49
- Issuer string `json:"iss"`
50
- Audience string `json:"aud"`
51
- Expires int64 `json:"exp"`
52
- IssuedAt int64 `json:"iat"`
53
- Subject string `json:"sub,omitempty"`
54
- UID string `json:"uid,omitempty"`
55
- Claims map [string ]interface {} `json:"-"`
56
- }
57
-
58
40
// Client is the interface for the Firebase auth service.
59
41
//
60
42
// Client facilitates generating custom JWT tokens for Firebase clients, and verifying ID tokens issued
61
43
// by Firebase backend services.
62
44
type Client struct {
63
- is * identitytoolkit.Service
64
- keySource keySource
65
- projectID string
66
- signer cryptoSigner
67
- version string
68
- clock internal.Clock
45
+ is * identitytoolkit.Service
46
+ idTokenVerifier * tokenVerifier
47
+ signer cryptoSigner
48
+ version string
49
+ clock internal.Clock
69
50
}
70
51
71
52
// NewClient creates a new instance of the Firebase Auth Client.
@@ -113,17 +94,16 @@ func NewClient(ctx context.Context, conf *internal.AuthConfig) (*Client, error)
113
94
return nil , err
114
95
}
115
96
116
- noAuthHTTPClient , _ , err := transport . NewHTTPClient (ctx , option . WithoutAuthentication () )
97
+ idTokenVerifier , err := newIDTokenVerifier (ctx , conf . ProjectID )
117
98
if err != nil {
118
99
return nil , err
119
100
}
120
101
return & Client {
121
- is : is ,
122
- keySource : newHTTPKeySource (idTokenCertURL , noAuthHTTPClient ),
123
- projectID : conf .ProjectID ,
124
- signer : signer ,
125
- version : "Go/Admin/" + conf .Version ,
126
- clock : internal .SystemClock ,
102
+ is : is ,
103
+ idTokenVerifier : idTokenVerifier ,
104
+ signer : signer ,
105
+ version : "Go/Admin/" + conf .Version ,
106
+ clock : internal .SystemClock ,
127
107
}, nil
128
108
}
129
109
@@ -183,101 +163,53 @@ func (c *Client) CustomTokenWithClaims(ctx context.Context, uid string, devClaim
183
163
Aud : firebaseAudience ,
184
164
UID : uid ,
185
165
Iat : now ,
186
- Exp : now + tokenExpSeconds ,
166
+ Exp : now + oneHourInSeconds ,
187
167
Claims : devClaims ,
188
168
},
189
169
}
190
170
return info .Token (ctx , c .signer )
191
171
}
192
172
173
+ // Token represents a decoded Firebase ID token.
174
+ //
175
+ // Token provides typed accessors to the common JWT fields such as Audience (aud) and Expiry (exp).
176
+ // Additionally it provides a UID field, which indicates the user ID of the account to which this token
177
+ // belongs. Any additional JWT claims can be accessed via the Claims map of Token.
178
+ type Token struct {
179
+ Issuer string `json:"iss"`
180
+ Audience string `json:"aud"`
181
+ Expires int64 `json:"exp"`
182
+ IssuedAt int64 `json:"iat"`
183
+ Subject string `json:"sub,omitempty"`
184
+ UID string `json:"uid,omitempty"`
185
+ Claims map [string ]interface {} `json:"-"`
186
+ }
187
+
193
188
// VerifyIDToken verifies the signature and payload of the provided ID token.
194
189
//
195
190
// VerifyIDToken accepts a signed JWT token string, and verifies that it is current, issued for the
196
191
// correct Firebase project, and signed by the Google Firebase services in the cloud. It returns
197
192
// a Token containing the decoded claims in the input JWT. See
198
193
// https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients for
199
194
// more details on how to obtain an ID token in a client app.
200
- // This does not check whether or not the token has been revoked. See `VerifyIDTokenAndCheckRevoked` below.
195
+ //
196
+ // This function does not make any RPC calls most of the time. The only time it makes an RPC call
197
+ // is when Google public keys need to be refreshed. These keys get cached up to 24 hours, and
198
+ // therefore the RPC overhead gets amortized over many invocations of this function.
199
+ //
200
+ // This does not check whether or not the token has been revoked. Use `VerifyIDTokenAndCheckRevoked()`
201
+ // when a revocation check is needed.
201
202
func (c * Client ) VerifyIDToken (ctx context.Context , idToken string ) (* Token , error ) {
202
- if c .projectID == "" {
203
- return nil , errors .New ("project id not available" )
204
- }
205
- if idToken == "" {
206
- return nil , fmt .Errorf ("id token must be a non-empty string" )
207
- }
208
-
209
- segments := strings .Split (idToken , "." )
210
-
211
- var (
212
- header jwtHeader
213
- payload Token
214
- claims map [string ]interface {}
215
- )
216
- if err := decode (segments [0 ], & header ); err != nil {
217
- return nil , err
218
- }
219
- if err := decode (segments [1 ], & payload ); err != nil {
220
- return nil , err
221
- }
222
- if err := decode (segments [1 ], & claims ); err != nil {
223
- return nil , err
224
- }
225
- // Delete standard claims from the custom claims maps.
226
- for _ , r := range []string {"iss" , "aud" , "exp" , "iat" , "sub" , "uid" } {
227
- delete (claims , r )
228
- }
229
- payload .Claims = claims
230
-
231
- projectIDMsg := "make sure the ID token comes from the same Firebase project as the credential used to" +
232
- " authenticate this SDK"
233
- verifyTokenMsg := "see https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to " +
234
- "retrieve a valid ID token"
235
- issuer := issuerPrefix + c .projectID
236
-
237
- var err error
238
- if header .KeyID == "" {
239
- if payload .Audience == firebaseAudience {
240
- err = fmt .Errorf ("expected an ID token but got a custom token" )
241
- } else {
242
- err = fmt .Errorf ("ID token has no 'kid' header" )
243
- }
244
- } else if header .Algorithm != "RS256" {
245
- err = fmt .Errorf ("ID token has invalid algorithm; expected 'RS256' but got %q; %s" ,
246
- header .Algorithm , verifyTokenMsg )
247
- } else if payload .Audience != c .projectID {
248
- err = fmt .Errorf ("ID token has invalid 'aud' (audience) claim; expected %q but got %q; %s; %s" ,
249
- c .projectID , payload .Audience , projectIDMsg , verifyTokenMsg )
250
- } else if payload .Issuer != issuer {
251
- err = fmt .Errorf ("ID token has invalid 'iss' (issuer) claim; expected %q but got %q; %s; %s" ,
252
- issuer , payload .Issuer , projectIDMsg , verifyTokenMsg )
253
- } else if (payload .IssuedAt - clockSkewSeconds ) > c .clock .Now ().Unix () {
254
- err = fmt .Errorf ("ID token issued at future timestamp: %d" , payload .IssuedAt )
255
- } else if (payload .Expires + clockSkewSeconds ) < c .clock .Now ().Unix () {
256
- err = fmt .Errorf ("ID token has expired at: %d" , payload .Expires )
257
- } else if payload .Subject == "" {
258
- err = fmt .Errorf ("ID token has empty 'sub' (subject) claim; %s" , verifyTokenMsg )
259
- } else if len (payload .Subject ) > 128 {
260
- err = fmt .Errorf ("ID token has a 'sub' (subject) claim longer than 128 characters; %s" , verifyTokenMsg )
261
- }
262
-
263
- if err != nil {
264
- return nil , err
265
- }
266
- payload .UID = payload .Subject
267
-
268
- // Verifying the signature requires syncronized access to a key store and
269
- // potentially issues a http request. Validating the fields of the token is
270
- // cheaper and invalid tokens will fail faster.
271
- if err := verifyToken (ctx , idToken , c .keySource ); err != nil {
272
- return nil , err
273
- }
274
- return & payload , nil
203
+ return c .idTokenVerifier .VerifyToken (ctx , idToken )
275
204
}
276
205
277
- // VerifyIDTokenAndCheckRevoked verifies the provided ID token and checks it has not been revoked.
206
+ // VerifyIDTokenAndCheckRevoked verifies the provided ID token, and additionally checks that the
207
+ // token has not been revoked.
278
208
//
279
- // VerifyIDTokenAndCheckRevoked verifies the signature and payload of the provided ID token and
280
- // checks that it wasn't revoked. Uses VerifyIDToken() internally to verify the ID token JWT.
209
+ // This function uses `VerifyIDToken()` internally to verify the ID token JWT. However, unlike
210
+ // `VerifyIDToken()` this function must make an RPC call to perform the revocation check.
211
+ // Developers are advised to take this additional overhead into consideration when including this
212
+ // function in an authorization flow that gets executed often.
281
213
func (c * Client ) VerifyIDTokenAndCheckRevoked (ctx context.Context , idToken string ) (* Token , error ) {
282
214
p , err := c .VerifyIDToken (ctx , idToken )
283
215
if err != nil {
0 commit comments