@@ -89,9 +89,6 @@ export type OAuthClientInformation = z.infer<typeof OAuthClientInformationSchema
89
89
export interface OAuthClientProvider {
90
90
/**
91
91
* 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.
95
92
*/
96
93
get redirectUrl ( ) : string | URL ;
97
94
@@ -104,19 +101,16 @@ export interface OAuthClientProvider {
104
101
* Loads information about this OAuth client, as registered already with the
105
102
* server, or returns `undefined` if the client is not registered with the
106
103
* server.
107
- *
108
- * This method must be implemented _unless_ redirecting to `localhost`.
109
104
*/
110
- clientInformation ? ( ) : OAuthClientInformation | undefined | Promise < OAuthClientInformation | undefined > ;
105
+ clientInformation ( ) : OAuthClientInformation | undefined | Promise < OAuthClientInformation | undefined > ;
111
106
112
107
/**
113
108
* If implemented, this permits the OAuth client to dynamically register with
114
109
* the server. Client information saved this way should later be read via
115
110
* `clientInformation()`.
116
111
*
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).
120
114
*/
121
115
saveClientInformation ?( clientInformation : OAuthClientInformation ) : void | Promise < void > ;
122
116
@@ -164,38 +158,30 @@ export async function auth(
164
158
const metadata = await discoverOAuthMetadata ( serverUrl ) ;
165
159
166
160
// 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" ) ;
171
165
}
172
166
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" ) ;
189
169
}
190
170
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 ) ;
192
177
}
193
178
194
179
// Exchange authorization code for tokens
195
180
if ( authorizationCode !== undefined ) {
196
181
const codeVerifier = await provider . codeVerifier ( ) ;
197
182
const tokens = await exchangeAuthorization ( serverUrl , {
198
183
metadata,
184
+ clientInformation,
199
185
authorizationCode,
200
186
codeVerifier,
201
187
} ) ;
@@ -212,6 +198,7 @@ export async function auth(
212
198
// Attempt to refresh the token
213
199
const newTokens = await refreshAuthorization ( serverUrl , {
214
200
metadata,
201
+ clientInformation,
215
202
refreshToken : tokens . refresh_token ,
216
203
} ) ;
217
204
@@ -223,7 +210,12 @@ export async function auth(
223
210
}
224
211
225
212
// 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
+
227
219
await provider . saveCodeVerifier ( codeVerifier ) ;
228
220
await provider . redirectToAuthorization ( authorizationUrl ) ;
229
221
return "REDIRECT" ;
@@ -260,8 +252,13 @@ export async function startAuthorization(
260
252
serverUrl : string | URL ,
261
253
{
262
254
metadata,
255
+ clientInformation,
263
256
redirectUrl,
264
- } : { metadata ?: OAuthMetadata ; redirectUrl : string | URL } ,
257
+ } : {
258
+ metadata ?: OAuthMetadata ;
259
+ clientInformation : OAuthClientInformation ;
260
+ redirectUrl : string | URL ;
261
+ } ,
265
262
) : Promise < { authorizationUrl : URL ; codeVerifier : string } > {
266
263
const responseType = "code" ;
267
264
const codeChallengeMethod = "S256" ;
@@ -294,6 +291,7 @@ export async function startAuthorization(
294
291
const codeChallenge = challenge . code_challenge ;
295
292
296
293
authorizationUrl . searchParams . set ( "response_type" , responseType ) ;
294
+ authorizationUrl . searchParams . set ( "client_id" , clientInformation . client_id ) ;
297
295
authorizationUrl . searchParams . set ( "code_challenge" , codeChallenge ) ;
298
296
authorizationUrl . searchParams . set (
299
297
"code_challenge_method" ,
@@ -311,10 +309,12 @@ export async function exchangeAuthorization(
311
309
serverUrl : string | URL ,
312
310
{
313
311
metadata,
312
+ clientInformation,
314
313
authorizationCode,
315
314
codeVerifier,
316
315
} : {
317
316
metadata ?: OAuthMetadata ;
317
+ clientInformation : OAuthClientInformation ;
318
318
authorizationCode : string ;
319
319
codeVerifier : string ;
320
320
} ,
@@ -338,16 +338,23 @@ export async function exchangeAuthorization(
338
338
}
339
339
340
340
// 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
+
341
352
const response = await fetch ( tokenUrl , {
342
353
method : "POST" ,
343
354
headers : {
344
355
"Content-Type" : "application/x-www-form-urlencoded" ,
345
356
} ,
346
- body : new URLSearchParams ( {
347
- grant_type : grantType ,
348
- code : authorizationCode ,
349
- code_verifier : codeVerifier ,
350
- } ) ,
357
+ body : params ,
351
358
} ) ;
352
359
353
360
if ( ! response . ok ) {
@@ -364,9 +371,11 @@ export async function refreshAuthorization(
364
371
serverUrl : string | URL ,
365
372
{
366
373
metadata,
374
+ clientInformation,
367
375
refreshToken,
368
376
} : {
369
377
metadata ?: OAuthMetadata ;
378
+ clientInformation : OAuthClientInformation ;
370
379
refreshToken : string ;
371
380
} ,
372
381
) : Promise < OAuthTokens > {
@@ -388,19 +397,27 @@ export async function refreshAuthorization(
388
397
tokenUrl = new URL ( "/token" , serverUrl ) ;
389
398
}
390
399
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
+
391
411
const response = await fetch ( tokenUrl , {
392
412
method : "POST" ,
393
413
headers : {
394
414
"Content-Type" : "application/x-www-form-urlencoded" ,
395
415
} ,
396
- body : new URLSearchParams ( {
397
- grant_type : grantType ,
398
- refresh_token : refreshToken ,
399
- } ) ,
416
+ body : params ,
400
417
} ) ;
401
418
402
419
if ( ! response . ok ) {
403
- throw new Error ( `Token exchange failed: HTTP ${ response . status } ` ) ;
420
+ throw new Error ( `Token refresh failed: HTTP ${ response . status } ` ) ;
404
421
}
405
422
406
423
return OAuthTokensSchema . parse ( await response . json ( ) ) ;
0 commit comments