Merge remote-tracking branch 'upstream' into v1.3.8-staging

This commit is contained in:
Alex Yang
2025-09-03 17:37:24 -07:00
2 changed files with 70 additions and 1 deletions

View File

@@ -66,6 +66,17 @@ import { auth } from "../../../lib/auth";
export const GET = oAuthDiscoveryMetadata(auth);
```
### OAuth Protected Resource Metadata
Add a route to expose protected resource metadata for MCP clients:
```ts title=".well-known/oauth-authorization-server/route.ts"
import { oAuthProtectedResourceMetadata } from "better-auth/plugins";
import { auth } from "@/lib/auth";
export const GET = oAuthProtectedResourceMetadata(auth);
```
### MCP Session Handling
You can use the helper function `withMcpAuth` to get the session and handle unauthenticated calls automatically.

View File

@@ -83,6 +83,27 @@ export const getMCPProviderMetadata = (
};
};
export const getMCPProtectedResourceMetadata = (
ctx: GenericEndpointContext,
options?: OIDCOptions,
) => {
const baseURL = ctx.context.baseURL;
return {
resource: baseURL,
authorization_servers: [baseURL],
jwks_uri: options?.metadata?.jwks_uri ?? `${baseURL}/mcp/jwks`,
scopes_supported: options?.metadata?.scopes_supported ?? [
"openid",
"profile",
"email",
"offline_access",
],
bearer_methods_supported: ["header"],
resource_signing_alg_values_supported: ["RS256", "none"],
};
};
export const mcp = (options: MCPOptions) => {
const opts = {
codeExpiresIn: 600,
@@ -168,6 +189,19 @@ export const mcp = (options: MCPOptions) => {
}
},
),
getMCPProtectedResource: createAuthEndpoint(
"/.well-known/oauth-protected-resource",
{
method: "GET",
metadata: {
client: false,
},
},
async (c) => {
const metadata = getMCPProtectedResourceMetadata(c, options);
return c.json(metadata);
},
),
mcpOAuthAuthroize: createAuthEndpoint(
"/mcp/authorize",
{
@@ -912,7 +946,7 @@ export const withMcpAuth = <
const session = await auth.api.getMcpSession({
headers: req.headers,
});
const wwwAuthenticateValue = `Bearer resource_metadata=${baseURL}/api/auth/.well-known/oauth-authorization-server`;
const wwwAuthenticateValue = `Bearer resource_metadata=${baseURL}/api/auth/.well-known/oauth-protected-resource`;
if (!session) {
return Response.json(
{
@@ -959,3 +993,27 @@ export const oAuthDiscoveryMetadata = <
});
};
};
export const oAuthProtectedResourceMetadata = <
Auth extends {
api: {
getMCPProtectedResource: (...args: any) => any;
};
},
>(
auth: Auth,
) => {
return async (request: Request) => {
const res = await auth.api.getMCPProtectedResource();
return new Response(JSON.stringify(res), {
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400",
},
});
};
};