@@ -4,6 +4,14 @@ import express from "express";
44import { OAuthServerProvider } from "../provider.js" ;
55import { rateLimit , Options as RateLimitOptions } from "express-rate-limit" ;
66import { allowedMethods } from "../middleware/allowedMethods.js" ;
7+ import {
8+ InvalidRequestError ,
9+ InvalidClientError ,
10+ InvalidScopeError ,
11+ ServerError ,
12+ TooManyRequestsError ,
13+ OAuthError
14+ } from "../errors.js" ;
715
816export type AuthorizationHandlerOptions = {
917 provider : OAuthServerProvider ;
@@ -42,81 +50,116 @@ export function authorizationHandler({ provider, rateLimit: rateLimitConfig }: A
4250 max : 100 , // 100 requests per windowMs
4351 standardHeaders : true ,
4452 legacyHeaders : false ,
45- message : {
46- error : 'too_many_requests' ,
47- error_description : 'You have exceeded the rate limit for authorization requests'
48- } ,
53+ message : new TooManyRequestsError ( 'You have exceeded the rate limit for authorization requests' ) . toResponseObject ( ) ,
4954 ...rateLimitConfig
5055 } ) ) ;
5156 }
5257
53- // Define the handler
5458 router . all ( "/" , async ( req , res ) => {
5559 res . setHeader ( 'Cache-Control' , 'no-store' ) ;
5660
57- let client_id , redirect_uri ;
61+ // In the authorization flow, errors are split into two categories:
62+ // 1. Pre-redirect errors (direct response with 400)
63+ // 2. Post-redirect errors (redirect with error parameters)
64+
65+ // Phase 1: Validate client_id and redirect_uri. Any errors here must be direct responses.
66+ let client_id , redirect_uri , client ;
5867 try {
59- const data = req . method === 'POST' ? req . body : req . query ;
60- ( { client_id, redirect_uri } = ClientAuthorizationParamsSchema . parse ( data ) ) ;
61- } catch ( error ) {
62- res . status ( 400 ) . end ( `Bad Request: ${ error } ` ) ;
63- return ;
64- }
68+ const result = ClientAuthorizationParamsSchema . safeParse ( req . method === 'POST' ? req . body : req . query ) ;
69+ if ( ! result . success ) {
70+ throw new InvalidRequestError ( result . error . message ) ;
71+ }
6572
66- const client = await provider . clientsStore . getClient ( client_id ) ;
67- if ( ! client ) {
68- res . status ( 400 ) . end ( "Bad Request: invalid client_id" ) ;
69- return ;
70- }
73+ client_id = result . data . client_id ;
74+ redirect_uri = result . data . redirect_uri ;
7175
72- if ( redirect_uri !== undefined ) {
73- if ( ! client . redirect_uris . includes ( redirect_uri ) ) {
74- res . status ( 400 ) . end ( "Bad Request: unregistered redirect_uri" ) ;
75- return ;
76+ client = await provider . clientsStore . getClient ( client_id ) ;
77+ if ( ! client ) {
78+ throw new InvalidClientError ( "Invalid client_id" ) ;
7679 }
77- } else if ( client . redirect_uris . length === 1 ) {
78- redirect_uri = client . redirect_uris [ 0 ] ;
79- } else {
80- res . status ( 400 ) . end ( "Bad Request: missing redirect_uri" ) ;
81- return ;
82- }
8380
84- let params ;
85- try {
86- const authData = req . method === 'POST' ? req . body : req . query ;
87- params = RequestAuthorizationParamsSchema . parse ( authData ) ;
81+ if ( redirect_uri !== undefined ) {
82+ if ( ! client . redirect_uris . includes ( redirect_uri ) ) {
83+ throw new InvalidRequestError ( "Unregistered redirect_uri" ) ;
84+ }
85+ } else if ( client . redirect_uris . length === 1 ) {
86+ redirect_uri = client . redirect_uris [ 0 ] ;
87+ } else {
88+ throw new InvalidRequestError ( "redirect_uri must be specified when client has multiple registered URIs" ) ;
89+ }
8890 } catch ( error ) {
89- const errorUrl = new URL ( redirect_uri ) ;
90- errorUrl . searchParams . set ( "error" , "invalid_request" ) ;
91- errorUrl . searchParams . set ( "error_description" , String ( error ) ) ;
92- res . redirect ( 302 , errorUrl . href ) ;
91+ // Pre-redirect errors - return direct response
92+ if ( error instanceof OAuthError ) {
93+ const status = error instanceof ServerError ? 500 : 400 ;
94+ res . status ( status ) . end ( error . message ) ;
95+ } else {
96+ console . error ( "Unexpected error looking up client:" , error ) ;
97+ res . status ( 500 ) . end ( "Internal Server Error" ) ;
98+ }
99+
93100 return ;
94101 }
95102
96- let requestedScopes : string [ ] = [ ] ;
97- if ( params . scope !== undefined && client . scope !== undefined ) {
98- requestedScopes = params . scope . split ( " " ) ;
99- const allowedScopes = new Set ( client . scope . split ( " " ) ) ;
100-
101- // If any requested scope is not in the client's registered scopes, error out
102- for ( const scope of requestedScopes ) {
103- if ( ! allowedScopes . has ( scope ) ) {
104- const errorUrl = new URL ( redirect_uri ) ;
105- errorUrl . searchParams . set ( "error" , "invalid_scope" ) ;
106- errorUrl . searchParams . set ( "error_description" , `Client was not registered with scope ${ scope } ` ) ;
107- res . redirect ( 302 , errorUrl . href ) ;
108- return ;
103+ // Phase 2: Validate other parameters. Any errors here should go into redirect responses.
104+ let state ;
105+ try {
106+ // Parse and validate authorization parameters
107+ const parseResult = RequestAuthorizationParamsSchema . safeParse ( req . method === 'POST' ? req . body : req . query ) ;
108+ if ( ! parseResult . success ) {
109+ throw new InvalidRequestError ( parseResult . error . message ) ;
110+ }
111+
112+ const { scope, code_challenge } = parseResult . data ;
113+ state = parseResult . data . state ;
114+
115+ // Validate scopes
116+ let requestedScopes : string [ ] = [ ] ;
117+ if ( scope !== undefined && client . scope !== undefined ) {
118+ requestedScopes = scope . split ( " " ) ;
119+ const allowedScopes = new Set ( client . scope . split ( " " ) ) ;
120+
121+ // Check each requested scope against allowed scopes
122+ for ( const scope of requestedScopes ) {
123+ if ( ! allowedScopes . has ( scope ) ) {
124+ throw new InvalidScopeError ( `Client was not registered with scope ${ scope } ` ) ;
125+ }
109126 }
110127 }
111- }
112128
113- await provider . authorize ( client , {
114- state : params . state ,
115- scopes : requestedScopes ,
116- redirectUri : redirect_uri ,
117- codeChallenge : params . code_challenge ,
118- } , res ) ;
129+ // All validation passed, proceed with authorization
130+ await provider . authorize ( client , {
131+ state,
132+ scopes : requestedScopes ,
133+ redirectUri : redirect_uri ,
134+ codeChallenge : code_challenge ,
135+ } , res ) ;
136+ } catch ( error ) {
137+ // Post-redirect errors - redirect with error parameters
138+ if ( error instanceof OAuthError ) {
139+ res . redirect ( 302 , createErrorRedirect ( redirect_uri , error , state ) ) ;
140+ } else {
141+ console . error ( "Unexpected error during authorization:" , error ) ;
142+ const serverError = new ServerError ( "Internal Server Error" ) ;
143+ res . redirect ( 302 , createErrorRedirect ( redirect_uri , serverError , state ) ) ;
144+ }
145+ }
119146 } ) ;
120147
121148 return router ;
149+ }
150+
151+ /**
152+ * Helper function to create redirect URL with error parameters
153+ */
154+ function createErrorRedirect ( redirectUri : string , error : OAuthError , state ?: string ) : string {
155+ const errorUrl = new URL ( redirectUri ) ;
156+ errorUrl . searchParams . set ( "error" , error . errorCode ) ;
157+ errorUrl . searchParams . set ( "error_description" , error . message ) ;
158+ if ( error . errorUri ) {
159+ errorUrl . searchParams . set ( "error_uri" , error . errorUri ) ;
160+ }
161+ if ( state ) {
162+ errorUrl . searchParams . set ( "state" , state ) ;
163+ }
164+ return errorUrl . href ;
122165}
0 commit comments