Skip to main content

rauthy_client/handler/
mod.rs

1use crate::build_lax_cookie_300;
2use crate::cookie_state::{OIDC_STATE_COOKIE, OidcCookieState};
3use crate::provider::OidcProvider;
4use crate::rauthy_error::RauthyError;
5use crate::token_set::OidcTokenSet;
6use crate::tokens::claims::{AccessToken, IdToken};
7use serde::{Deserialize, Serialize};
8use std::borrow::Cow;
9use tracing::error;
10
11#[cfg(feature = "axum")]
12pub mod axum;
13
14#[cfg(feature = "actix-web")]
15pub mod actix_web;
16
17/// Query params appended to the callback URL after a successful login from the user
18#[derive(Debug, Deserialize)]
19pub struct OidcCallbackParams {
20    pub code: String,
21    pub state: String,
22}
23
24#[derive(Debug, Serialize)]
25struct OidcCodeRequestParams {
26    client_id: String,
27    client_secret: Option<String>,
28    code: String,
29    code_verifier: String,
30    grant_type: &'static str,
31    redirect_uri: String,
32}
33
34/// Used instead of a normal bool to never confuse people about a meaning of multiple bool's
35#[derive(Debug, PartialEq)]
36pub enum OidcCookieInsecure {
37    Yes,
38    No,
39}
40
41/// Used instead of a normal bool to never confuse people about a meaning of multiple bool's
42#[derive(Debug, PartialEq)]
43pub enum OidcSetRedirectStatus {
44    Yes,
45    No,
46}
47
48impl OidcCodeRequestParams {
49    pub async fn try_new(
50        code: String,
51        code_verifier: String,
52        redirect_uri: String,
53    ) -> Result<Self, RauthyError> {
54        let cfg = OidcProvider::config()?;
55        let client_id = cfg.client_id.clone();
56        let client_secret = cfg.secret.clone();
57        Ok(Self {
58            client_id,
59            client_secret,
60            code,
61            code_verifier,
62            grant_type: "authorization_code",
63            redirect_uri,
64        })
65    }
66}
67
68/// Check the authentication
69///
70/// This will only exist without `actix-web` or `axum` features
71///
72/// # Returns
73/// - Ok(()) if the user is logged in
74/// - Err(None) if the user is not logged in and the OIDC provider is not correctly set up
75/// - Err(Some(String, String)) if the user is not logged in.
76///   In this case, the values in the tuple are header values you should return to the client:
77///   (LocationHeaderString, EncryptedStateCookieValue)
78#[cfg(not(any(feature = "axum", feature = "actix-web")))]
79pub async fn validate_principal_generic(
80    principal: Option<crate::principal::PrincipalOidc>,
81    enc_key: &[u8],
82    insecure: OidcCookieInsecure,
83) -> Result<(), Option<(String, String)>> {
84    if principal.is_some() {
85        Ok(())
86    } else {
87        let (cookie_state, challenge) = OidcCookieState::generate();
88        let loc = {
89            let base = match OidcProvider::config() {
90                Ok(c) => &c.auth_url_base,
91                Err(_) => {
92                    return Err(None);
93                }
94            };
95            format!(
96                "{base}&code_challenge={challenge}&nonce={}&state={}",
97                cookie_state.nonce, cookie_state.state
98            )
99        };
100
101        let value = cookie_state.to_encrypted_cookie_value(enc_key);
102        let cookie = build_lax_cookie_300(OIDC_STATE_COOKIE, &value, insecure);
103
104        Err(Some((loc, cookie)))
105    }
106}
107
108/// Handles the OIDC callback
109///
110/// If you use `actix-web` or `axum` features, you should use the more specific implementations.
111///
112/// # Panics
113/// If the given `enc_key` is not exactly 32 bytes long
114pub async fn oidc_callback(
115    cookie_state: OidcCookieState,
116    params: OidcCallbackParams,
117    insecure: OidcCookieInsecure,
118) -> Result<(String, OidcTokenSet, IdToken), RauthyError> {
119    // validate the state to prevent xsrf attacks
120    if params.state != cookie_state.state {
121        return Err(RauthyError::BadRequest("Bad state"));
122    }
123
124    let (token_uri, redirect_uri) = {
125        let cfg = OidcProvider::config()?;
126        let t = cfg.provider.token_endpoint.clone();
127        let r = cfg.redirect_uri.clone();
128        (t, r)
129    };
130    let req_data = OidcCodeRequestParams::try_new(
131        params.code.clone(),
132        cookie_state.pkce_verifier,
133        redirect_uri,
134    )
135    .await?;
136
137    let res = OidcProvider::client()
138        .post(&token_uri)
139        .form(&req_data)
140        .send()
141        .await?;
142    if res.status().as_u16() >= 300 {
143        error!("{:?}", res);
144        let body = res.text().await;
145        let msg = match body {
146            Ok(value) => {
147                error!("raw OIDC provider response: {:?}", value);
148                value
149            }
150            Err(_) => "Internal Error - Bad response status".to_string(),
151        };
152
153        Err(RauthyError::Provider(Cow::from(msg)))
154    } else {
155        match res.json::<OidcTokenSet>().await {
156            Ok(ts) => {
157                // validate access token
158                let access_claims = AccessToken::from_token_validated(&ts.access_token).await?;
159
160                // validate id token
161                if ts.id_token.is_none() {
162                    return Err(RauthyError::Provider(Cow::from("ID token is missing")));
163                }
164                let id_claims = IdToken::from_token_validated(
165                    ts.id_token.as_deref().unwrap(),
166                    &cookie_state.nonce,
167                )
168                .await?;
169
170                // make sure the `sub` claims match
171                if access_claims.common.sub.is_none()
172                    || access_claims.common.sub != id_claims.common.sub
173                {
174                    return Err(RauthyError::InvalidClaims("Invalid `sub` claims"));
175                }
176
177                // reset STATE_COOKIE
178                let cookie = build_lax_cookie_300(OIDC_STATE_COOKIE, "", insecure);
179
180                Ok((cookie, ts, id_claims))
181            }
182            Err(err) => {
183                error!("Deserializing OIDC response to OidcTokenSet: {}", err);
184                Err(RauthyError::Provider(Cow::from(
185                    "Internal Error - Deserializing OIDC response",
186                )))
187            }
188        }
189    }
190}