@everframe/identity
Mint Everframe identity tokens from any runtime.
One createIdentityHandler call returns a standard
(Request) => Promise<Response> — which is simultaneously a Next App Router
handler, a Cloudflare Worker, a Vercel/Netlify Edge function, a Supabase Edge
Function and a Deno/Bun handler.
import { createIdentityHandler } from '@everframe/identity';
const handler = createIdentityHandler({
secret: process.env.EVERFRAME_IDENTITY_SECRET!,
projectId: process.env.EVERFRAME_PROJECT_ID!,
resolveUser: async (req) => {
const user = await getSessionUser(req);
return user ? { id: user.id, email: user.email, name: user.name } : null;
},
});
export { handler as GET, handler as OPTIONS };
Then point the SDK at it:
<EverframeProvider
config={{ apiKey }}
identity={{ endpoint: '/api/everframe-identity', key: user?.id }}
>
Auth is a callback, not a cookie
resolveUser receives the Request. Cookies are one implementation; a bearer
token is another:
resolveUser: (req) => verifyAccessToken(req.headers.get('authorization'))
On the client, headers is re-invoked on every mint, so a rotating access
token is never captured stale:
identity={{
endpoint: 'https://api.example.com/everframe-identity',
key: user?.id,
headers: async () => ({ Authorization: `Bearer ${await getAccessToken()}` }),
}}
Cross-origin
Pass allowedOrigins to answer preflights and echo matched origins. Absent, the
handler is same-origin only. '*' throws — a wildcard origin on an endpoint
that returns identity tokens would expose them to any site.
createIdentityHandler({ …, allowedOrigins: ['https://app.example.com'] })
Node
import { toNodeHandler } from '@everframe/identity/node';
const nodeHandler = (req, res) => void toNodeHandler(handler)(req, res);
app.get('/api/everframe-identity', nodeHandler);
app.options('/api/everframe-identity', nodeHandler);
Responses
| User resolves | 200 { token, expiresAt } (expiresAt is ms epoch) |
| Nobody signed in | 200 { token: null } |
user.id empty or >255 chars | 200 { token: null, reason: 'subject_too_long' } |
resolveUser throws | 500, empty body |
Every response carries Cache-Control: no-store. A signed-out user is a normal
state, not an error — all token: null cases read as "anonymous" to the SDK.
Licence
MIT