cf_access/
lib.rs

1/*
2 * MIT License
3 *
4 * Copyright (c) 2025 Jasmine Tai
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy of
7 * this software and associated documentation files (the "Software"), to deal in
8 * the Software without restriction, including without limitation the rights to
9 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10 * of the Software, and to permit persons to whom the Software is furnished to do
11 * so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24
25#![doc = include_str!("../README.md")]
26
27mod error;
28mod token;
29
30use std::time::Duration;
31
32use jwtk::jwk::RemoteJwksVerifier;
33use jwtk::Claims;
34use reqwest::Client;
35
36pub use error::Error;
37pub use jwtk;
38pub use reqwest;
39pub use token::Token;
40
41/// A validator for Cloudflare Access JWTs.
42pub struct Validator {
43    inner: RemoteJwksVerifier,
44    audience: String,
45}
46
47impl Validator {
48    /// Creates a new [`Validator`] with the given Cloudflare Access team name
49    /// and application AUD tag.
50    pub fn new(team_name: &str, audience: impl Into<String>) -> Self {
51        Validator::with_client(Client::default(), team_name, audience)
52    }
53
54    /// Creates a new [`Validator`] from the current process's environment
55    /// variables.
56    #[cfg(feature = "env")]
57    pub fn from_env() -> Result<Self, Error> {
58        fn var(name: &'static str) -> Result<String, Error> {
59            std::env::var(name).map_err(|_| Error::MissingEnv(name))
60        }
61
62        let team_name = var("CF_ACCESS_TEAM")?;
63        let audience = var("CF_ACCESS_AUD")?;
64        Ok(Validator::new(&team_name, audience))
65    }
66
67    /// Creates a new [`Validator`] that uses a specific [`reqwest::Client`].
68    pub fn with_client(client: Client, team_name: &str, audience: impl Into<String>) -> Self {
69        let issuer = format!("https://{team_name}.cloudflareaccess.com");
70        let url = format!("{issuer}/cdn-cgi/access/certs");
71        Validator {
72            inner: RemoteJwksVerifier::new(url, Some(client), CACHE_DURATION),
73            audience: audience.into(),
74        }
75    }
76
77    /// Validates the JWT.
78    pub async fn validate(&self, jwt: &str) -> Result<Claims<Token>, Error> {
79        let mut token = self.inner.verify(jwt).await?;
80        if !token.claims().aud.iter().any(|aud| **aud == self.audience) {
81            return Err(Error::InvalidAud);
82        }
83
84        Ok(std::mem::take(token.claims_mut()))
85    }
86}
87
88const CACHE_DURATION: Duration = Duration::from_secs(60 * 60 * 24 * 3); // 3 days