diff --git a/src/jwt.rs b/src/jwt.rs index b9bdc75..5284c5b 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -15,6 +15,7 @@ const CUSTOM_ENGINE: engine::GeneralPurpose = pub fn validate_token_rs256( token_string: &str, settings: &Config, + required_nonce: Option<&str>, ) -> Result, Error> { // Peek at the token metadata before verification and retrieve the key identifier, // in order to pick the right key out of the JWK set. @@ -49,6 +50,7 @@ pub fn validate_token_rs256( &settings.openid_configuration.issuer, ])), allowed_audiences: Some(HashSet::from_strings(&[&settings.config.client_id])), + required_nonce: required_nonce.map(str::to_owned), ..Default::default() }; public_key.verify_token::(token_string, Some(verification_options)) @@ -77,14 +79,20 @@ impl NonceToken { nonce, ) } - // Verifies the token and retrieves its subject claim, a state string. - pub fn get_claimed_state(&self, state_and_nonce: &str) -> Option { - match &self - .auth_key - .verify_token::(state_and_nonce, None) - { - Ok(state_and_nonce_claim) => state_and_nonce_claim.subject.to_owned(), - _ => None, - } + // Verifies the token, requires its subject claim to equal `expected_state`, + // and returns the attached nonce. + pub fn verify_and_claim_nonce( + &self, + state_and_nonce: &str, + expected_state: &str, + ) -> Option { + let opts = VerificationOptions { + required_subject: Some(expected_state.to_string()), + ..Default::default() + }; + self.auth_key + .verify_token::(state_and_nonce, Some(opts)) + .ok()? + .nonce } } diff --git a/src/main.rs b/src/main.rs index f51406d..c3ddda8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,22 +65,19 @@ fn main(mut req: Request) -> Result { (Some(state), Some(code_verifier)) => { println!("state and code_verifier cookies found."); - // Authenticate the state token returned by the IdP, - // and verify that the state we stored matches its subject claim. + // Authenticate the state token returned by the IdP, require its + // subject claim to equal the cookie value, and recover the nonce + // we attached. println!("Verifying the token's claims and matching state..."); - match NonceToken::new(&settings.config.nonce_secret).get_claimed_state(&qs.state) { - Some(claimed_state) => { - if state != &claimed_state { - return Ok(responses::unauthorized("State mismatch.")); - } - } - _ => { - return Ok(responses::unauthorized("Could not verify state.")); - } + let nonce = match NonceToken::new(&settings.config.nonce_secret) + .verify_and_claim_nonce(&qs.state, state) + { + Some(n) => n, + None => return Ok(responses::unauthorized("Could not verify state.")), }; // Exchange the authorization code for tokens. println!("Exchanging the authorization code for tokens..."); - let mut exchange_res = Request::post(settings.openid_configuration.token_endpoint) + let mut exchange_res = Request::post(&settings.openid_configuration.token_endpoint) .with_body_form(&ExchangePayload { client_id: &settings.config.client_id, client_secret: settings.config.client_secret.as_deref(), @@ -103,6 +100,17 @@ fn main(mut req: Request) -> Result { } // Deserialize the response from the authorize step. let auth = exchange_res.take_body_json::().unwrap(); + // Validate the IdP-signed id_token now and require the nonce we + // sent in the authorize request to come back unchanged. + if validate_token_rs256::( + &auth.id_token, + &settings, + Some(&nonce), + ) + .is_err() + { + return Ok(responses::unauthorized("ID token invalid.")); + } // Replay the original request, setting the tokens as cookies. Ok(responses::temporary_redirect( original_req, @@ -139,7 +147,7 @@ fn main(mut req: Request) -> Result { } // Validate the JWT access token. } else if settings.config.jwt_access_token - && validate_token_rs256::(access_token, &settings).is_err() + && validate_token_rs256::(access_token, &settings, None).is_err() { println!("Failed to validate the access token at the edge."); return Ok(responses::unauthorized("JWT access token invalid.")); @@ -147,7 +155,7 @@ fn main(mut req: Request) -> Result { // Validate the ID token. println!("Validating the ID token at the edge..."); - if validate_token_rs256::(id_token, &settings).is_err() { + if validate_token_rs256::(id_token, &settings, None).is_err() { return Ok(responses::unauthorized("ID token invalid.")); }