Skip to content

Create an introspection endpoint #72

@kentcdodds

Description

@kentcdodds

Here's what I'm doing right now (I've got React Router set up in my worker alongside @cloudflare/workers-oauth-provider):

import { invariantResponse } from '@epic-web/invariant'
import { type Token } from '#types/helpers'
import { type Route } from './+types/introspection.ts'

export async function action({ request, context }: Route.LoaderArgs) {
	const token = (await request.formData()).get('token')?.toString()
	invariantResponse(token, 'invalid_request')

	const parts = token.split(':')
	if (parts.length !== 3) return { active: false }

	const [userId, grantId] = parts
	const tokenId = await generateTokenId(token)
	const tokenKey = `token:${userId}:${grantId}:${tokenId}`

	const tokenData = await context.cloudflare.env.OAUTH_KV.get(tokenKey, {
		type: 'json',
	})

	if (!tokenData) return { active: false }

	const info = tokenData as Token

	if (info.expiresAt * 1000 < Date.now()) {
		return { active: false }
	}

	return {
		active: true,
		client_id: info.grant.clientId,
		scope: info.grant.scope.join(' '),
		sub: info.userId,
		exp: info.expiresAt,
		iat: info.createdAt,
	}
}

// copied from @cloudflare/workers-oauth-provider
async function generateTokenId(token: string) {
	const encoder = new TextEncoder()
	const data = encoder.encode(token)
	const hashBuffer = await crypto.subtle.digest('SHA-256', data)
	const hashArray = Array.from(new Uint8Array(hashBuffer))
	const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
	return hashHex
}

It technically works, but I would rather not have to rely on undocumented tokenKey formats and copy/pasted code 😅

Even better if it could work like other auth providers so I could easily swap out this for WorkOS/etc if the code in my resource server were set up like this:

import { jwtVerify, createRemoteJWKSet } from 'jose';

const JWKS = createRemoteJWKSet(new URL('https://whatever.authkit.app/oauth2/jwks'));

// ...

async function verifyToken(req: Request) {
	const token = req.headers.get('authorization')?.match(/^Bearer (.+)$/)?.[1];
	// ...

	const { payload } = await jwtVerify(token, JWKS, {
		issuer: 'https://whatever.authkit.app',
	});

	// Use access token claims to populate request context.
	// i.e. `const userId = payload.sub;`

	// ...
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions