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::error::TokenSourceError;
18use crate::request::{TokenSourceFetchOptions, TokenSourceRequest};
19use crate::response::{TokenSourceResponse, TokenSourceResult};
20use async_trait::async_trait;
21use livekit_net::{Header, HttpClientExt};
22
23const DEVELOPMENT_TOKEN_SERVER_ENDPOINT_URL: &str =
24    "https://cloud-api.livekit.io/api/v2/sandbox/connection-details";
25const DEVELOPMENT_TOKEN_SERVER_ID_HEADER: &str = "X-Sandbox-ID";
26
27/// A token source whose credentials are not parameterized: `fetch` takes no
28/// options and every call resolves the same way.
29#[async_trait]
30pub trait TokenSourceFixed {
31    async fn fetch(&self) -> TokenSourceResult<TokenSourceResponse>;
32}
33
34/// The return type of [`literal`].
35pub struct TokenSourceLiteral {
36    response: TokenSourceResponse,
37}
38
39#[async_trait]
40impl TokenSourceFixed for TokenSourceLiteral {
41    async fn fetch(&self) -> TokenSourceResult<TokenSourceResponse> {
42        Ok(self.response.clone())
43    }
44}
45
46/// Creates a token source holding a single, literal set of credentials,
47/// returned as-is on every fetch.
48pub fn literal(
49    server_url: impl Into<String>,
50    participant_token: impl Into<String>,
51) -> TokenSourceLiteral {
52    TokenSourceLiteral {
53        response: TokenSourceResponse {
54            server_url: server_url.into(),
55            participant_token: participant_token.into(),
56        },
57    }
58}
59
60/// A token source that generates credentials from per-call
61/// [`TokenSourceFetchOptions`] (room name, participant identity, agent
62/// dispatch, ...).
63///
64/// Implement this trait to plug a custom credential backend into code that is
65/// generic over token sources.
66#[async_trait]
67pub trait TokenSourceConfigurable {
68    async fn fetch(
69        &self,
70        options: &TokenSourceFetchOptions,
71    ) -> TokenSourceResult<TokenSourceResponse>;
72}
73
74/// The return type of [`endpoint`].
75pub struct TokenSourceEndpoint {
76    endpoint_url: String,
77    headers: HashMap<String, String>,
78}
79
80#[async_trait]
81impl TokenSourceConfigurable for TokenSourceEndpoint {
82    async fn fetch(
83        &self,
84        options: &TokenSourceFetchOptions,
85    ) -> TokenSourceResult<TokenSourceResponse> {
86        let request = TokenSourceRequest::from(options);
87
88        let http_client =
89            livekit_net::http_client().ok_or(TokenSourceError::TransportNotConfigured)?;
90
91        let body = serde_json::to_vec(&request)?;
92        let mut headers =
93            vec![Header { name: "Content-Type".into(), value: "application/json".into() }];
94        headers.extend(
95            self.headers
96                .iter()
97                .map(|(name, value)| Header { name: name.clone(), value: value.clone() }),
98        );
99
100        let response = http_client.post(self.endpoint_url.clone(), headers, body).await?;
101
102        if !(200..300).contains(&response.status) {
103            return Err(TokenSourceError::Server {
104                status: response.status,
105                body: String::from_utf8_lossy(&response.body).into_owned(),
106            });
107        }
108
109        let connection_details = serde_json::from_slice::<TokenSourceResponse>(&response.body)?;
110        Ok(connection_details)
111    }
112}
113
114/// Creates a token source that fetches credentials from the given URL
115/// using the standard token endpoint format.
116///
117/// The given headers are sent along with every request, e.g. for
118/// authentication against the endpoint.
119///
120/// See <https://docs.livekit.io/frontends/build/authentication/endpoint/>
121/// for the endpoint contract.
122pub fn endpoint(endpoint_url: impl Into<String>) -> TokenSourceEndpoint {
123    TokenSourceEndpoint { endpoint_url: endpoint_url.into(), headers: HashMap::new() }
124}
125
126impl TokenSourceEndpoint {
127    /// Adds a single header to the headers sent with every request, keeping any set previously.
128    pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
129        self.headers.insert(key.into(), value.into());
130        self
131    }
132
133    /// Adds the given headers to the headers sent with every request, keeping any set previously.
134    /// A key that was already set is overwritten with its new value.
135    pub fn with_headers(
136        mut self,
137        value: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
138    ) -> Self {
139        self.headers.extend(value.into_iter().map(|(k, v)| (k.into(), v.into())));
140        self
141    }
142}
143
144/// The return type of [`development_token_server`].
145pub struct TokenSourceDevelopmentTokenServer {
146    token_source_endpoint: TokenSourceEndpoint,
147}
148
149#[async_trait]
150impl TokenSourceConfigurable for TokenSourceDevelopmentTokenServer {
151    async fn fetch(
152        &self,
153        options: &TokenSourceFetchOptions,
154    ) -> TokenSourceResult<TokenSourceResponse> {
155        self.token_source_endpoint.fetch(options).await
156    }
157}
158
159/// Creates a token source that queries a LiveKit development token server
160/// for credentials, for quick prototyping / getting-started use cases.
161///
162/// **This token provider is INSECURE and should NOT be used in
163/// production.**
164///
165/// See <https://docs.livekit.io/frontends/build/authentication/sandbox-token-server/>.
166pub fn development_token_server(
167    token_server_id: impl Into<String>,
168) -> TokenSourceDevelopmentTokenServer {
169    TokenSourceDevelopmentTokenServer {
170        token_source_endpoint: endpoint(DEVELOPMENT_TOKEN_SERVER_ENDPOINT_URL)
171            .with_header(DEVELOPMENT_TOKEN_SERVER_ID_HEADER, token_server_id),
172    }
173}