atproto user agency toolkit for individuals and groups
1import type { Context, Next } from "hono";
2import { verifyServiceJwt } from "../service-auth.js";
3import { verifyAccessToken, TokenExpiredError } from "../session.js";
4import type { Config } from "../config.js";
5
6export interface AuthInfo {
7 did: string;
8 scope: string;
9}
10
11export type AuthVariables = {
12 auth: AuthInfo;
13};
14
15export async function requireAuth(
16 c: Context<{ Bindings: Config; Variables: AuthVariables }>,
17 next: Next,
18): Promise<Response | void> {
19 const auth = c.req.header("Authorization");
20
21 if (!auth) {
22 return c.json(
23 {
24 error: "AuthMissing",
25 message: "Authorization header required",
26 },
27 401,
28 );
29 }
30
31 // Handle Bearer tokens (session JWTs, static token, service JWTs)
32 if (!auth.startsWith("Bearer ")) {
33 return c.json(
34 {
35 error: "AuthMissing",
36 message: "Invalid authorization scheme",
37 },
38 401,
39 );
40 }
41
42 const token = auth.slice(7);
43
44 // Try static token first
45 if (token === c.env.AUTH_TOKEN) {
46 c.set("auth", { did: c.env.DID ?? "", scope: "com.atproto.access" });
47 return next();
48 }
49
50 // Legacy JWT auth requires PDS_HOSTNAME and JWT_SECRET
51 if (!c.env.PDS_HOSTNAME || !c.env.JWT_SECRET) {
52 return c.json({ error: "AuthenticationRequired", message: "Not authenticated" }, 401);
53 }
54
55 const serviceDid = `did:web:${c.env.PDS_HOSTNAME}`;
56
57 // Try session JWT verification (HS256, signed with JWT_SECRET)
58 try {
59 const payload = await verifyAccessToken(
60 token,
61 c.env.JWT_SECRET,
62 serviceDid,
63 );
64
65 if (!payload.sub || payload.sub !== c.env.DID) {
66 return c.json(
67 {
68 error: "AuthenticationRequired",
69 message: "Invalid access token",
70 },
71 401,
72 );
73 }
74
75 c.set("auth", { did: payload.sub as string, scope: payload.scope as string });
76 return next();
77 } catch (err) {
78 if (err instanceof TokenExpiredError) {
79 return c.json(
80 {
81 error: "ExpiredToken",
82 message: err.message,
83 },
84 400,
85 );
86 }
87 // Session JWT verification failed, try service JWT
88 }
89
90 // Try service JWT verification (ES256K, signed with our signing key)
91 if (c.env.SIGNING_KEY && c.env.DID) {
92 try {
93 const payload = await verifyServiceJwt(
94 token,
95 c.env.SIGNING_KEY,
96 serviceDid,
97 c.env.DID,
98 );
99
100 if (payload.iss === c.env.DID) {
101 c.set("auth", { did: payload.iss, scope: payload.lxm || "atproto" });
102 return next();
103 }
104 } catch {
105 // Service JWT verification also failed
106 }
107 }
108
109 return c.json(
110 {
111 error: "AuthenticationRequired",
112 message: "Invalid authentication token",
113 },
114 401,
115 );
116}