Token Verification & Refresh
Verify a user's identity from your backend using an API key.
Refresh expired access tokens transparently — the user never sees a session interruption.
Token types
| Token | Lifetime | Stored as | Purpose |
| access_token |
15 minutes |
httpOnly cookie |
Sent with every authenticated request. Short-lived to limit exposure. |
| refresh_token |
7 days |
httpOnly cookie |
Used only to get a new access token when the old one expires. |
| partial_token |
5 minutes |
httpOnly cookie |
Intermediate state during 2FA — exchanged for a full session. |
ℹ️
All cookies are set with httpOnly and Secure flags.
The browser JavaScript on your client app never has access to the raw token values.
Verify a token
Call this from your backend to confirm a user's identity and check their membership status.
Requires an API key — this endpoint is designed for server-to-server use, never called from the browser.
POST
/public/verify
Headers
| Header | | Description |
| X-Org-Slug | required | Your organization's slug. |
| X-API-Key | required | Your org API key (pk_live_…). |
Request body
| Field | Type | | Description |
| token |
string |
required |
The user's access_token JWT string. |
Request
curl -X POST https://authworx.uthings.io/api/v1/public/verify \
-H "X-Org-Slug: acme-corp" \
-H "X-API-Key: pk_live_abc123" \
-H "Content-Type: application/json" \
-d '{ "token": "eyJhbGciOiJIUzI1NiIs..." }'
Response — valid token (200)
{
"status": "success",
"data": {
"valid": true,
"user": {
"id": "usr_01HXYZ",
"name": "Alice Chen",
"email": "alice@acme.com",
"role": "user",
"isEmailVerified": true
},
"membership": {
"role": "member",
"status": "active",
"joinedAt": "2025-01-15T10:00:00.000Z"
},
"org": {
"id": "org_01ABCD",
"name": "Acme Corp",
"slug": "acme-corp",
"plan": "pro",
"status": "active"
}
}
}
Response — invalid or expired token (200)
{
"status": "success",
"data": { "valid": false }
}
⚠️
Always check data.valid === true and
data.membership.status === "active" before granting access.
A user may be valid but suspended from the org.
Refresh an access token
Exchange a valid refresh token for a new access token. The old access token is invalidated.
Call this when a protected route returns 401 — or proactively before a long operation.
POST
/auth/refresh
Cookies required
| Cookie | Description |
| refresh_token | Set during login. Valid for 7 days. |
Response — success (200)
{
"status": "success",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}
}
| Status | Cause |
| 401 | Refresh token is missing, expired (7 days), or revoked. User must log in again. |
Silent refresh pattern
The recommended pattern is transparent refresh inside your protected API route.
The user never sees a 401 — if the access token is expired, the route silently refreshes
it and retries the verify, all in a single request.
app/api/me/route.ts — transparent refresh
import { cookies } from 'next/headers';
import { authworxVerifyToken, authworxRefreshToken } from '@/lib/authworx';
import { setAuthCookies, clearAuthCookies } from '@/lib/cookies';
async function attemptRefresh(jar: Awaited<ReturnType<typeof cookies>>) {
const rt = jar.get('refresh_token')?.value;
if (!rt) return null;
const { data, error } = await authworxRefreshToken(rt);
if (error || !data?.accessToken) return null;
setAuthCookies(jar, data.accessToken, data.refreshToken);
return data.accessToken;
}
export async function GET() {
const jar = await cookies();
let token = jar.get('access_token')?.value;
// No access token — try refresh before giving up
if (!token) {
token = (await attemptRefresh(jar)) ?? undefined;
if (!token) {
clearAuthCookies(jar);
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
}
let result = await authworxVerifyToken(token);
// Token present but invalid (expired mid-flight) — refresh once and retry
if (!result.data?.valid) {
const fresh = await attemptRefresh(jar);
if (!fresh) {
clearAuthCookies(jar);
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
result = await authworxVerifyToken(fresh);
}
if (!result.data?.valid || result.data.membership?.status !== 'active') {
return Response.json({ error: 'Forbidden' }, { status: 403 });
}
return Response.json({ status: 'success', data: result.data });
}
💡
Copy the attemptRefresh helper into any protected route to get
transparent refresh for free. You only need one instance per route handler.