Skip to main content

spiral_auth/providers/
oidc.rs

1use std::env;
2use std::fmt::Debug;
3
4use reqwest_middleware::ClientWithMiddleware as Client;
5use tracing::debug;
6use url::Url;
7
8use super::jwt_bearer;
9use crate::{CredentialResult, Token, TokenSource};
10
11/// Authenticates a workload with a token from any OpenID Connect issuer, read from
12/// `SPIRAL_OIDC_PROVIDER_TOKEN`, as a JWT-bearer grant at the authorization server. The
13/// token's audience must be the authorization server's token endpoint.
14#[derive(Debug)]
15pub struct OidcAuthProvider {
16    client: Client,
17    token_endpoint: Url,
18    workload_id: String,
19    assertion_provider: Option<TokenSource>,
20}
21
22impl OidcAuthProvider {
23    /// Obtain the workload's tokens at `token_endpoint`, the authorization server's.
24    pub fn new(client: impl Into<Client>, token_endpoint: Url, workload_id: String) -> Self {
25        OidcAuthProvider {
26            client: client.into(),
27            token_endpoint,
28            workload_id,
29            assertion_provider: None,
30        }
31    }
32    /// Obtain a fresh platform assertion from this source before each exchange. Use the
33    /// installation's ID as the client ID when composing installation credentials.
34    pub fn with_assertion_provider(mut self, source: TokenSource) -> Self {
35        self.assertion_provider = Some(source);
36        self
37    }
38}
39
40impl OidcAuthProvider {
41    /// A usable token, or `None` when this provider has no credentials to offer.
42    pub async fn token(&self) -> CredentialResult<Option<Token>> {
43        let assertion = match &self.assertion_provider {
44            Some(provider) => provider
45                .token()
46                .await?
47                .map(|token| token.expose_secret().to_owned()),
48            None => env::var("SPIRAL_OIDC_PROVIDER_TOKEN").ok(),
49        };
50        let Some(oidc_token) = assertion else {
51            return Ok(None);
52        };
53
54        let token = jwt_bearer(
55            &self.client,
56            &self.token_endpoint,
57            self.workload_id.clone(),
58            oidc_token,
59            None,
60        )
61        .await?;
62
63        debug!("OidcAuthProvider obtained a workload token");
64        Ok(Some(token))
65    }
66}
67
68impl_token_source!(OidcAuthProvider);