Token Verification
Verify Optare ID tokens on your backend using JWKS (JSON Web Key Set).
JWKS Endpoint
Optare publishes public keys at:
https://id.optare.one/.well-known/jwks.jsonUsing jose Library (Recommended)
The jose library provides secure JWT verification with automatic key rotation support.
Installation
npm install joseBasic Verification
import { createRemoteJWKSet, jwtVerify } from 'jose';
// Create JWKS client (cache this in production)
const JWKS = createRemoteJWKSet(
new URL('https://id.optare.one/.well-known/jwks.json')
);
// Verify a token
async function verifyToken(token: string) {
try {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://id.optare.one',
audience: 'your-client-id', // Optional: your OAuth client ID
});
return {
valid: true,
userId: payload.sub,
email: payload.email,
organizationId: payload.org_id,
};
} catch (error) {
return { valid: false, error: error.message };
}
}Next.js API Route Example
// app/api/protected/route.ts
import { createRemoteJWKSet, jwtVerify } from 'jose';
import { NextRequest, NextResponse } from 'next/server';
const JWKS = createRemoteJWKSet(
new URL('https://id.optare.one/.well-known/jwks.json')
);
export async function GET(request: NextRequest) {
const authHeader = request.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json({ error: 'Missing token' }, { status: 401 });
}
const token = authHeader.slice(7);
try {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://id.optare.one',
});
// Token is valid - proceed with request
return NextResponse.json({
message: 'Authenticated',
user: {
id: payload.sub,
email: payload.email,
}
});
} catch (error) {
return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
}
}Express Middleware
import { createRemoteJWKSet, jwtVerify } from 'jose';
import { Request, Response, NextFunction } from 'express';
const JWKS = createRemoteJWKSet(
new URL('https://id.optare.one/.well-known/jwks.json')
);
export async function requireAuth(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing token' });
}
try {
const { payload } = await jwtVerify(authHeader.slice(7), JWKS, {
issuer: 'https://id.optare.one',
});
req.user = {
id: payload.sub as string,
email: payload.email as string,
organizationId: payload.org_id as string,
};
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}
// Usage
app.get('/api/profile', requireAuth, (req, res) => {
res.json({ user: req.user });
});Token Claims
Optare access tokens include these standard claims:
| Claim | Description |
|---|---|
sub | User ID |
email | User's email address |
name | User's display name |
org_id | Organization ID |
iss | Issuer (https://id.optare.one) |
aud | Audience (your client ID) |
exp | Expiration timestamp |
iat | Issued at timestamp |
OIDC Discovery
For automatic configuration, use the OIDC discovery endpoint:
https://id.optare.one/.well-known/openid-configurationThis returns all endpoints and supported features:
{
"issuer": "https://id.optare.one",
"authorization_endpoint": "https://id.optare.one/oauth/authorize",
"token_endpoint": "https://id.optare.one/oauth/token",
"userinfo_endpoint": "https://id.optare.one/oauth/userinfo",
"jwks_uri": "https://id.optare.one/.well-known/jwks.json",
"scopes_supported": ["openid", "email", "profile", "offline_access"]
}Security Best Practices
- Cache JWKS:
createRemoteJWKSetcaches keys automatically with proper TTL - Validate issuer: Always verify
issclaim matcheshttps://id.optare.one - Check expiration: The library automatically rejects expired tokens
- Use HTTPS: Never transmit tokens over unencrypted connections
Next Steps
- Frontend SDK - React hooks and components
- OAuth Flow - Understanding the OAuth flow
- API Reference - Complete API documentation