verifyToken() - Verify JWT signature with JWKSdecodeToken() - Decode JWT without verificationextractUserId() - Extract user ID from subjectisClientToken() - Check if token is M2M clientisTokenExpired() - Check token expirationshouldRefreshToken() - Check if refresh neededimport {
verifyToken,
decodeToken,
extractUserId,
isClientToken,
isTokenExpired,
shouldRefreshToken,
} from 'authsafe-express';async function verifyToken(
token: string,
domain: string,
clientId: string,
organizationId: string,
): Promise<AuthSafeJWTPayload>;| Parameter | Type | Description |
|---|---|---|
token | string | JWT token to verify |
domain | string | AuthSafe domain (issuer) |
clientId | string | Expected audience claim |
organizationId | string | Organization ID for JWKS URL |
import { verifyToken, decodeToken } from 'authsafe-express';
app.post('/api/verify', async (req, res) => {
const { token } = req.body;
try {
// First decode to get organization ID
const decoded = decodeToken(token);
if (!decoded) {
return res.status(400).json({ error: 'Invalid token format' });
}
// Verify signature and claims
const payload = await verifyToken(
token,
process.env.AUTHSAFE_DOMAIN!,
process.env.AUTHSAFE_CLIENT_ID!,
decoded.org_id,
);
res.json({ valid: true, payload });
} catch (error) {
res.status(401).json({ valid: false, error: 'Invalid token' });
}
});{domain}:{organizationId}function decodeToken(token: string): AuthSafeJWTPayload | null;import { decodeToken } from 'authsafe-express';
app.get('/api/token-info', (req, res) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
const payload = decodeToken(token);
if (!payload) {
return res.status(400).json({ error: 'Invalid token format' });
}
res.json({
userId: payload.sub,
organizationId: payload.org_id,
expiresAt: payload.exp,
issuedAt: payload.iat,
});
});AuthSafeJWTPayload if token is valid Base64url-encoded JWT, otherwise null.
authsafe| or client| prefix.
function extractUserId(sub: string): string;import { extractUserId } from 'authsafe-express';
// Subject: "authsafe|user_123"
const userId = extractUserId('authsafe|user_123');
console.log(userId); // "user_123"
// Subject: "client|app_456"
const clientId = extractUserId('client|app_456');
console.log(clientId); // "app_456"
// No prefix
const rawId = extractUserId('custom_789');
console.log(rawId); // "custom_789"import { decodeToken, extractUserId } from 'authsafe-express';
app.get('/api/user-id', (req, res) => {
const token = req.cookies['authsafe.access_token'];
const payload = decodeToken(token);
if (!payload) {
return res.status(401).json({ error: 'Invalid token' });
}
const userId = extractUserId(payload.sub);
res.json({ userId });
});function isClientToken(sub: string): boolean;import { isClientToken } from 'authsafe-express';
console.log(isClientToken('authsafe|user_123')); // false
console.log(isClientToken('client|app_456')); // trueimport { decodeToken, isClientToken } from 'authsafe-express';
app.post('/api/admin', (req, res) => {
const token = req.cookies['authsafe.access_token'];
const payload = decodeToken(token);
if (!payload) {
return res.status(401).json({ error: 'Invalid token' });
}
// Block M2M clients from accessing user-specific endpoints
if (isClientToken(payload.sub)) {
return res.status(403).json({
error: 'Client credentials not allowed for this endpoint',
});
}
res.json({ message: 'User-only endpoint' });
});function isTokenExpired(exp: number): boolean;exp - Expiration timestamp (Unix seconds from JWT exp claim)import { isTokenExpired } from 'authsafe-express';
const expiresAt = 1735689600; // Some future timestamp
console.log(isTokenExpired(expiresAt)); // false
const expiredAt = 1609459200; // Past timestamp
console.log(isTokenExpired(expiredAt)); // trueimport { decodeToken, isTokenExpired } from 'authsafe-express';
app.get('/api/check-session', (req, res) => {
const token = req.cookies['authsafe.access_token'];
const payload = decodeToken(token);
if (!payload || isTokenExpired(payload.exp)) {
return res.status(401).json({
expired: true,
message: 'Session expired',
});
}
res.json({
expired: false,
expiresAt: payload.exp,
});
});function shouldRefreshToken(exp: number): boolean;exp - Expiration timestamp (Unix seconds from JWT exp claim)import { shouldRefreshToken } from 'authsafe-express';
const expiresAt = Math.floor(Date.now() / 1000) + 600; // Expires in 10 minutes
console.log(shouldRefreshToken(expiresAt)); // false
const expiresSoon = Math.floor(Date.now() / 1000) + 200; // Expires in 3.3 minutes
console.log(shouldRefreshToken(expiresSoon)); // trueimport {
getAuthCookies,
shouldRefreshToken,
refreshTokens,
setAuthCookies,
} from 'authsafe-express';
async function autoRefreshMiddleware(req: any, res: any, next: any) {
const tokens = getAuthCookies(req, authConfig);
if (!tokens) {
return next();
}
// Refresh if token expires in < 5 minutes
if (shouldRefreshToken(tokens.expiresAt) && tokens.refreshToken) {
try {
const newTokens = await refreshTokens(tokens.refreshToken, {
clientId: process.env.AUTHSAFE_CLIENT_ID!,
clientSecret: process.env.AUTHSAFE_CLIENT_SECRET!,
domain: process.env.AUTHSAFE_DOMAIN!,
});
setAuthCookies(res, newTokens, authConfig);
console.log('Token auto-refreshed');
} catch (error) {
console.error('Auto-refresh failed:', error);
}
}
next();
}interface AuthSafeJWTPayload {
/** Issuer (AuthSafe domain) */
iss: string;
/** Subject (user ID with "authsafe|" prefix or "client|" for M2M) */
sub: string;
/** Audience (client ID) */
aud: string;
/** Issued at (Unix timestamp in seconds) */
iat: number;
/** Expiration time (Unix timestamp in seconds) */
exp: number;
/** Organization ID */
org_id: string;
/** Scopes (access token only) */
scope?: string[];
/** Nonce (ID token only) */
nonce?: string;
}import { verifyToken, decodeToken } from 'authsafe-express';
function verifyBearerToken() {
return async (req: any, res: any, next: any) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.substring(7);
try {
// Decode to get org_id
const decoded = decodeToken(token);
if (!decoded) {
return res.status(401).json({ error: 'Invalid token' });
}
// Verify signature
const payload = await verifyToken(
token,
process.env.AUTHSAFE_DOMAIN!,
process.env.AUTHSAFE_CLIENT_ID!,
decoded.org_id,
);
req.auth = {
userId: extractUserId(payload.sub),
organizationId: payload.org_id,
scopes: payload.scope || [],
payload,
};
next();
} catch (error) {
res.status(401).json({ error: 'Invalid or expired token' });
}
};
}
// Use in routes
app.get('/api/protected', verifyBearerToken(), (req, res) => {
res.json({ user: req.auth });
});import {
decodeToken,
isTokenExpired,
shouldRefreshToken,
isClientToken,
extractUserId,
} from 'authsafe-express';
app.post('/api/inspect-token', (req, res) => {
const { token } = req.body;
const payload = decodeToken(token);
if (!payload) {
return res.status(400).json({ error: 'Invalid token format' });
}
res.json({
userId: extractUserId(payload.sub),
organizationId: payload.org_id,
scopes: payload.scope || [],
issuedAt: new Date(payload.iat * 1000).toISOString(),
expiresAt: new Date(payload.exp * 1000).toISOString(),
isExpired: isTokenExpired(payload.exp),
shouldRefresh: shouldRefreshToken(payload.exp),
isClient: isClientToken(payload.sub),
timeUntilExpiry: payload.exp - Math.floor(Date.now() / 1000),
});
});import { decodeToken } from 'authsafe-express';
function hasScope(requiredScope: string) {
return (req: any, res: any, next: any) => {
const token = req.cookies['authsafe.access_token'];
const payload = decodeToken(token);
if (!payload || !payload.scope) {
return res.status(401).json({ error: 'Unauthorized' });
}
if (!payload.scope.includes(requiredScope)) {
return res.status(403).json({
error: 'Forbidden',
required: requiredScope,
available: payload.scope,
});
}
next();
};
}
app.delete('/api/admin/users/:id', hasScope('admin:delete'), (req, res) => {
res.json({ message: 'User deleted' });
});// First verification: Fetches JWKS from AuthSafe
await verifyToken(token1, domain, clientId, orgId);
// Subsequent verifications: Use cached JWKS (fast)
await verifyToken(token2, domain, clientId, orgId);{domain}:{organizationId}
// ✅ Efficient
const decoded = decodeToken(token);
if (decoded) {
const verified = await verifyToken(token, domain, clientId, decoded.org_id);
}
// ❌ Won't work - need org_id
await verifyToken(token, domain, clientId, '???');verifyToken() for authenticationisTokenExpired() before trustingconst payload = decodeToken(token);
if (!payload) {
console.error('Token is not valid JWT format');
}// Verify JWKS URL is reachable
const jwksUrl = `${domain}/auth/.well-known/jwks.json?organization_id=${orgId}`;
console.log('JWKS URL:', jwksUrl);