mas_oidc_client/requests/
refresh_token.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2022-2024 Kévin Commaille.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7//! Requests for using [Refresh Tokens].
8//!
9//! [Refresh Tokens]: https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
10
11use chrono::{DateTime, Utc};
12use mas_jose::claims::{self, TokenHash};
13use oauth2_types::{
14    requests::{AccessTokenRequest, AccessTokenResponse, RefreshTokenGrant},
15    scope::Scope,
16};
17use rand::Rng;
18use url::Url;
19
20use super::jose::JwtVerificationData;
21use crate::{
22    error::{IdTokenError, TokenRefreshError},
23    requests::{jose::verify_id_token, token::request_access_token},
24    types::{IdToken, client_credentials::ClientCredentials},
25};
26
27/// Exchange an authorization code for an access token.
28///
29/// This should be used as the first step for logging in, and to request a
30/// token with a new scope.
31///
32/// # Arguments
33///
34/// * `http_client` - The reqwest client to use for making HTTP requests.
35///
36/// * `client_credentials` - The credentials obtained when registering the
37///   client.
38///
39/// * `token_endpoint` - The URL of the issuer's Token endpoint.
40///
41/// * `refresh_token` - The token used to refresh the access token returned at
42///   the Token endpoint.
43///
44/// * `scope` - The scope of the access token. The requested scope must not
45///   include any scope not originally granted to the access token, and if
46///   omitted is treated as equal to the scope originally granted by the issuer.
47///
48/// * `id_token_verification_data` - The data required to verify the ID Token in
49///   the response.
50///
51///   The signing algorithm corresponds to the `id_token_signed_response_alg`
52/// field in the client metadata.
53///
54///   If it is not provided, the ID Token won't be verified.
55///
56/// * `auth_id_token` - If an ID Token is expected in the response, the ID token
57///   that was returned from the latest authorization request.
58///
59/// * `now` - The current time.
60///
61/// * `rng` - A random number generator.
62///
63/// # Errors
64///
65/// Returns an error if the request fails, the response is invalid or the
66/// verification of the ID Token fails.
67#[allow(clippy::too_many_arguments)]
68#[tracing::instrument(skip_all, fields(token_endpoint))]
69pub async fn refresh_access_token(
70    http_client: &reqwest::Client,
71    client_credentials: ClientCredentials,
72    token_endpoint: &Url,
73    refresh_token: String,
74    scope: Option<Scope>,
75    id_token_verification_data: Option<JwtVerificationData<'_>>,
76    auth_id_token: Option<&IdToken<'_>>,
77    now: DateTime<Utc>,
78    rng: &mut impl Rng,
79) -> Result<(AccessTokenResponse, Option<IdToken<'static>>), TokenRefreshError> {
80    tracing::debug!("Refreshing access token…");
81
82    let token_response = request_access_token(
83        http_client,
84        client_credentials,
85        token_endpoint,
86        AccessTokenRequest::RefreshToken(RefreshTokenGrant {
87            refresh_token,
88            scope,
89        }),
90        now,
91        rng,
92    )
93    .await?;
94
95    let id_token = if let Some((verification_data, id_token)) =
96        id_token_verification_data.zip(token_response.id_token.as_ref())
97    {
98        let auth_id_token = auth_id_token.ok_or(IdTokenError::MissingAuthIdToken)?;
99        let signing_alg = verification_data.signing_algorithm;
100
101        let id_token = verify_id_token(id_token, verification_data, Some(auth_id_token), now)?;
102
103        let mut claims = id_token.payload().clone();
104
105        // Access token hash must match.
106        claims::AT_HASH
107            .extract_optional_with_options(
108                &mut claims,
109                TokenHash::new(signing_alg, &token_response.access_token),
110            )
111            .map_err(IdTokenError::from)?;
112
113        Some(id_token.into_owned())
114    } else {
115        None
116    };
117
118    Ok((token_response, id_token))
119}