Sessions and access
Sessions
Session cookies, reading the user, refresh, local verification and revocation.
Cookies#
After sign-in the SDK sets httpOnly cookies on your app's domain. They are SameSite=Lax, Path=/, and Secure in production. Their name comes from your client ID, so two apps on one domain don't read each other's cookies.
| Cookie | Holds |
|---|---|
<clientId>_session | The access token: a JWT signed (RS256) with your environment’s key. |
<clientId>_session_refresh | The refresh token, used to renew the access token. |
<clientId>_session_mfa | Only during an embedded sign-in with MFA: the pending sign-in, for 10 minutes. |
By default an access token lasts 60 minutes and a session 7 days; both are set per environment under Authentication → Sessions. Set cookie.name in the config to choose another name.
Reading the session#
| Call | Where | Checks with the API? | Returns |
|---|---|---|---|
getAuth(config) | Next.js server | No: verifies the signature locally | Verified claims, userId and has(), or null |
getUser(config) | Next.js server | Yes: sees revoked sessions | AuthUser, or null (refreshes first when needed) |
req.user (authMiddleware) | Express | Yes | AuthUser |
useSession() | Browser | Through your /api/auth/user | The user for display, loading, has(), refresh() |
// app/account/page.tsx — the user record, checked with the API
import { redirect } from 'next/navigation';
import { getUser } from '@3een/auth/nextjs';
import { authConfig } from '@/lib/auth-config';
export default async function AccountPage() {
// Asks the API, so a revoked session is refused here even while its token
// would still verify locally. Refreshes an expiring session first.
const user = await getUser(authConfig);
if (!user) redirect('/api/auth/signin?redirect_uri=/account');
return (
<dl>
<dt>Email</dt>
<dd>{user.email}</dd>
<dt>Verified</dt>
<dd>{user.emailVerified ? 'Yes' : 'No'}</dd>
</dl>
);
}Refresh#
When the access token is within five minutes of expiring and a refresh cookie exists, the Next.js middleware, getUser and the Express authMiddleware renew it through the API and write the new cookies. If the refresh call fails, for example on a network error, the current token keeps working until it expires. From your own server code, AuthClient.refreshToken(refreshToken) does the same.
Verifying a token yourself#
verifySessionToken(token, { clientId, apiUrl }) checks a token against your environment's published keys at /.well-known/jwks.json?client_id=…. The keys are cached, and fetched again when a new key ID appears (after a rotation). It never throws; it returns one of three results.
// Verify a session token yourself, e.g. in a service that receives
// `Authorization: Bearer <token>` from your frontend.
import { verifySessionToken } from '@3een/auth';
export async function userIdFromBearer(authorization: string | null): Promise<string | null> {
const token = authorization?.startsWith('Bearer ') ? authorization.slice(7) : null;
if (!token) return null;
const result = await verifySessionToken(token, {
clientId: process.env.AUTH_CLIENT_ID!,
apiUrl: process.env.AUTH_API_URL,
});
switch (result.status) {
case 'valid':
return result.claims.id ?? null;
case 'legacy':
// An old HS256 session: no longer accepted anywhere. Sign in again.
return null;
case 'invalid':
console.warn('Session refused:', result.reason);
return null;
}
}- A token from another environment, another customer's app or the 3een dashboard is
invalidhere, even though its signature is genuine: keys are per environment. - If the key set can't be fetched, the token is
invalid. An outage never becomes a way in.
Revocation#
A session ends when the user signs out, when their password is reset (every session of that user), when an administrator removes it (DELETE /app/users/:id/sessions/:sessionId), or when the account is blocked or deprovisioned. A revoked session can't be refreshed.