-
Notifications
You must be signed in to change notification settings - Fork 426
update id_token when a new Access Token is fetched #2189
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
base: main
Are you sure you want to change the base?
Changes from all commits
9a404eb
ef2f1a8
85d782f
150aee2
ca45b93
30b1099
add285e
67d36df
fb50abb
8828596
734aa1f
e8ac4a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,8 @@ import { | |
LogoutToken, | ||
SessionData, | ||
StartInteractiveLoginOptions, | ||
TokenSet | ||
TokenSet, | ||
User | ||
} from "../types/index.js"; | ||
import { | ||
ensureNoLeadingSlash, | ||
|
@@ -577,18 +578,9 @@ export class AuthClient { | |
|
||
const res = await this.onCallback(null, onCallbackCtx, session); | ||
|
||
if (this.beforeSessionSaved) { | ||
const updatedSession = await this.beforeSessionSaved( | ||
session, | ||
oidcRes.id_token ?? null | ||
); | ||
session = { | ||
...updatedSession, | ||
internal: session.internal | ||
}; | ||
} else { | ||
session.user = filterDefaultIdTokenClaims(idTokenClaims); | ||
} | ||
// call beforeSessionSaved callback if present | ||
// if not then filter id_token claims with default rules | ||
session = await this.finalizeSession(session, oidcRes.id_token); | ||
|
||
await this.sessionStore.set(req.cookies, res.cookies, session, true); | ||
addCacheControlHeadersForSession(res); | ||
|
@@ -633,7 +625,9 @@ export class AuthClient { | |
); | ||
} | ||
|
||
const [error, updatedTokenSet] = await this.getTokenSet(session.tokenSet); | ||
const [error, getTokenSetResponse] = await this.getTokenSet( | ||
session.tokenSet | ||
); | ||
|
||
if (error) { | ||
return NextResponse.json( | ||
|
@@ -648,6 +642,9 @@ export class AuthClient { | |
} | ||
); | ||
} | ||
|
||
const { tokenSet: updatedTokenSet, idTokenClaims } = getTokenSetResponse; | ||
|
||
const res = NextResponse.json({ | ||
token: updatedTokenSet.accessToken, | ||
scope: updatedTokenSet.scope, | ||
|
@@ -656,11 +653,20 @@ export class AuthClient { | |
|
||
if ( | ||
updatedTokenSet.accessToken !== session.tokenSet.accessToken || | ||
updatedTokenSet.refreshToken !== session.tokenSet.refreshToken || | ||
updatedTokenSet.expiresAt !== session.tokenSet.expiresAt | ||
updatedTokenSet.expiresAt !== session.tokenSet.expiresAt || | ||
updatedTokenSet.refreshToken !== session.tokenSet.refreshToken | ||
) { | ||
if (idTokenClaims) { | ||
session.user = idTokenClaims as User; | ||
} | ||
// call beforeSessionSaved callback if present | ||
// if not then filter id_token claims with default rules | ||
const finalSession = await this.finalizeSession( | ||
session, | ||
updatedTokenSet.idToken | ||
); | ||
await this.sessionStore.set(req.cookies, res.cookies, { | ||
...session, | ||
...finalSession, | ||
tokenSet: updatedTokenSet | ||
}); | ||
addCacheControlHeadersForSession(res); | ||
|
@@ -710,13 +716,17 @@ export class AuthClient { | |
} | ||
|
||
/** | ||
* getTokenSet returns a valid token set. If the access token has expired, it will attempt to | ||
* refresh it using the refresh token, if available. | ||
* Retrieves OAuth token sets, handling token refresh when necessary or if forced. | ||
* | ||
* @returns A tuple containing either: | ||
* - `[SdkError, null]` if an error occurred (missing refresh token, discovery failure, or refresh failure) | ||
* - `[null, {tokenSet, idTokenClaims}]` if a new token was retrieved, containing the new token set ID token claims | ||
* - `[null, {tokenSet, }]` if token refresh was not done and existing token was returned | ||
*/ | ||
async getTokenSet( | ||
tokenSet: TokenSet, | ||
forceRefresh?: boolean | undefined | ||
): Promise<[null, TokenSet] | [SdkError, null]> { | ||
): Promise<[null, GetTokenSetResponse] | [SdkError, null]> { | ||
tusharpandey13 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// the access token has expired but we do not have a refresh token | ||
if (!tokenSet.refreshToken && tokenSet.expiresAt <= Date.now() / 1000) { | ||
return [ | ||
|
@@ -771,6 +781,7 @@ export class AuthClient { | |
]; | ||
} | ||
|
||
const idTokenClaims = oauth.getValidatedIdTokenClaims(oauthRes)!; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since |
||
const accessTokenExpiresAt = | ||
Math.floor(Date.now() / 1000) + Number(oauthRes.expires_in); | ||
|
||
|
@@ -789,11 +800,17 @@ export class AuthClient { | |
updatedTokenSet.refreshToken = tokenSet.refreshToken; | ||
} | ||
|
||
return [null, updatedTokenSet]; | ||
return [ | ||
null, | ||
{ | ||
tokenSet: updatedTokenSet, | ||
idTokenClaims: idTokenClaims | ||
} | ||
]; | ||
} | ||
} | ||
|
||
return [null, tokenSet]; | ||
return [null, { tokenSet, idTokenClaims: undefined }]; | ||
} | ||
|
||
private async discoverAuthorizationServerMetadata(): Promise< | ||
|
@@ -1161,6 +1178,32 @@ export class AuthClient { | |
|
||
return [null, connectionTokenSet] as [null, ConnectionTokenSet]; | ||
} | ||
|
||
/** | ||
* Filters and processes ID token claims for a session. | ||
* | ||
* If a `beforeSessionSaved` callback is configured, it will be invoked to allow | ||
* custom processing of the session and ID token. Otherwise, default filtering | ||
* will be applied to remove standard ID token claims from the user object. | ||
*/ | ||
async finalizeSession( | ||
session: SessionData, | ||
idToken?: string | ||
): Promise<SessionData> { | ||
if (this.beforeSessionSaved) { | ||
const updatedSession = await this.beforeSessionSaved( | ||
session, | ||
idToken ?? null | ||
); | ||
session = { | ||
...updatedSession, | ||
internal: session.internal | ||
}; | ||
} else { | ||
session.user = filterDefaultIdTokenClaims(session.user); | ||
} | ||
return session; | ||
} | ||
Comment on lines
+1189
to
+1206
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This logic was duplicated at 3 places so moved this to a seperate method. Also, moving to a method was necessary to remove this logic from |
||
} | ||
|
||
const encodeBase64 = (input: string) => { | ||
|
@@ -1175,3 +1218,8 @@ const encodeBase64 = (input: string) => { | |
} | ||
return btoa(arr.join("")); | ||
}; | ||
|
||
type GetTokenSetResponse = { | ||
tokenSet: TokenSet; | ||
idTokenClaims?: { [key: string]: any }; | ||
}; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
Uh oh!
There was an error while loading. Please reload this page.
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.
getTokenSet
now returnstokenSet
and parseduser
object from id_token claims on success.handleAccessToken
updated to set session ifuser
is avaialble fromgetTokenSet
finalizeSession
was added that callsbeforeSessionSaved
hook if supplied else fitlers the ID token claims insession.user
using the default filtering rules.