A library for ATProtocol identities.
1//! OAuth client metadata endpoint handler.
2//!
3//! RFC 7591 compliant client metadata for dynamic registration
4//! and authorization server discovery.
5
6use atproto_identity::key::to_public;
7use axum::{Json, response::IntoResponse};
8use serde::Serialize;
9
10use crate::state::OAuthClientConfig;
11use atproto_oauth::jwk::{WrappedJsonWebKeySet, generate};
12
13// See also: https://atproto.com/specs/oauth#client-id-metadata-document
14#[derive(Serialize, Default)]
15struct AuthMetadata<'a> {
16 client_id: String,
17 dpop_bound_access_tokens: bool,
18 application_type: &'a str,
19 redirect_uris: Vec<String>,
20 grant_types: Vec<&'a str>,
21 response_types: Vec<&'a str>,
22 scope: String,
23 token_endpoint_auth_method: &'a str,
24 subject_type: &'a str,
25 token_endpoint_auth_signing_alg: &'a str,
26
27 #[serde(skip_serializing_if = "Option::is_none")]
28 #[serde(default)]
29 jwks_uri: Option<String>,
30
31 #[serde(skip_serializing_if = "Option::is_none")]
32 #[serde(default)]
33 jwks: Option<WrappedJsonWebKeySet>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
36 #[serde(default)]
37 client_name: Option<String>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
40 #[serde(default)]
41 client_uri: Option<String>,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
44 #[serde(default)]
45 logo_uri: Option<String>,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
48 #[serde(default)]
49 tos_uri: Option<String>,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
52 #[serde(default)]
53 policy_uri: Option<String>,
54}
55
56/// Handles requests for OAuth client metadata.
57///
58/// Returns RFC 7591 compliant client metadata for dynamic client registration.
59pub async fn handle_oauth_metadata(oauth_client_config: OAuthClientConfig) -> impl IntoResponse {
60 // Determine jwks_uri and jwks based on configuration
61 let (jwks_uri, jwks) = if let Some(ref uri) = oauth_client_config.jwks_uri {
62 // If jwks_uri is provided, use it and don't include inline jwks
63 (Some(uri.clone()), None)
64 } else {
65 // If no jwks_uri, build inline jwks from signing keys
66 let mut jwks_keys = Vec::new();
67 for key_data in &oauth_client_config.signing_keys {
68 if let Ok(public_key_data) = to_public(key_data)
69 && let Ok(jwk) = generate(&public_key_data)
70 {
71 jwks_keys.push(jwk);
72 }
73 }
74 (None, Some(WrappedJsonWebKeySet { keys: jwks_keys }))
75 };
76
77 let resp = AuthMetadata {
78 application_type: "web",
79 client_id: oauth_client_config.client_id.clone(),
80 dpop_bound_access_tokens: true,
81 grant_types: vec!["authorization_code", "refresh_token"],
82 redirect_uris: vec![oauth_client_config.redirect_uris.clone()],
83 response_types: vec!["code"],
84 scope: oauth_client_config.scope().to_string(),
85 token_endpoint_auth_method: "private_key_jwt",
86 token_endpoint_auth_signing_alg: "ES256",
87 subject_type: "public",
88 jwks_uri,
89 jwks,
90 client_name: oauth_client_config.client_name.clone(),
91 client_uri: oauth_client_config.client_uri.clone(),
92 logo_uri: oauth_client_config.logo_uri.clone(),
93 tos_uri: oauth_client_config.tos_uri.clone(),
94 policy_uri: oauth_client_config.policy_uri.clone(),
95 };
96 Json(resp)
97}