@@ -5,6 +5,31 @@ import * as oidc from 'openid-client';
55import type { JWT } from '@auth/core/jwt' ;
66import { ZITADEL_SCOPES } from './lib/scopes' ;
77
8+ /**
9+ * Automatically refreshes an expired access token using the refresh token.
10+ *
11+ * When a user's access token expires (typically after 1 hour), this function
12+ * seamlessly exchanges the refresh token for a new access token, allowing the
13+ * user to continue using the application without having to log in again.
14+ *
15+ * This is essential for maintaining long-lived sessions and preventing users
16+ * from being unexpectedly logged out during active use of the application.
17+ *
18+ * ## How Token Refresh Works
19+ *
20+ * 1. **Token Expiry Detection**: Auth.js automatically checks if the access token has expired
21+ * 2. **Refresh Request**: Uses the refresh token to request new tokens from ZITADEL
22+ * 3. **Token Update**: Updates the JWT with the new access token and expiry time
23+ * 4. **Seamless Experience**: User continues without interruption
24+ *
25+ * ## Error Handling
26+ *
27+ * If the refresh fails (e.g., refresh token expired, user permissions revoked),
28+ * the function sets an error flag that forces the user to sign in again.
29+ *
30+ * @param token - The current JWT containing the refresh token and other session data
31+ * @returns Promise resolving to updated JWT with new tokens or error state
32+ */
833async function refreshAccessToken ( token : JWT ) : Promise < JWT > {
934 if ( ! token . refreshToken ) {
1035 console . error ( 'No refresh token available for refresh' ) ;
@@ -35,6 +60,30 @@ async function refreshAccessToken(token: JWT): Promise<JWT> {
3560 }
3661}
3762
63+ /**
64+ * Constructs a secure logout URL for ZITADEL with CSRF protection.
65+ *
66+ * This function creates a proper logout URL that terminates the user's session
67+ * both in your application and in ZITADEL. It includes security measures to
68+ * prevent Cross-Site Request Forgery (CSRF) attacks during the logout process.
69+ *
70+ * ## Security Features
71+ *
72+ * - **State Parameter**: Random UUID for CSRF protection
73+ * - **ID Token Hint**: Tells ZITADEL which session to terminate
74+ * - **Post-Logout Redirect**: Where to send the user after logout
75+ *
76+ * ## Logout Flow
77+ *
78+ * 1. User clicks "logout" in your app
79+ * 2. Your app calls this function to get the logout URL
80+ * 3. User is redirected to ZITADEL's logout endpoint
81+ * 4. ZITADEL terminates the session and redirects back to your app
82+ * 5. Your app validates the state parameter for security
83+ *
84+ * @param idToken - The user's ID token from their current session (used to identify which session to terminate)
85+ * @returns Promise containing the logout URL to redirect to and state value for validation
86+ */
3887export async function buildLogoutUrl (
3988 idToken : string ,
4089) : Promise < { url : string ; state : string } > {
@@ -68,12 +117,45 @@ export const { handlers, getSession, signInUrl, signOutUrl } = TanStackAuth({
68117 secret : process . env . SESSION_SECRET ,
69118 pages : { signIn : '/auth/login' , error : '/auth/error' } ,
70119 callbacks : {
120+ /**
121+ * Controls where users are redirected after successful authentication.
122+ *
123+ * This callback runs after a user successfully signs in and determines
124+ * their destination. By default, Auth.js would redirect to the page they
125+ * came from, but this override ensures all users go to the profile page.
126+ *
127+ * @param baseUrl - Your application's base URL (e.g., https://yourdomain.com)
128+ * @returns The URL to redirect the user to after successful login
129+ */
71130 async redirect ( { baseUrl } ) {
72131 const postLoginUrl = process . env . ZITADEL_POST_LOGIN_URL || '/profile' ;
73132 return postLoginUrl . startsWith ( 'http' )
74133 ? postLoginUrl
75134 : `${ baseUrl } ${ postLoginUrl } ` ;
76135 } ,
136+ /**
137+ * Manages JWT token lifecycle including storage and automatic refresh.
138+ *
139+ * This callback runs every time a JWT is accessed and handles:
140+ * 1. **Initial Login**: Stores tokens from the authentication provider
141+ * 2. **Token Expiry Check**: Determines if access token needs refreshing
142+ * 3. **Automatic Refresh**: Calls refresh function when token expires
143+ *
144+ * ## When This Runs
145+ * - Every time getSession() is called
146+ * - Before each authenticated API request
147+ *
148+ * ## Token Storage Strategy
149+ * - ID Token: Used for logout and user identification
150+ * - Access Token: Used for API calls to ZITADEL
151+ * - Refresh Token: Used to get new access tokens
152+ * - Expiry Time: Used to determine when refresh is needed
153+ *
154+ * @param token - Current JWT token object
155+ * @param account - Authentication provider data (only present on initial login)
156+ * @param user - User object (only present on initial login)
157+ * @returns Updated JWT token with fresh tokens or error state
158+ */
77159 async jwt ( { token, account, user } ) {
78160 if ( account && user ) {
79161 return {
@@ -90,6 +172,29 @@ export const { handlers, getSession, signInUrl, signOutUrl } = TanStackAuth({
90172 if ( Date . now ( ) < ( token . expiresAt as number ) ) return token ;
91173 return refreshAccessToken ( token ) ;
92174 } ,
175+ /**
176+ * Shapes the session object that your application receives.
177+ *
178+ * This callback transforms the internal JWT token into the session object
179+ * that your application code can access via getSession().
180+ *
181+ *
182+ * ## Security Note
183+ *
184+ * Only include data in the session that your frontend needs. Sensitive
185+ * tokens like refresh tokens should NOT be exposed to the client.
186+ *
187+ *
188+ *
189+ * ## Available Data
190+ * - **idToken**: For logout functionality
191+ * - **accessToken**: For API calls (if needed on the client-side)
192+ * - **error**: To handle token refresh failures
193+ *
194+ * @param session - The base session object from Auth.js
195+ * @param token - The JWT token containing all stored data
196+ * @returns The session object that your application will receive
197+ */
93198 async session ( { session, token } ) {
94199 session . idToken = token . idToken ;
95200 session . accessToken = token . accessToken ;
0 commit comments