Protect Server Actions
Learn how to protect your Server Actions so only authenticated users can execute them.
Server Actions bypass the Next.js middleware/proxy (and thus bypass our auth middleware), so they cannot be protected automatically. You must validate authentication within each Server Action that requires it.
Configure Server Action Auth Helper
First, add the Server Action auth helper to your src/wristband.ts file by using the appRouter.createServerActionAuth() function. This helper validates the session and automatically refreshes expired tokens.
// src/wristband.ts
import { NextRequest } from 'next/server';
import {
createWristbandAuth,
getReadOnlySessionFromCookies,
getSessionFromRequest,
NextJsCookieStore,
SessionOptions,
} from '@wristband/nextjs-auth';
export const wristbandAuth = createWristbandAuth({
clientId: '<WRISTBAND_CLIENT_ID>',
clientSecret: '<WRISTBAND_CLIENT_SECRET>',
wristbandApplicationVanityDomain: '<WRISTBAND_APPLICATION_VANITY_DOMAIN>',
});
const sessionOptions: SessionOptions = {
secrets: '<your-generated-secret>'
};
export function getRequestSession(request: NextRequest) {
return getSessionFromRequest(request, sessionOptions);
}
export const requireAuth = wristbandAuth.createMiddlewareAuth({
authStrategies: ['SESSION'],
sessionConfig: {
sessionOptions,
sessionEndpoint: '/api/auth/session'
},
protectedPages: ['/', '/dashboard', '/settings(.*)'],
});
export function getServerComponentSession(cookieStore: NextJsCookieStore) {
return getReadOnlySessionFromCookies(cookieStore, sessionOptions);
}
/*
* NEW: Configure auth helper for Server Actions.
*/
export const requireServerActionAuth = wristbandAuth.appRouter.createServerActionAuth({
sessionOptions
});Protect a Server Action
Use the requireServerActionAuth() helper at the start of any Server Action that requires authentication. It always returns an authentication result, and it also returns the session object if the user is authenticated.
// src/app/actions/fetch-tenant.ts
'use server';
import { cookies } from 'next/headers';
import { requireServerActionAuth } from 'src/wristband';
export async function fetchTenant() {
const cookieStore = await cookies();
const { authenticated, session } = await requireServerActionAuth(cookieStore);
// Check authentication
if (!authenticated) {
return { error: 'You are not authenticated. Please log in again.' };
}
// Access session data
const { accessToken, tenantId } = session;
// Make authenticated API call
const url = `https://${'your-wristband-app-vanity-domain'}/api/v1/tenants/${tenantId}`;
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
return { error: 'Failed to fetch tenant data.' };
}
const tenant = await response.json();
return { data: tenant };
}Update Session Data in Server Actions (Optional)
Server Actions can modify session data. If you need more granular control or special handling for specific Server Actions, you can use mutable session helpers to retrieve, update, and save session changes.
Configure Server Action Session Helpers
First, add the mutable session helpers to your src/wristband.ts file:
// src/wristband.ts
import { NextRequest } from 'next/server';
import {
createWristbandAuth,
destroySessionWithCookies,
getMutableSessionFromCookies,
getReadOnlySessionFromCookies,
getSessionFromRequest,
MutableSession,
NextJsCookieStore,
saveSessionWithCookies,
SessionOptions,
} from '@wristband/nextjs-auth';
//
// ... existing wristbandAuth and sessionOptions config ...
//
export const requireServerActionAuth = wristbandAuth.appRouter.createServerActionAuth({
sessionOptions
});
/*
* NEW: Retrieves mutable session for Server Actions.
*/
export async function getServerActionSession(cookieStore: NextJsCookieStore) {
return await getMutableSessionFromCookies(cookieStore, sessionOptions);
}
/*
* NEW: Saves modified session data back to cookies.
*/
export async function saveServerActionSession(
cookieStore: NextJsCookieStore,
session: MutableSession
) {
await saveSessionWithCookies(cookieStore, session);
}
/*
* NEW: Destroys the session and clears session cookies.
*/
export function destroyServerActionSession(
cookieStore: NextJsCookieStore,
session: MutableSession
) {
destroySessionWithCookies(cookieStore, session);
}Use Server Action Session Helpers
Next, you can use these session helpers in your Server Actions to update session data.
// src/app/actions/update-tenant.ts
'use server';
import { cookies } from 'next/headers';
import {
destroyServerActionSession,
getServerActionSession,
saveServerActionSession
} from 'src/wristband';
export async function updateTenantDescription(formData: FormData) {
// Get the session without performing any auth checks
const cookieStore = await cookies();
const session = await getServerActionSession(cookieStore);
// Perform auth check and destory session if user is not authenticated
if (!session.isAuthenticated || !session.accessToken) {
destroyServerActionSession(cookieStore, session);
return { error: 'You are not authenticated. Please log in again.' };
}
const description = formData.get('description') as string;
// Access session data
const { accessToken, tenantId } = session;
// Make authenticated API call
const url = `https://${'your-wristband-app-vanity-domain'}/api/v1/tenants/${tenantId}`;
const response = await fetch(url, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ description })
});
if (!response.ok) {
return { error: 'Failed to update tenant.' };
}
// Update session with new data and save it
session.tenantDescription = description;
await saveServerActionSession(cookieStore, session);
const tenant = await response.json();
return { data: tenant };
}Updated about 10 hours ago
Next, let's protect your API routes so only authenticated requests can access them.