@@ -89,9 +89,6 @@ export type OAuthClientInformation = z.infer<typeof OAuthClientInformationSchema
8989export interface OAuthClientProvider {
9090 /**
9191 * The URL to redirect the user agent to after authorization.
92- *
93- * If the client is not redirecting to localhost, `clientInformation` must be
94- * implemented.
9592 */
9693 get redirectUrl ( ) : string | URL ;
9794
@@ -104,19 +101,16 @@ export interface OAuthClientProvider {
104101 * Loads information about this OAuth client, as registered already with the
105102 * server, or returns `undefined` if the client is not registered with the
106103 * server.
107- *
108- * This method must be implemented _unless_ redirecting to `localhost`.
109104 */
110- clientInformation ? ( ) : OAuthClientInformation | undefined | Promise < OAuthClientInformation | undefined > ;
105+ clientInformation ( ) : OAuthClientInformation | undefined | Promise < OAuthClientInformation | undefined > ;
111106
112107 /**
113108 * If implemented, this permits the OAuth client to dynamically register with
114109 * the server. Client information saved this way should later be read via
115110 * `clientInformation()`.
116111 *
117- * This method is not required to be implemented if redirecting to
118- * `localhost`, or if client information is statically known (e.g.,
119- * pre-registered).
112+ * This method is not required to be implemented if client information is
113+ * statically known (e.g., pre-registered).
120114 */
121115 saveClientInformation ?( clientInformation : OAuthClientInformation ) : void | Promise < void > ;
122116
@@ -164,38 +158,30 @@ export async function auth(
164158 const metadata = await discoverOAuthMetadata ( serverUrl ) ;
165159
166160 // Handle client registration if needed
167- const hostname = new URL ( provider . redirectUrl ) . hostname ;
168- if ( hostname !== "localhost" && hostname !== "127.0.0.1" ) {
169- if ( ! provider . clientInformation ) {
170- throw new Error ( "OAuth client information is required when not redirecting to localhost" )
161+ let clientInformation = await Promise . resolve ( provider . clientInformation ( ) ) ;
162+ if ( ! clientInformation ) {
163+ if ( authorizationCode !== undefined ) {
164+ throw new Error ( "Existing OAuth client information is required when exchanging an authorization code" ) ;
171165 }
172166
173- let clientInformation = await Promise . resolve ( provider . clientInformation ( ) ) ;
174- if ( ! clientInformation ) {
175- if ( authorizationCode !== undefined ) {
176- throw new Error ( "Existing OAuth client information is required when exchanging an authorization code" ) ;
177- }
178-
179- if ( ! provider . saveClientInformation ) {
180- throw new Error ( "OAuth client information must be saveable when not provided and not redirecting to localhost" ) ;
181- }
182-
183- clientInformation = await registerClient ( serverUrl , {
184- metadata,
185- clientMetadata : provider . clientMetadata ,
186- } ) ;
187-
188- await provider . saveClientInformation ( clientInformation ) ;
167+ if ( ! provider . saveClientInformation ) {
168+ throw new Error ( "OAuth client information must be saveable for dynamic registration" ) ;
189169 }
190170
191- // TODO: Send clientInformation into auth flow
171+ clientInformation = await registerClient ( serverUrl , {
172+ metadata,
173+ clientMetadata : provider . clientMetadata ,
174+ } ) ;
175+
176+ await provider . saveClientInformation ( clientInformation ) ;
192177 }
193178
194179 // Exchange authorization code for tokens
195180 if ( authorizationCode !== undefined ) {
196181 const codeVerifier = await provider . codeVerifier ( ) ;
197182 const tokens = await exchangeAuthorization ( serverUrl , {
198183 metadata,
184+ clientInformation,
199185 authorizationCode,
200186 codeVerifier,
201187 } ) ;
@@ -212,6 +198,7 @@ export async function auth(
212198 // Attempt to refresh the token
213199 const newTokens = await refreshAuthorization ( serverUrl , {
214200 metadata,
201+ clientInformation,
215202 refreshToken : tokens . refresh_token ,
216203 } ) ;
217204
@@ -223,7 +210,12 @@ export async function auth(
223210 }
224211
225212 // Start new authorization flow
226- const { authorizationUrl, codeVerifier } = await startAuthorization ( serverUrl , { metadata, redirectUrl : provider . redirectUrl } ) ;
213+ const { authorizationUrl, codeVerifier } = await startAuthorization ( serverUrl , {
214+ metadata,
215+ clientInformation,
216+ redirectUrl : provider . redirectUrl
217+ } ) ;
218+
227219 await provider . saveCodeVerifier ( codeVerifier ) ;
228220 await provider . redirectToAuthorization ( authorizationUrl ) ;
229221 return "REDIRECT" ;
@@ -260,8 +252,13 @@ export async function startAuthorization(
260252 serverUrl : string | URL ,
261253 {
262254 metadata,
255+ clientInformation,
263256 redirectUrl,
264- } : { metadata ?: OAuthMetadata ; redirectUrl : string | URL } ,
257+ } : {
258+ metadata ?: OAuthMetadata ;
259+ clientInformation : OAuthClientInformation ;
260+ redirectUrl : string | URL ;
261+ } ,
265262) : Promise < { authorizationUrl : URL ; codeVerifier : string } > {
266263 const responseType = "code" ;
267264 const codeChallengeMethod = "S256" ;
@@ -294,6 +291,7 @@ export async function startAuthorization(
294291 const codeChallenge = challenge . code_challenge ;
295292
296293 authorizationUrl . searchParams . set ( "response_type" , responseType ) ;
294+ authorizationUrl . searchParams . set ( "client_id" , clientInformation . client_id ) ;
297295 authorizationUrl . searchParams . set ( "code_challenge" , codeChallenge ) ;
298296 authorizationUrl . searchParams . set (
299297 "code_challenge_method" ,
@@ -311,10 +309,12 @@ export async function exchangeAuthorization(
311309 serverUrl : string | URL ,
312310 {
313311 metadata,
312+ clientInformation,
314313 authorizationCode,
315314 codeVerifier,
316315 } : {
317316 metadata ?: OAuthMetadata ;
317+ clientInformation : OAuthClientInformation ;
318318 authorizationCode : string ;
319319 codeVerifier : string ;
320320 } ,
@@ -338,16 +338,23 @@ export async function exchangeAuthorization(
338338 }
339339
340340 // Exchange code for tokens
341+ const params = new URLSearchParams ( {
342+ grant_type : grantType ,
343+ client_id : clientInformation . client_id ,
344+ code : authorizationCode ,
345+ code_verifier : codeVerifier ,
346+ } ) ;
347+
348+ if ( clientInformation . client_secret ) {
349+ params . set ( "client_secret" , clientInformation . client_secret ) ;
350+ }
351+
341352 const response = await fetch ( tokenUrl , {
342353 method : "POST" ,
343354 headers : {
344355 "Content-Type" : "application/x-www-form-urlencoded" ,
345356 } ,
346- body : new URLSearchParams ( {
347- grant_type : grantType ,
348- code : authorizationCode ,
349- code_verifier : codeVerifier ,
350- } ) ,
357+ body : params ,
351358 } ) ;
352359
353360 if ( ! response . ok ) {
@@ -364,9 +371,11 @@ export async function refreshAuthorization(
364371 serverUrl : string | URL ,
365372 {
366373 metadata,
374+ clientInformation,
367375 refreshToken,
368376 } : {
369377 metadata ?: OAuthMetadata ;
378+ clientInformation : OAuthClientInformation ;
370379 refreshToken : string ;
371380 } ,
372381) : Promise < OAuthTokens > {
@@ -388,19 +397,27 @@ export async function refreshAuthorization(
388397 tokenUrl = new URL ( "/token" , serverUrl ) ;
389398 }
390399
400+ // Exchange refresh token
401+ const params = new URLSearchParams ( {
402+ grant_type : grantType ,
403+ client_id : clientInformation . client_id ,
404+ refresh_token : refreshToken ,
405+ } ) ;
406+
407+ if ( clientInformation . client_secret ) {
408+ params . set ( "client_secret" , clientInformation . client_secret ) ;
409+ }
410+
391411 const response = await fetch ( tokenUrl , {
392412 method : "POST" ,
393413 headers : {
394414 "Content-Type" : "application/x-www-form-urlencoded" ,
395415 } ,
396- body : new URLSearchParams ( {
397- grant_type : grantType ,
398- refresh_token : refreshToken ,
399- } ) ,
416+ body : params ,
400417 } ) ;
401418
402419 if ( ! response . ok ) {
403- throw new Error ( `Token exchange failed: HTTP ${ response . status } ` ) ;
420+ throw new Error ( `Token refresh failed: HTTP ${ response . status } ` ) ;
404421 }
405422
406423 return OAuthTokensSchema . parse ( await response . json ( ) ) ;
0 commit comments