Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions src/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const CUSTOM_ENGINE: engine::GeneralPurpose =
pub fn validate_token_rs256<CustomClaims: Serialize + DeserializeOwned>(
token_string: &str,
settings: &Config,
required_nonce: Option<&str>,
) -> Result<JWTClaims<CustomClaims>, 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.
Expand Down Expand Up @@ -49,6 +50,7 @@ pub fn validate_token_rs256<CustomClaims: Serialize + DeserializeOwned>(
&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::<CustomClaims>(token_string, Some(verification_options))
Expand Down Expand Up @@ -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<String> {
match &self
.auth_key
.verify_token::<NoCustomClaims>(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<String> {
let opts = VerificationOptions {
required_subject: Some(expected_state.to_string()),
..Default::default()
};
self.auth_key
.verify_token::<NoCustomClaims>(state_and_nonce, Some(opts))
.ok()?
.nonce
}
}
36 changes: 22 additions & 14 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,19 @@ fn main(mut req: Request) -> Result<Response, Error> {
(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(),
Expand All @@ -103,6 +100,17 @@ fn main(mut req: Request) -> Result<Response, Error> {
}
// Deserialize the response from the authorize step.
let auth = exchange_res.take_body_json::<AuthorizeResponse>().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::<NoCustomClaims>(
&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,
Expand Down Expand Up @@ -139,15 +147,15 @@ fn main(mut req: Request) -> Result<Response, Error> {
}
// Validate the JWT access token.
} else if settings.config.jwt_access_token
&& validate_token_rs256::<NoCustomClaims>(access_token, &settings).is_err()
&& validate_token_rs256::<NoCustomClaims>(access_token, &settings, None).is_err()
{
println!("Failed to validate the access token at the edge.");
return Ok(responses::unauthorized("JWT access token invalid."));
}

// Validate the ID token.
println!("Validating the ID token at the edge...");
if validate_token_rs256::<NoCustomClaims>(id_token, &settings).is_err() {
if validate_token_rs256::<NoCustomClaims>(id_token, &settings, None).is_err() {
return Ok(responses::unauthorized("ID token invalid."));
}

Expand Down
Loading