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

Adds support for Firebase Auth session management. #245

Merged
merged 5 commits into from
Apr 5, 2018

Conversation

bojeil-google
Copy link
Contributor

This adds 2 new APIs:
admin.auth().createSessionCookie(idToken: string, sessionCookieOptions:
SessionCookieOptions): Promise
admin.auth().verifySessionCookie(sessionCookie: string, checkRevoked?:
boolean): Promise

Refactored token generator and split token verification to a new class
FirebaseTokenVerifier so it can be used to also verify session cookies.
Kept the same error handling for backward compatibility.
In the process, ported the same tests to token verifier.
Updated token generator ID token and session cookie verification to
check token verifier is called underneath with the expected parameters.

Added integration tests to test all common flows for session cookie
creation and verification.
Added mocks for session cookie JWTs.
Fixed error in URL validator.
Increased integration test timeout to 5 seconds from the default 2 seconds to accommodate user deletion throttling.

This adds 2 new APIs:
admin.auth().createSessionCookie(idToken: string, sessionCookieOptions:
SessionCookieOptions): Promise<string>
admin.auth().verifySessionCookie(sessionCookie: string, checkRevoked?:
boolean): Promise<DecodedIdToken>

Refactored token generator and split token verification to a new class
FirebaseTokenVerifier so it can be used to also verify session cookies.
Kept the same error handling for backward compatibility.
In the process, ported the same tests to token verifier.
Updated token generator ID token and session cookie verification to
check token verifier is called underneath with the expected parameters.

Added integration tests to test all common flows for session cookie
creation and verification.
Added mocks for session cookie JWTs.

Fixed error in URL validator.
Copy link
Contributor

@hiranya911 hiranya911 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good. Just a few things to clean up.

// Set response validator.
.setResponseValidator((response: any) => {
// Response should always contain the session cookie.
if (!response.sessionCookie || !validator.isNonEmptyString(response.sessionCookie)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isNonEmptyString check should be sufficient here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* The session cookie JWT will have the same payload claims as the provided ID token.
*
* @param {string} idToken The Firebase ID token to exchange for a session cookie.
* @param {number} expiresIn The session cookie duration.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please specify time unit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

src/auth/auth.ts Outdated
* verification.
*/
private verifyDecodedJWTNotRevoked(
decodedIdToken: DecodedIdToken, revocationError: FirebaseAuthError): Promise<DecodedIdToken> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Perhaps just take the error code as the second argument? You can instantiate the FirebaseAuthError in this function. It is a little weird to pass an exception as an argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

src/auth/auth.ts Outdated
*/
public createSessionCookie(
idToken: string, sessionCookieOptions: SessionCookieOptions): Promise<string> {
return this.authRequestHandler.createSessionCookie(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we validate the existence of expiresIn here and throw an error? (since it's a required field)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I added a check. Note this will return a rejected promise. We do this for all other APIs here. We don't directly throw an error and return a rejected promise after validation in auth-api-request.ts.

src/auth/auth.ts Outdated
public createSessionCookie(
idToken: string, sessionCookieOptions: SessionCookieOptions): Promise<string> {
return this.authRequestHandler.createSessionCookie(
idToken, sessionCookieOptions && sessionCookieOptions.expiresIn);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just sessionCookieOptions.expiresIn?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a risk that if sessionCookieOptions is passed as undefined, an error is thrown. So we have to keep this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I changed it since I validate this above.

@@ -318,6 +318,10 @@ export class AuthClientErrorCode {
code: 'claims-too-large',
message: 'Developer claims maximum payload size exceeded.',
};
public static ID_TOKEN_EXPIRED = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we referencing these constants anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend error TOKEN_EXPIRED will map to this client facing error. I think you can see this error in action in the integration tests I added.

})
.then((sessionCookie) => admin.auth().verifySessionCookie(sessionCookie))
.then((decodedIdToken) => {
// Check for expected expiration with +/-2 seconds of variation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets be on the safe side and use a 5s interval for tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

return admin.auth().verifySessionCookie(currentSessionCookie, true)
.should.eventually.be.rejected.and.have.property('code', 'auth/session-cookie-revoked');
});
}).timeout(5000);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we drop .timeout(5000) calls now that we are setting it in package.json?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

});
}).timeout(5000);

it('fails when called with an invalid ID token', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably enough to have tests like these that do not make remote API calls as unit tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok removed these 2 tests.

.then((resp) => {
throw new Error('Unexpected success');
}, (error) => {
expect(error).to.deep.equal(expectedError);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the error have some code that we can test here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server will return the error code INVALID_ID_TOKEN which gets mapped to developer facing error AuthClientErrorCode.INVALID_ID_TOKEN. The deep equal assertion here should test for exact code match.

Copy link
Contributor

@hiranya911 hiranya911 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

Thanks for making the changes.

@bojeil-google
Copy link
Contributor Author

Thanks for the prompt review, @hiranya911 !

@hiranya911
Copy link
Contributor

@bojeil-google I forgot to mention this in the code review. Please add an entry to the CHANGELOG file as part of this PR.

@bojeil-google
Copy link
Contributor Author

I updated the changelog file. PTAL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants