Enhance Auth Endpoints To Support Sessions
Update your auth endpoints to properly manage user sessions.
Now that you've configured your application to support session cookies, we need to update our authentication endpoints to manage the lifecycle of the user's session.
Update Existing Auth Endpoints
First, we'll need to update the existing Callback and Logout endpoints to create and clean up a user's session, respectively.
Update Callback Endpoint
After a user has successfully authenticated, Wristband will redirect to your application's Callback Endpoint. Calling the wristbandAuth.pageRouter.callback
or wristbandAuth.appRouter.callback
functions will return a CallbackResult
object containing the user's tokens and claims. We can use these tokens and claims to create the user's session by calling the session.save()
function.
The below code example shows how to update your existing Callback Endpoint to create the user's session:
//
// Location -> src/pages/api/auth/callback.ts
//
import type { NextApiRequest, NextApiResponse } from 'next';
import { CallbackResultType, PageRouterCallbackResult } from '@wristband/nextjs-auth';
import { wristbandAuth } from '@/wristband-auth';
import { getSession } from '@/session/session';
// Callback Endpoint
export default async function handleCallback(req: NextApiRequest, res: NextApiResponse) {
try {
const callbackResult: PageRouterCallbackResult = await wristbandAuth.pageRouter.callback(req, res);
const { callbackData, result } = callbackResult;
if (result === CallbackResultType.REDIRECT_REQUIRED) {
return;
}
/* ***** BEGIN NEW SESSION LOGIC ***** */
// Establish the user's session.
const session = await getSession(req, res);
// You can choose which user info to store in the session based on your app requirements.
// The following fields are a good minimum to include.
session.isAuthenticated = true;
session.accessToken = callbackData.accessToken;
// Convert expiration seconds to a Unix timestamp in milliseconds.
session.expiresAt = Date.now() + callbackData.expiresIn * 1000;
session.refreshToken = callbackData.refreshToken;
session.userId = callbackData.userinfo.sub;
session.tenantId = callbackData.userinfo.tnt_id;
session.tenantDomainName = callbackData.tenantDomainName;
session.tenantCustomDomain = callbackData.tenantCustomDomain || undefined;
// This persists any updated session values.
await session.save();
/* ***** END NEW SESSION LOGIC ***** */
return res.redirect(callbackData!.returnUrl || '<replace_with_a_default_return_url>');
} catch (error) {
console.error(error);
return res.status(500).json({ error: "Internal Server Error" });
}
}
//
// Location -> src/app/api/auth/callback/route.ts
//
import { NextRequest } from 'next/server';
import { AppRouterCallbackResult, CallbackResultType } from '@wristband/nextjs-auth';
import { wristbandAuth } from '@/wristband-auth';
import { getSession } from '@/session/session';
// Callback Endpoint
export async function GET(req: NextRequest) {
try {
const callbackResult: AppRouterCallbackResult = await wristbandAuth.appRouter.callback(req);
const { callbackData, redirectResponse, result } = callbackResult;
if (result === CallbackResultType.REDIRECT_REQUIRED) {
return redirectResponse;
}
const appUrl = callbackData!.returnUrl || '<replace_with_a_default_return_url>';
const callbackResponse = await wristbandAuth.appRouter.createCallbackResponse(req, appUrl);
/* ***** BEGIN NEW SESSION LOGIC ***** */
// Establish the user's session.
const session = await getSession();
// You can choose which user info to store in the session based on your app requirements.
// The following fields are a good minimum to include.
session.isAuthenticated = true;
session.accessToken = callbackData.accessToken;
// Convert expiration seconds to a Unix timestamp in milliseconds.
session.expiresAt = Date.now() + callbackData.expiresIn * 1000;
session.refreshToken = callbackData.refreshToken;
session.userId = callbackData.userinfo.sub;
session.tenantId = callbackData.userinfo.tnt_id;
session.tenantDomainName = callbackData.tenantDomainName;
session.tenantCustomDomain = callbackData.tenantCustomDomain || undefined;
// This persists any updated session values.
await session.save();
/* ***** END NEW SESSION LOGIC ***** */
return callbackResponse;
} catch (error) {
console.error(error);
return Response.json({ error: "Internal Server Error" }, { status: 500 });
}
}
Update Logout Endpoint
When a user logs out of your application, you need to ensure that all authenticated state associated with the user is cleaned up. Therefore, we need to update our Logout Endpoint to invalidate the user's session and revoke their refresh token (assuming the session contains a refresh token).
The below code example shows how to update your existing Logout Endpoint to clean up the user's session:
//
// Location -> src/pages/api/auth/logout.ts
//
import type { NextApiRequest, NextApiResponse } from 'next';
import { wristbandAuth } from '@/wristband-auth';
import { getSession } from '@/session/session';
// Logout endpoint
export default async function handleLogout(req: NextApiRequest, res: NextApiResponse) {
try {
/* ***** BEGIN NEW SESSION LOGIC ***** */
const session = await getSession(req, res);
const { refreshToken, tenantCustomDomain, tenantDomainName } = session;
const logoutConfig = { refreshToken, tenantCustomDomain, tenantDomainName };
res.setHeader('Set-Cookie', [`session=; Max-Age=0; Path=/`]);
session.destroy();
/* ***** END NEW SESSION LOGIC ***** */
return await wristbandAuth.pageRouter.logout(req, res, logoutConfig);
} catch (error) {
console.error(error);
return res.status(500).json({ error: "Internal Server Error" });
}
}
//
// Location -> src/app/api/auth/logout/route.ts
//
import type { NextRequest } from 'next/server';
import { cookies } from 'next/headers';
import { wristbandAuth } from '@/wristband-auth';
import { getSession } from '@/session/session';
// Logout endpoint
export async function GET(req: NextRequest) {
try {
/* ***** BEGIN NEW SESSION LOGIC ***** */
const session = await getSession();
const { refreshToken, tenantCustomDomain, tenantDomainName } = session;
const logoutConfig = { refreshToken, tenantCustomDomain, tenantDomainName };
const cookieStore = await cookies();
cookieStore.delete('session');
session.destroy();
/* ***** END NEW SESSION LOGIC ***** */
return await wristbandAuth.appRouter.logout(req, logoutConfig);
} catch (error) {
console.error(error);
return Response.json({ error: "Internal Server Error" }, { status: 500 });
}
}
Create a Session Endpoint
In addition to updating the Callback and Logout endpoints, we'll also need to create a Session Endpoint. The Session Endpoint will check that the incoming request has a valid session, and, if so, it will return a response containing the user's session data. The Session Endpoint serves two primary purposes:
- It provides a way for the frontend to check whether the user has a valid session.
- It allows the frontend to access the user's session data so it can be utilized in the browser.
We'll structure the response to match what the React Client Auth SDK expects (which will be covered in the next section of this guide).
At a minimum, the response should include userId
and tenantId
.
//
// Location -> src/pages/api/v1/session.ts
//
import type { NextApiRequest, NextApiResponse } from 'next';
import { getSession } from '@/session/session';
// Session Endpoint
export default async function sessionRoute(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession(req, res);
const { isAuthenticated, userId, tenantId } = session;
// Check that the user has an authenticated session. If they don't, return a 401.
if (!isAuthenticated) {
return res.status(401).end();
}
//
// If needed, you can make additional API calls to gather other session data you
// might want to return to your frontend.
//
// You can add any session data needed by the frontend to the response, but for
// now we'll add the bare minimum of the user's userId and tenantId.
return res.status(200).json({ userId, tenantId });
}
//
// Location -> src/app/api/v1/session/route.ts
//
import { NextResponse } from 'next/server';
import { getSession } from '@/session/session';
// Session Endpoint
export async function GET() {
const session = await getSession();
const { isAuthenticated, userId, tenantId } = session;
// Check that the user has an authenticated session. If they don't, return a 401.
if (!isAuthenticated) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
//
// If needed, you can make additional API calls to gather other session data you
// might want to return to your frontend.
//
// You can add any session data needed by the frontend to the response, but for
// now we'll add the bare minimum of the user's userId and tenantId.
return NextResponse.json({ userId, tenantId });
}
Updated 10 days ago
Now that session management is set up on the server, the next step is to integrate the sessions with the frontend.