@@ -14,10 +14,9 @@ use axum_extra::response::Html;
1414use hyper:: StatusCode ;
1515use mas_axum_utils:: { cookies:: CookieJar , sentry:: SentryEventID } ;
1616use mas_data_model:: { UpstreamOAuthProvider , UpstreamOAuthProviderResponseMode } ;
17+ use mas_jose:: claims:: TokenHash ;
1718use mas_keystore:: { Encrypter , Keystore } ;
18- use mas_oidc_client:: requests:: {
19- authorization_code:: AuthorizationValidationData , jose:: JwtVerificationData ,
20- } ;
19+ use mas_oidc_client:: requests:: jose:: JwtVerificationData ;
2120use mas_router:: UrlBuilder ;
2221use mas_storage:: {
2322 upstream_oauth2:: {
@@ -27,7 +26,7 @@ use mas_storage::{
2726 BoxClock , BoxRepository , BoxRng , Clock ,
2827} ;
2928use mas_templates:: { FormPostContext , Templates } ;
30- use oauth2_types:: errors:: ClientErrorCode ;
29+ use oauth2_types:: { errors:: ClientErrorCode , requests :: AccessTokenRequest } ;
3130use serde:: { Deserialize , Serialize } ;
3231use serde_json:: json;
3332use thiserror:: Error ;
@@ -88,9 +87,6 @@ pub(crate) enum RouteError {
8887 #[ error( "State parameter mismatch" ) ]
8988 StateMismatch ,
9089
91- #[ error( "Missing ID token" ) ]
92- MissingIDToken ,
93-
9490 #[ error( "Could not extract subject from ID token" ) ]
9591 ExtractSubject ( #[ source] minijinja:: Error ) ,
9692
@@ -125,7 +121,8 @@ impl_from_error_for_route!(mas_templates::TemplateError);
125121impl_from_error_for_route ! ( mas_storage:: RepositoryError ) ;
126122impl_from_error_for_route ! ( mas_oidc_client:: error:: DiscoveryError ) ;
127123impl_from_error_for_route ! ( mas_oidc_client:: error:: JwksError ) ;
128- impl_from_error_for_route ! ( mas_oidc_client:: error:: TokenAuthorizationCodeError ) ;
124+ impl_from_error_for_route ! ( mas_oidc_client:: error:: TokenRequestError ) ;
125+ impl_from_error_for_route ! ( mas_oidc_client:: error:: IdTokenError ) ;
129126impl_from_error_for_route ! ( mas_oidc_client:: error:: UserInfoError ) ;
130127impl_from_error_for_route ! ( super :: ProviderCredentialsError ) ;
131128impl_from_error_for_route ! ( super :: cookie:: UpstreamSessionNotFound ) ;
@@ -253,11 +250,6 @@ pub(crate) async fn handler(
253250
254251 let mut lazy_metadata = LazyProviderInfos :: new ( & metadata_cache, & provider, & client) ;
255252
256- // Fetch the JWKS
257- let jwks =
258- mas_oidc_client:: requests:: jose:: fetch_jwks ( & client, lazy_metadata. jwks_uri ( ) . await ?)
259- . await ?;
260-
261253 // Figure out the client credentials
262254 let client_credentials = client_credentials_for_provider (
263255 & provider,
@@ -268,41 +260,72 @@ pub(crate) async fn handler(
268260
269261 let redirect_uri = url_builder. upstream_oauth_callback ( provider. id ) ;
270262
271- // TODO: all that should be borrowed
272- let validation_data = AuthorizationValidationData {
273- state : session. state_str . clone ( ) ,
274- nonce : session. nonce . clone ( ) ,
275- code_challenge_verifier : session. code_challenge_verifier . clone ( ) ,
276- redirect_uri,
277- } ;
278-
279- let verification_data = JwtVerificationData {
280- issuer : & provider. issuer ,
281- jwks : & jwks,
282- // TODO: make that configurable
283- signing_algorithm : & mas_iana:: jose:: JsonWebSignatureAlg :: Rs256 ,
284- client_id : & provider. client_id ,
285- } ;
263+ let token_response = mas_oidc_client:: requests:: token:: request_access_token (
264+ & client,
265+ client_credentials,
266+ lazy_metadata. token_endpoint ( ) . await ?,
267+ AccessTokenRequest :: AuthorizationCode ( oauth2_types:: requests:: AuthorizationCodeGrant {
268+ code : code. clone ( ) ,
269+ redirect_uri : Some ( redirect_uri) ,
270+ code_verifier : session. code_challenge_verifier . clone ( ) ,
271+ } ) ,
272+ clock. now ( ) ,
273+ & mut rng,
274+ )
275+ . await ?;
276+
277+ let mut context = AttributeMappingContext :: new ( ) ;
278+ if let Some ( id_token) = token_response. id_token . as_ref ( ) {
279+ // Fetch the JWKS
280+ let jwks =
281+ mas_oidc_client:: requests:: jose:: fetch_jwks ( & client, lazy_metadata. jwks_uri ( ) . await ?)
282+ . await ?;
283+
284+ let verification_data = JwtVerificationData {
285+ issuer : & provider. issuer ,
286+ jwks : & jwks,
287+ // TODO: make that configurable
288+ signing_algorithm : & mas_iana:: jose:: JsonWebSignatureAlg :: Rs256 ,
289+ client_id : & provider. client_id ,
290+ } ;
286291
287- let ( response, id_token_map) =
288- mas_oidc_client:: requests:: authorization_code:: access_token_with_authorization_code (
289- & client,
290- client_credentials,
291- lazy_metadata. token_endpoint ( ) . await ?,
292- code,
293- validation_data,
294- Some ( verification_data) ,
292+ // Decode and verify the ID token
293+ let id_token = mas_oidc_client:: requests:: jose:: verify_id_token (
294+ id_token,
295+ verification_data,
296+ None ,
295297 clock. now ( ) ,
296- & mut rng,
297- )
298- . await ?;
298+ ) ?;
299+
300+ let ( _headers, mut claims) = id_token. into_parts ( ) ;
301+
302+ // Access token hash must match.
303+ mas_jose:: claims:: AT_HASH
304+ . extract_optional_with_options (
305+ & mut claims,
306+ TokenHash :: new (
307+ verification_data. signing_algorithm ,
308+ & token_response. access_token ,
309+ ) ,
310+ )
311+ . map_err ( mas_oidc_client:: error:: IdTokenError :: from) ?;
299312
300- let ( _header, id_token) = id_token_map
301- . clone ( )
302- . ok_or ( RouteError :: MissingIDToken ) ?
303- . into_parts ( ) ;
313+ // Code hash must match.
314+ mas_jose:: claims:: C_HASH
315+ . extract_optional_with_options (
316+ & mut claims,
317+ TokenHash :: new ( verification_data. signing_algorithm , & code) ,
318+ )
319+ . map_err ( mas_oidc_client:: error:: IdTokenError :: from) ?;
320+
321+ // Nonce must match.
322+ mas_jose:: claims:: NONCE
323+ . extract_required_with_options ( & mut claims, session. nonce . as_str ( ) )
324+ . map_err ( mas_oidc_client:: error:: IdTokenError :: from) ?;
325+
326+ context = context. with_id_token_claims ( claims) ;
327+ }
304328
305- let mut context = AttributeMappingContext :: new ( ) . with_id_token_claims ( id_token) ;
306329 if let Some ( extra_callback_parameters) = extra_callback_parameters. clone ( ) {
307330 context = context. with_extra_callback_parameters ( extra_callback_parameters) ;
308331 }
@@ -312,9 +335,8 @@ pub(crate) async fn handler(
312335 mas_oidc_client:: requests:: userinfo:: fetch_userinfo(
313336 & client,
314337 lazy_metadata. userinfo_endpoint( ) . await ?,
315- response. access_token. as_str( ) ,
316- Some ( verification_data) ,
317- & id_token_map. ok_or( RouteError :: MissingIDToken ) ?,
338+ token_response. access_token. as_str( ) ,
339+ None ,
318340 )
319341 . await ?
320342 ) )
@@ -364,7 +386,7 @@ pub(crate) async fn handler(
364386 & clock,
365387 session,
366388 & link,
367- response . id_token ,
389+ token_response . id_token ,
368390 extra_callback_parameters,
369391 userinfo,
370392 )
0 commit comments