improve sso login flow
This commit is contained in:
parent
29507ba7c6
commit
ca93d6a3b2
89
IFRAME_LOGOUT_FIX.md
Normal file
89
IFRAME_LOGOUT_FIX.md
Normal file
@ -0,0 +1,89 @@
|
||||
# Iframe Logout Session Invalidation Fix
|
||||
|
||||
## Problem
|
||||
|
||||
When a user logs out from an application inside an iframe:
|
||||
1. The iframe application calls Keycloak logout endpoint
|
||||
2. Keycloak session is invalidated
|
||||
3. NextAuth dashboard still has a valid JWT token
|
||||
4. When NextAuth tries to refresh the token, Keycloak returns: `{ error: 'invalid_grant', error_description: 'Session not active' }`
|
||||
5. This causes a `JWT_SESSION_ERROR` and the user sees errors but isn't automatically signed out
|
||||
|
||||
## Root Cause
|
||||
|
||||
The `refreshAccessToken` function was catching all errors generically and setting `error: "RefreshAccessTokenError"`. When the session callback received this error, it would throw, causing a JWT_SESSION_ERROR but not properly signing the user out.
|
||||
|
||||
## Solution
|
||||
|
||||
### 1. Detect Session Invalidation
|
||||
|
||||
In `refreshAccessToken`, we now specifically detect when Keycloak returns `invalid_grant` with "Session not active":
|
||||
|
||||
```typescript
|
||||
if (refreshedTokens.error === 'invalid_grant' ||
|
||||
refreshedTokens.error_description?.includes('Session not active') ||
|
||||
refreshedTokens.error_description?.includes('Token is not active')) {
|
||||
return {
|
||||
...token,
|
||||
error: "SessionNotActive",
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Clear Tokens in JWT Callback
|
||||
|
||||
When we detect `SessionNotActive`, we clear the tokens in the JWT callback:
|
||||
|
||||
```typescript
|
||||
if (refreshedToken.error === "SessionNotActive") {
|
||||
return {
|
||||
...refreshedToken,
|
||||
accessToken: undefined,
|
||||
refreshToken: undefined,
|
||||
idToken: undefined,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Return Null in Session Callback
|
||||
|
||||
When tokens are missing or session is invalidated, the session callback returns `null`, which makes NextAuth treat the user as unauthenticated:
|
||||
|
||||
```typescript
|
||||
if (token.error === "SessionNotActive" || !token.accessToken) {
|
||||
return null as any; // NextAuth will treat user as unauthenticated
|
||||
}
|
||||
```
|
||||
|
||||
## Result
|
||||
|
||||
Now when a user logs out from an iframe application:
|
||||
1. Keycloak session is invalidated
|
||||
2. NextAuth detects the invalid session on next token refresh
|
||||
3. Tokens are cleared
|
||||
4. Session callback returns null
|
||||
5. User is automatically treated as unauthenticated
|
||||
6. NextAuth redirects to sign-in page (via AuthCheck component)
|
||||
|
||||
## Files Modified
|
||||
|
||||
- `app/api/auth/options.ts`:
|
||||
- Enhanced `refreshAccessToken` to detect `invalid_grant` errors
|
||||
- Clear tokens when session is invalidated
|
||||
- Return null from session callback when session is invalid
|
||||
|
||||
## Testing
|
||||
|
||||
To test this fix:
|
||||
1. Log in to the dashboard
|
||||
2. Open an iframe application
|
||||
3. Log out from the iframe application
|
||||
4. Wait for NextAuth to try to refresh the token (or trigger a page refresh)
|
||||
5. User should be automatically signed out and redirected to sign-in
|
||||
|
||||
---
|
||||
|
||||
**Date**: 2024
|
||||
**Status**: ✅ Fixed
|
||||
**Version**: 1.0
|
||||
|
||||
@ -95,6 +95,17 @@ async function refreshAccessToken(token: ExtendedJWT) {
|
||||
const refreshedTokens = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
// Check if the error is due to invalid session (e.g., user logged out from iframe)
|
||||
if (refreshedTokens.error === 'invalid_grant' ||
|
||||
refreshedTokens.error_description?.includes('Session not active') ||
|
||||
refreshedTokens.error_description?.includes('Token is not active')) {
|
||||
console.log("Keycloak session invalidated (likely logged out from iframe), marking token for removal");
|
||||
// Return token with specific error to trigger session invalidation
|
||||
return {
|
||||
...token,
|
||||
error: "SessionNotActive",
|
||||
};
|
||||
}
|
||||
throw refreshedTokens;
|
||||
}
|
||||
|
||||
@ -106,8 +117,19 @@ async function refreshAccessToken(token: ExtendedJWT) {
|
||||
idToken: token.idToken,
|
||||
accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error("Error refreshing access token:", error);
|
||||
|
||||
// Check if it's an invalid_grant error (session invalidated)
|
||||
if (error?.error === 'invalid_grant' ||
|
||||
error?.error_description?.includes('Session not active') ||
|
||||
error?.error_description?.includes('Token is not active')) {
|
||||
return {
|
||||
...token,
|
||||
error: "SessionNotActive",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...token,
|
||||
error: "RefreshAccessTokenError",
|
||||
@ -194,13 +216,38 @@ export const authOptions: NextAuthOptions = {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if token is expired and needs refresh
|
||||
if (Date.now() < (token.accessTokenExpires as number) * 1000) {
|
||||
return token;
|
||||
}
|
||||
|
||||
return refreshAccessToken(token);
|
||||
// Token expired, try to refresh
|
||||
const refreshedToken = await refreshAccessToken(token);
|
||||
|
||||
// If refresh failed due to invalid session, clear the token to force re-authentication
|
||||
if (refreshedToken.error === "SessionNotActive") {
|
||||
console.log("Keycloak session invalidated, clearing token to force re-authentication");
|
||||
// Return a token that will cause session callback to return null
|
||||
return {
|
||||
...refreshedToken,
|
||||
accessToken: undefined,
|
||||
refreshToken: undefined,
|
||||
idToken: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return refreshedToken;
|
||||
},
|
||||
async session({ session, token }) {
|
||||
// If session was invalidated or tokens are missing, return null to sign out
|
||||
if (token.error === "SessionNotActive" || !token.accessToken) {
|
||||
console.log("Session invalidated or tokens missing, user will be signed out");
|
||||
// Return null to make NextAuth treat user as unauthenticated
|
||||
// This will trigger automatic redirect to sign-in page
|
||||
return null as any;
|
||||
}
|
||||
|
||||
// For other errors, throw to trigger error handling
|
||||
if (token.error) {
|
||||
throw new Error(token.error as string);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user