-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathmiddleware.ts
More file actions
115 lines (98 loc) · 3.09 KB
/
Copy pathmiddleware.ts
File metadata and controls
115 lines (98 loc) · 3.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
* Next.js Middleware for StrongDM ID Authentication
*
* This middleware protects API routes based on path patterns.
* Configure which paths require authentication and which scopes are needed.
*/
import { NextRequest, NextResponse } from "next/server";
import { StrongDMAuth, StrongDMAuthError, TokenClaims } from "./lib/strongdm-auth";
// Configuration for protected routes
interface RouteConfig {
/** Required scopes (at least one must match) */
scopes?: string[];
/** Require ALL scopes (default: false, meaning any scope matches) */
requireAllScopes?: boolean;
/** Allow unauthenticated access (useful for optional auth) */
optional?: boolean;
}
// Define your protected routes here
const protectedRoutes: Record<string, RouteConfig> = {
"/api/protected": {},
"/api/agent-info": {},
"/api/admin": { scopes: ["admin"] },
};
// Public routes that skip authentication
const publicRoutes = ["/api/health", "/api/public"];
// Create auth instance
const auth = new StrongDMAuth({
issuer: process.env.STRONGDM_ISSUER || "https://id.strongdm.ai",
audience: process.env.STRONGDM_AUDIENCE,
});
export async function middleware(request: NextRequest) {
const path = request.nextUrl.pathname;
// Skip public routes
if (publicRoutes.some((route) => path.startsWith(route))) {
return NextResponse.next();
}
// Check if this is a protected API route
const routeConfig = Object.entries(protectedRoutes).find(([route]) =>
path.startsWith(route)
)?.[1];
// If not a protected route, continue
if (!routeConfig) {
return NextResponse.next();
}
try {
// Get auth headers
const authHeader = request.headers.get("authorization");
const dpopHeader = request.headers.get("dpop");
// If auth is optional and no header provided, continue
if (routeConfig.optional && !authHeader) {
return NextResponse.next();
}
// Verify the token
const claims = await auth.verifyRequest(
authHeader,
dpopHeader,
request.method,
request.url
);
// Check scopes if required
if (routeConfig.scopes && routeConfig.scopes.length > 0) {
auth.checkScopes(
claims,
routeConfig.scopes,
routeConfig.requireAllScopes
);
}
// Add claims to request headers for downstream handlers
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-strongdm-claims", JSON.stringify(claims));
requestHeaders.set("x-strongdm-subject", claims.sub);
requestHeaders.set("x-strongdm-scopes", claims.scope || "");
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} catch (error) {
if (error instanceof StrongDMAuthError) {
return NextResponse.json(
{ error: error.message },
{ status: error.statusCode }
);
}
console.error("Middleware auth error:", error);
return NextResponse.json(
{ error: "Authentication failed" },
{ status: 401 }
);
}
}
// Configure which paths the middleware should run on
export const config = {
matcher: [
// Match all API routes
"/api/:path*",
],
};