Skip to content

Commit 0e3311a

Browse files
authored
Support for creating custom tokens without service account credentials (#285)
* Initial framework for signing tokens remotely * Separated token generator and verifier * Unit tests for service acct signer * Added integration test * Changed getAccount() to return a Promise * Returning a buffer from sign API * More unit tests * Removing unused config * Updated test description * Cleaning up imports * Updated test to verify metadata server headers * Renamed serviceAccount to serviceAccountId * Using new http API for token generation * Added comments and documentation * Updated changelog * Responding to code review feedback * Updated IAM errorm essages * Updated error message
1 parent 6223073 commit 0e3311a

15 files changed

+710
-452
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Unreleased
22

3+
- [changed] Admin SDK can now create custom tokens without being initialized
4+
with service account credentials. When a service account private key is not
5+
available, the SDK uses the remote IAM service to sign JWTs in the cloud.
36
- [changed] Updated the typings of the `admin.database.Query.once()`
47
method to return a more specific type.
58
- [changed] Admin SDK can now read the Firebase/GCP project ID from both

src/auth/auth.ts

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import {UserRecord, CreateRequest, UpdateRequest} from './user-record';
1818
import {FirebaseApp} from '../firebase-app';
19-
import {FirebaseTokenGenerator} from './token-generator';
19+
import {FirebaseTokenGenerator, cryptoSignerFromApp} from './token-generator';
2020
import {FirebaseAuthRequestHandler} from './auth-api-request';
2121
import {AuthClientErrorCode, FirebaseAuthError, ErrorInfo} from '../utils/error';
2222
import {FirebaseServiceInterface, FirebaseServiceInternalsInterface} from '../firebase-service';
@@ -26,6 +26,7 @@ import {
2626

2727
import * as utils from '../utils/index';
2828
import * as validator from '../utils/validator';
29+
import { FirebaseTokenVerifier, createSessionCookieVerifier, createIdTokenVerifier } from './token-verifier';
2930

3031

3132
/**
@@ -82,9 +83,11 @@ export interface SessionCookieOptions {
8283
export class Auth implements FirebaseServiceInterface {
8384
public INTERNAL: AuthInternals = new AuthInternals();
8485

85-
private app_: FirebaseApp;
86-
private tokenGenerator_: FirebaseTokenGenerator;
87-
private authRequestHandler: FirebaseAuthRequestHandler;
86+
private readonly app_: FirebaseApp;
87+
private readonly tokenGenerator: FirebaseTokenGenerator;
88+
private readonly idTokenVerifier: FirebaseTokenVerifier;
89+
private readonly sessionCookieVerifier: FirebaseTokenVerifier;
90+
private readonly authRequestHandler: FirebaseAuthRequestHandler;
8891

8992
/**
9093
* @param {object} app The app for this Auth service.
@@ -99,27 +102,10 @@ export class Auth implements FirebaseServiceInterface {
99102
}
100103

101104
this.app_ = app;
105+
this.tokenGenerator = new FirebaseTokenGenerator(cryptoSignerFromApp(app));
102106
const projectId = utils.getProjectId(app);
103-
104-
// TODO (inlined): plumb this into a factory method for tokenGenerator_ once we
105-
// can generate custom tokens from access tokens.
106-
let serviceAccount;
107-
if (typeof app.options.credential.getCertificate === 'function') {
108-
serviceAccount = app.options.credential.getCertificate();
109-
}
110-
if (serviceAccount) {
111-
// Cert credentials and Application Default Credentials created from a service account file
112-
// provide a certificate we can use to mint custom tokens and verify ID tokens.
113-
this.tokenGenerator_ = new FirebaseTokenGenerator(serviceAccount);
114-
} else if (validator.isNonEmptyString(projectId)) {
115-
// Google infrastructure like GAE, GCE, and GCF store the GCP / Firebase project ID in an
116-
// environment variable that we can use to get verifyIdToken() to work. createCustomToken()
117-
// still won't work since it requires a private key and client email which we do not have.
118-
const cert: any = {
119-
projectId,
120-
};
121-
this.tokenGenerator_ = new FirebaseTokenGenerator(cert);
122-
}
107+
this.sessionCookieVerifier = createSessionCookieVerifier(projectId);
108+
this.idTokenVerifier = createIdTokenVerifier(projectId);
123109
// Initialize auth request handler with the app.
124110
this.authRequestHandler = new FirebaseAuthRequestHandler(app);
125111
}
@@ -143,13 +129,7 @@ export class Auth implements FirebaseServiceInterface {
143129
* @return {Promise<string>} A JWT for the provided payload.
144130
*/
145131
public createCustomToken(uid: string, developerClaims?: object): Promise<string> {
146-
if (typeof this.tokenGenerator_ === 'undefined') {
147-
throw new FirebaseAuthError(
148-
AuthClientErrorCode.INVALID_CREDENTIAL,
149-
'Must initialize app with a cert credential to call auth().createCustomToken().',
150-
);
151-
}
152-
return this.tokenGenerator_.createCustomToken(uid, developerClaims);
132+
return this.tokenGenerator.createCustomToken(uid, developerClaims);
153133
}
154134

155135
/**
@@ -165,14 +145,7 @@ export class Auth implements FirebaseServiceInterface {
165145
* verification.
166146
*/
167147
public verifyIdToken(idToken: string, checkRevoked: boolean = false): Promise<object> {
168-
if (typeof this.tokenGenerator_ === 'undefined') {
169-
throw new FirebaseAuthError(
170-
AuthClientErrorCode.INVALID_CREDENTIAL,
171-
'Must initialize app with a cert credential or set your Firebase project ID as the ' +
172-
'GOOGLE_CLOUD_PROJECT environment variable to call auth().verifyIdToken().',
173-
);
174-
}
175-
return this.tokenGenerator_.verifyIdToken(idToken)
148+
return this.idTokenVerifier.verifyJWT(idToken)
176149
.then((decodedIdToken: DecodedIdToken) => {
177150
// Whether to check if the token was revoked.
178151
if (!checkRevoked) {
@@ -401,14 +374,7 @@ export class Auth implements FirebaseServiceInterface {
401374
*/
402375
public verifySessionCookie(
403376
sessionCookie: string, checkRevoked: boolean = false): Promise<DecodedIdToken> {
404-
if (typeof this.tokenGenerator_ === 'undefined') {
405-
throw new FirebaseAuthError(
406-
AuthClientErrorCode.INVALID_CREDENTIAL,
407-
'Must initialize app with a cert credential or set your Firebase project ID as the ' +
408-
'GOOGLE_CLOUD_PROJECT environment variable to call auth().verifySessionCookie().',
409-
);
410-
}
411-
return this.tokenGenerator_.verifySessionCookie(sessionCookie)
377+
return this.sessionCookieVerifier.verifyJWT(sessionCookie)
412378
.then((decodedIdToken: DecodedIdToken) => {
413379
// Whether to check if the token was revoked.
414380
if (!checkRevoked) {

0 commit comments

Comments
 (0)