From baea194677142221ab2d5fecc32636aaccf1cd0a Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:48:50 +0100 Subject: [PATCH] fix(auth): add OAuth2 routes for OIDC discovery compatibility Better Auth's OIDC discovery document advertises endpoints at /api/auth/oauth2/* paths. Add routes for these native paths to ensure Matrix Synapse and other OIDC clients can complete the authorization flow. Routes added: - GET /api/auth/oauth2/authorize - POST /api/auth/oauth2/token - GET /api/auth/oauth2/userinfo - GET /api/auth/jwks Co-Authored-By: Claude Opus 4.5 --- .../src/auth/oidc.controller.ts | 92 +++++++++++++------ services/mana-core-auth/src/main.ts | 17 +++- 2 files changed, 76 insertions(+), 33 deletions(-) diff --git a/services/mana-core-auth/src/auth/oidc.controller.ts b/services/mana-core-auth/src/auth/oidc.controller.ts index 1f2a94267..a45a65e43 100644 --- a/services/mana-core-auth/src/auth/oidc.controller.ts +++ b/services/mana-core-auth/src/auth/oidc.controller.ts @@ -4,12 +4,17 @@ * Exposes Better Auth's OIDC Provider endpoints for external services * like Matrix/Synapse to use SSO authentication. * + * Better Auth exposes OIDC endpoints at /api/auth/oauth2/* paths. + * This controller provides routes at both: + * - /api/auth/oauth2/* (native Better Auth paths from discovery document) + * - /api/oidc/* (alternative paths for convenience) + * * Endpoints: * - GET /.well-known/openid-configuration - OIDC Discovery - * - GET /api/oidc/authorize - Authorization endpoint - * - POST /api/oidc/token - Token endpoint - * - GET /api/oidc/userinfo - UserInfo endpoint - * - GET /api/oidc/jwks - JWKS endpoint + * - GET /api/auth/oauth2/authorize - Authorization endpoint + * - POST /api/auth/oauth2/token - Token endpoint + * - GET /api/auth/oauth2/userinfo - UserInfo endpoint + * - GET /api/auth/jwks - JWKS endpoint */ import { Controller, Get, Post, All, Req, Res, HttpStatus } from '@nestjs/common'; @@ -30,10 +35,58 @@ export class OidcController { return this.handleOidcRequest(req, res); } + // ============================================ + // Better Auth Native OAuth2 Endpoints + // These match the paths in the discovery document + // ============================================ + /** - * Authorization Endpoint - * - * Handles OAuth2 authorization requests. + * Authorization Endpoint (Better Auth native path) + */ + @Get('api/auth/oauth2/authorize') + async authorizeOauth2(@Req() req: Request, @Res() res: Response) { + return this.handleOidcRequest(req, res); + } + + /** + * Token Endpoint (Better Auth native path) + */ + @Post('api/auth/oauth2/token') + async tokenOauth2(@Req() req: Request, @Res() res: Response) { + return this.handleOidcRequest(req, res); + } + + /** + * UserInfo Endpoint (Better Auth native path) + */ + @Get('api/auth/oauth2/userinfo') + async userinfoOauth2(@Req() req: Request, @Res() res: Response) { + return this.handleOidcRequest(req, res); + } + + /** + * JWKS Endpoint (Better Auth native path) + */ + @Get('api/auth/jwks') + async jwksAuth(@Req() req: Request, @Res() res: Response) { + return this.handleOidcRequest(req, res); + } + + /** + * Catch-all for other Better Auth OAuth2 endpoints + */ + @All('api/auth/oauth2/*') + async catchAllOauth2(@Req() req: Request, @Res() res: Response) { + return this.handleOidcRequest(req, res); + } + + // ============================================ + // Alternative /api/oidc/* paths + // For backwards compatibility and convenience + // ============================================ + + /** + * Authorization Endpoint (alternative path) */ @Get('api/oidc/authorize') async authorize(@Req() req: Request, @Res() res: Response) { @@ -41,9 +94,7 @@ export class OidcController { } /** - * Token Endpoint - * - * Exchanges authorization codes for tokens. + * Token Endpoint (alternative path) */ @Post('api/oidc/token') async token(@Req() req: Request, @Res() res: Response) { @@ -51,9 +102,7 @@ export class OidcController { } /** - * UserInfo Endpoint - * - * Returns user information for the authenticated user. + * UserInfo Endpoint (alternative path) */ @Get('api/oidc/userinfo') async userinfo(@Req() req: Request, @Res() res: Response) { @@ -61,9 +110,7 @@ export class OidcController { } /** - * JWKS Endpoint (via /api/oidc/jwks) - * - * Returns JSON Web Key Set for token verification. + * JWKS Endpoint (alternative path) */ @Get('api/oidc/jwks') async jwks(@Req() req: Request, @Res() res: Response) { @@ -71,18 +118,7 @@ export class OidcController { } /** - * JWKS Endpoint (via /api/auth/jwks) - * - * Better Auth's discovery document points to this path, - * so we need to expose it directly as well. - */ - @Get('api/auth/jwks') - async jwksAlt(@Req() req: Request, @Res() res: Response) { - return this.handleOidcRequest(req, res); - } - - /** - * Catch-all for other OIDC endpoints + * Catch-all for other OIDC endpoints (alternative path) */ @All('api/oidc/*') async catchAll(@Req() req: Request, @Res() res: Response) { diff --git a/services/mana-core-auth/src/main.ts b/services/mana-core-auth/src/main.ts index aa4cc91f8..933909241 100644 --- a/services/mana-core-auth/src/main.ts +++ b/services/mana-core-auth/src/main.ts @@ -81,18 +81,25 @@ async function bootstrap() { // Global prefix (exclude metrics, health, Better Auth native routes, and OIDC routes) // Better Auth generates verification URLs with /api/auth/* prefix - // OIDC Provider requires routes without prefix: /.well-known/*, /api/oidc/* + // OIDC Provider requires routes without prefix: /.well-known/*, /api/auth/oauth2/*, /api/oidc/* app.setGlobalPrefix('api/v1', { exclude: [ { path: 'metrics', method: RequestMethod.ALL }, { path: 'health', method: RequestMethod.ALL }, - // Better Auth routes - use path-to-regexp wildcards - { path: 'api/auth/(.*)', method: RequestMethod.ALL }, + // Better Auth routes (verification emails, password reset) + { path: 'api/auth/verify-email', method: RequestMethod.ALL }, + { path: 'api/auth/reset-password/(.*)', method: RequestMethod.ALL }, + // Better Auth OIDC/OAuth2 routes (native paths from discovery document) { path: 'api/auth/jwks', method: RequestMethod.ALL }, - { path: 'api/auth/:path*', method: RequestMethod.ALL }, - // OIDC routes + { path: 'api/auth/oauth2/(.*)', method: RequestMethod.ALL }, + { path: 'api/auth/oauth2/authorize', method: RequestMethod.ALL }, + { path: 'api/auth/oauth2/token', method: RequestMethod.ALL }, + { path: 'api/auth/oauth2/userinfo', method: RequestMethod.ALL }, + { path: 'api/auth/oauth2/:path*', method: RequestMethod.ALL }, + // OIDC discovery { path: '.well-known/(.*)', method: RequestMethod.ALL }, { path: '.well-known/openid-configuration', method: RequestMethod.ALL }, + // Alternative OIDC routes { path: 'api/oidc/(.*)', method: RequestMethod.ALL }, { path: 'api/oidc/:path*', method: RequestMethod.ALL }, ],