Skip to main content

livekit_token_source/
token_source.rs

1// Copyright 2026 LiveKit, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16
17use crate::caching::TokenSourceCached;
18use crate::error::TokenSourceError;
19use crate::request::{TokenSourceFetchOptions, TokenSourceRequest};
20use crate::response::{TokenSourceResponse, TokenSourceResult};
21use async_trait::async_trait;
22use livekit_net::{Header, HttpClientExt};
23
24const DEVELOPMENT_TOKEN_SERVER_ENDPOINT_URL: &str =
25    "https://cloud-api.livekit.io/api/v2/sandbox/connection-details";
26const DEVELOPMENT_TOKEN_SERVER_ID_HEADER: &str = "X-Sandbox-ID";
27
28/// A token source whose credentials are not parameterized: `fetch` takes no
29/// options and every call resolves the same way.
30#[async_trait]
31pub trait TokenSourceFixed {
32    async fn fetch(&self) -> TokenSourceResult<TokenSourceResponse>;
33}
34
35/// The return type of [`literal`].
36pub struct TokenSourceLiteral {
37    response: TokenSourceResponse,
38}
39
40#[async_trait]
41impl TokenSourceFixed for TokenSourceLiteral {
42    async fn fetch(&self) -> TokenSourceResult<TokenSourceResponse> {
43        Ok(self.response.clone())
44    }
45}
46
47/// Creates a token source holding a single, literal set of credentials,
48/// returned as-is on every fetch.
49pub fn literal(
50    server_url: impl Into<String>,
51    participant_token: impl Into<String>,
52) -> TokenSourceLiteral {
53    TokenSourceLiteral {
54        response: TokenSourceResponse {
55            server_url: server_url.into(),
56            participant_token: participant_token.into(),
57        },
58    }
59}
60
61/// A token source that generates credentials from per-call
62/// [`TokenSourceFetchOptions`] (room name, participant identity, agent
63/// dispatch, ...).
64///
65/// Implement this trait to plug a custom credential backend into code that is
66/// generic over token sources.
67#[async_trait]
68pub trait TokenSourceConfigurable {
69    async fn fetch(
70        &self,
71        options: &TokenSourceFetchOptions,
72    ) -> TokenSourceResult<TokenSourceResponse>;
73
74    /// Wraps this source in a caching layer that stores the last fetched
75    /// credentials and serves them for repeat fetches with equal options, for
76    /// as long as the token stays valid. See [`TokenSourceCached`].
77    fn cached(self) -> TokenSourceCached<Self>
78    where
79        Self: Sized + Send + Sync,
80    {
81        TokenSourceCached::new(self)
82    }
83}
84
85/// The return type of [`endpoint`].
86pub struct TokenSourceEndpoint {
87    endpoint_url: String,
88    headers: HashMap<String, String>,
89}
90
91#[async_trait]
92impl TokenSourceConfigurable for TokenSourceEndpoint {
93    async fn fetch(
94        &self,
95        options: &TokenSourceFetchOptions,
96    ) -> TokenSourceResult<TokenSourceResponse> {
97        let request = TokenSourceRequest::from(options);
98
99        let http_client =
100            livekit_net::http_client().ok_or(TokenSourceError::TransportNotConfigured)?;
101
102        let body = serde_json::to_vec(&request)?;
103        let mut headers =
104            vec![Header { name: "Content-Type".into(), value: "application/json".into() }];
105        headers.extend(
106            self.headers
107                .iter()
108                .map(|(name, value)| Header { name: name.clone(), value: value.clone() }),
109        );
110
111        let response = http_client.post(self.endpoint_url.clone(), headers, body).await?;
112
113        if !(200..300).contains(&response.status) {
114            return Err(TokenSourceError::Server {
115                status: response.status,
116                body: String::from_utf8_lossy(&response.body).into_owned(),
117            });
118        }
119
120        let connection_details = serde_json::from_slice::<TokenSourceResponse>(&response.body)?;
121        Ok(connection_details)
122    }
123}
124
125/// Creates a token source that fetches credentials from the given URL
126/// using the standard token endpoint format.
127///
128/// The given headers are sent along with every request, e.g. for
129/// authentication against the endpoint.
130///
131/// See <https://docs.livekit.io/frontends/build/authentication/endpoint/>
132/// for the endpoint contract.
133pub fn endpoint(endpoint_url: impl Into<String>) -> TokenSourceEndpoint {
134    TokenSourceEndpoint { endpoint_url: endpoint_url.into(), headers: HashMap::new() }
135}
136
137impl TokenSourceEndpoint {
138    /// Adds a single header to the headers sent with every request, keeping any set previously.
139    pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
140        self.headers.insert(key.into(), value.into());
141        self
142    }
143
144    /// Adds the given headers to the headers sent with every request, keeping any set previously.
145    /// A key that was already set is overwritten with its new value.
146    pub fn with_headers(
147        mut self,
148        value: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
149    ) -> Self {
150        self.headers.extend(value.into_iter().map(|(k, v)| (k.into(), v.into())));
151        self
152    }
153}
154
155/// The return type of [`development_token_server`].
156pub struct TokenSourceDevelopmentTokenServer {
157    token_source_endpoint: TokenSourceEndpoint,
158}
159
160#[async_trait]
161impl TokenSourceConfigurable for TokenSourceDevelopmentTokenServer {
162    async fn fetch(
163        &self,
164        options: &TokenSourceFetchOptions,
165    ) -> TokenSourceResult<TokenSourceResponse> {
166        self.token_source_endpoint.fetch(options).await
167    }
168}
169
170/// Creates a token source that queries a LiveKit development token server
171/// for credentials, for quick prototyping / getting-started use cases.
172///
173/// **This token provider is INSECURE and should NOT be used in
174/// production.**
175///
176/// See <https://docs.livekit.io/frontends/build/authentication/development-token-server/>.
177pub fn development_token_server(
178    token_server_id: impl Into<String>,
179) -> TokenSourceDevelopmentTokenServer {
180    TokenSourceDevelopmentTokenServer {
181        token_source_endpoint: endpoint(DEVELOPMENT_TOKEN_SERVER_ENDPOINT_URL)
182            .with_header(DEVELOPMENT_TOKEN_SERVER_ID_HEADER, token_server_id),
183    }
184}