Secure storage and distribution of cryptographic keys in ATProto applications
1import { error, IRequest } from 'itty-router'
2import { ServiceJwtVerifier, VerifiedJwt } from '@atcute/xrpc-server/auth'
3import {
4 CompositeDidDocumentResolver,
5 PlcDidDocumentResolver,
6 WebDidDocumentResolver,
7} from '@atcute/identity-resolver'
8import { Nsid, Did } from '@atcute/lexicons'
9
10const didDocResolver = new CompositeDidDocumentResolver({
11 methods: {
12 plc: new PlcDidDocumentResolver(),
13 web: new WebDidDocumentResolver(),
14 },
15})
16const createJwtVerifier = (did: Did<'web'>) =>
17 new ServiceJwtVerifier({
18 resolver: didDocResolver,
19 serviceDid: did,
20 })
21
22export default async function authMiddleware(
23 serviceDid: Did<'web'>,
24 ctx: IRequest,
25) {
26 const url = new URL(ctx.url)
27 if (!url.pathname.startsWith('/xrpc/')) {
28 return error(404)
29 }
30 const lxm = url.pathname.split('/xrpc/')[1] as Nsid
31
32 const authorization = ctx.headers.get('authorization')
33 if (!authorization) {
34 return error(403, 'Authorization token required.')
35 }
36 if (!authorization.startsWith('Bearer ')) {
37 return error(403, 'Bearer token required')
38 }
39
40 const jwt = authorization.split('Bearer ')[1]
41 const jwtVerifier = createJwtVerifier(serviceDid)
42 const jwtPayload = await jwtVerifier.verify(jwt, { lxm })
43 if (!jwtPayload.ok) {
44 console.error('Error validating JWT:', jwtPayload.error)
45 return error(403, 'Could not verify authorization JWT.')
46 }
47
48 ctx.audience = jwtPayload.value.audience
49 ctx.lxm = jwtPayload.value.lxm
50 ctx.did = jwtPayload.value.issuer
51
52 return undefined
53}