-
Notifications
You must be signed in to change notification settings - Fork 915
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
Auth cookie persistence #8839
base: main
Are you sure you want to change the base?
Auth cookie persistence #8839
Conversation
|
@@ -241,6 +242,9 @@ export async function _performSignInRequest<T, V extends IdTokenResponse>( | |||
request?: T, | |||
customErrorMap: Partial<ServerErrorMap<ServerError>> = {} | |||
): Promise<V> { | |||
// TODO(jamedaniels) if auth persistence is cookie, proxy through the server endpoint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you remove the completed TODO
comments from the source if they're actually done. It would help with the review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do
@@ -44,6 +44,8 @@ export class UserCredentialImpl | |||
this.operationType = params.operationType; | |||
} | |||
|
|||
// TODO(jamesdaniels) fetch the user credential from the cookie and response returned from the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -62,6 +62,8 @@ export interface UserInternal extends User { | |||
|
|||
auth: AuthInternal; | |||
providerId: ProviderId.FIREBASE; | |||
// TODO(jamesdaniels): refreshToken should either be optional or a sentinel value for COOKIE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -0,0 +1,82 @@ | |||
/** | |||
* @license | |||
* Copyright 2019 Google LLC |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2025
export class CookiePersistence implements PersistenceInternal { | ||
static type: 'COOKIE' = 'COOKIE'; | ||
readonly type = PersistenceType.COOKIE; | ||
listeners: Map<StorageEventListener, (e: any) => void> = new Map(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think our linter frowns up on the use of any
.
readonly type = PersistenceType.COOKIE; | ||
listeners: Map<StorageEventListener, (e: any) => void> = new Map(); | ||
|
||
async _isAvailable(): Promise<boolean> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add class and method comments?
} | ||
|
||
/** | ||
* An implementation of {@link Persistence} of type 'NONE'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this of type COOKIE
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
heh, yeah copy-pasta
@@ -1,5 +1,5 @@ | |||
{ | |||
"extends": "../../config/api-extractor.json", | |||
// Point it to your entry point d.ts file. | |||
"mainEntryPointFilePath": "<projectFolder>/dist/rules-unit-testing/index.d.ts" | |||
"mainEntryPointFilePath": "<projectFolder>/dist/index.d.ts" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this change in here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No idea, I should have left a comment this was to fix the build
@@ -241,6 +242,9 @@ export async function _performSignInRequest<T, V extends IdTokenResponse>( | |||
request?: T, | |||
customErrorMap: Partial<ServerErrorMap<ServerError>> = {} | |||
): Promise<V> { | |||
// TODO(jamedaniels) if auth persistence is cookie, proxy through the server endpoint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you remove the completed TODO
comments from the source if they're actually done. It would help with the review.
return null; | ||
} | ||
if (typeof blob === 'string') { | ||
const response = await getAccountInfo(this.auth, { idToken: blob }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we check if the token is still valid and return null if it's not? We already have validateTokenTTL
in app/src/firebaseServerApp.ts
which could be brought into our utils package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you tested this with an invalid token to see how it behaves?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call on both
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about this, in normal operation this shouldn't happen, as the server-side route should be covered by middleware which will return set-cookie—but if the developer made a mistake and didn't cover the route with middleware then this could expire—so we should handle & log a sensible error.
if (blob) { | ||
const user = UserImpl._fromJSON(auth, blob); // throws for unparsable blob (wrong format) | ||
let user: UserInternal; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, what if the token isn't valid or has expired?
@@ -845,6 +845,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService { | |||
} | |||
|
|||
async _getAppCheckToken(): Promise<string | undefined> { | |||
// @ts-ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not be needed, my local build was broken without it.
listeners: Map<StorageEventListener, (e: any) => void> = new Map(); | ||
|
||
async _isAvailable(): Promise<boolean> { | ||
return navigator.hasOwnProperty('cookieEnabled') ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably need a check on typeof navigator !== 'undefined'
just in case. IndexedDB persistence has a check on if (!indexedDB)
inside a try/catch which I think also works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good call, will do
(path.startsWith('/v1/accounts:signIn') || path === Endpoint.TOKEN) | ||
) { | ||
const params = new URLSearchParams({ finalTarget }); | ||
return `${window.location.origin}/__cookies__?${params.toString()}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function can be called in Node so we may have to make sure window
doesn't throw. I'm not sure if the === PersistenceType.COOKIE
is a guarantee that we're in browser, maybe safer to explicitly check for window. What's the "don't use startsWith v1/accounts" comment about?
Proof-of-concept of go/firebase-auth-cookie-persistence, pair with this NextJS middleware.
Demo here—login restricted to
@google.com
Google accounts. Only confirmed working in desktop Chrome ATM.Principles of operation:
COOKIE
/__cookie__
, which is handled by the NextJS middleware. The middleware this redacts the refreshToken and stores it in an HTTP-only cookie, the idToken is stored in an JS-readable cookiegetCurrentUser()
internally can accept an idToken and will initiated a fetch request to get the user-details""
and will make a best-effort attempt to hit/__cookie__
, the middleware treats the blank string as a logout and will delete the refreshToken cookie if seenThere's a lot to clean up here, some high level things that need to be addressed
/__cookie__
URL, this should come from the persistence manager and be configurablesetPersistence(auth, cookiePersistence)
was acting up, need to look into this, probably something to do with upgradesFor the middleware:
@firebase/auth