Skip to content

Commit 76bb154

Browse files
committed
docs: add JSDoc on auth helpers + callbacks (parity with astro/nuxt/sveltekit/qwik)
1 parent e2abe17 commit 76bb154

1 file changed

Lines changed: 105 additions & 0 deletions

File tree

app/auth.server.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,31 @@ import * as oidc from 'openid-client';
55
import type { JWT } from '@auth/core/jwt';
66
import { 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+
*/
833
async 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+
*/
3887
export 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

Comments
 (0)