Skip to main content

better_auth_api/plugins/oauth/
mod.rs

1use async_trait::async_trait;
2
3use better_auth_core::AuthResult;
4use better_auth_core::{AuthContext, AuthPlugin, AuthRoute};
5use better_auth_core::{AuthRequest, AuthResponse, HttpMethod};
6
7pub mod encryption;
8mod handlers;
9mod providers;
10mod state;
11mod types;
12
13pub use providers::{
14    OAuthCallbackUserName, OAuthCallbackUserPayload, OAuthConfig, OAuthIdTokenVerifier,
15    OAuthProvider, OAuthRefreshTokenHandler, OAuthTokenSet, OAuthUserInfo, OAuthUserInfoHandler,
16    OAuthUserInfoRequest, OAuthUserInfoResponse,
17};
18
19pub struct OAuthPlugin {
20    config: OAuthConfig,
21}
22
23impl OAuthPlugin {
24    pub fn new() -> Self {
25        Self {
26            config: OAuthConfig::default(),
27        }
28    }
29
30    pub fn with_config(config: OAuthConfig) -> Self {
31        Self { config }
32    }
33
34    pub fn add_provider(mut self, name: &str, provider: OAuthProvider) -> Self {
35        let _ = self.config.providers.insert(name.to_string(), provider);
36        self
37    }
38}
39
40impl Default for OAuthPlugin {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46#[async_trait]
47impl<S: better_auth_core::AuthSchema> AuthPlugin<S> for OAuthPlugin {
48    fn name(&self) -> &'static str {
49        "oauth"
50    }
51
52    fn routes(&self) -> Vec<AuthRoute> {
53        vec![
54            AuthRoute::post("/sign-in/social", "social_sign_in"),
55            AuthRoute::get("/callback/{provider}", "oauth_callback"),
56            AuthRoute::post("/callback/{provider}", "oauth_callback_post"),
57            AuthRoute::post("/link-social", "link_social"),
58            AuthRoute::post("/get-access-token", "get_access_token"),
59            AuthRoute::post("/refresh-token", "refresh_token"),
60            AuthRoute::get("/account-info", "account_info"),
61        ]
62    }
63
64    async fn on_request(
65        &self,
66        req: &AuthRequest,
67        ctx: &AuthContext<S>,
68    ) -> AuthResult<Option<AuthResponse>> {
69        match (req.method(), req.path()) {
70            (HttpMethod::Post, "/sign-in/social") => Ok(Some(
71                handlers::handle_social_sign_in(&self.config, req, ctx).await?,
72            )),
73            (HttpMethod::Get | HttpMethod::Post, path) if path_matches_callback(path) => {
74                let provider = extract_provider_from_callback(path);
75                Ok(Some(
76                    handlers::handle_callback(&self.config, &provider, req, ctx).await?,
77                ))
78            }
79            (HttpMethod::Post, "/link-social") => Ok(Some(
80                handlers::handle_link_social(&self.config, req, ctx).await?,
81            )),
82            (HttpMethod::Post, "/get-access-token") => Ok(Some(
83                handlers::handle_get_access_token(&self.config, req, ctx).await?,
84            )),
85            (HttpMethod::Post, "/refresh-token") => Ok(Some(
86                handlers::handle_refresh_token(&self.config, req, ctx).await?,
87            )),
88            (HttpMethod::Get, "/account-info") => Ok(Some(
89                handlers::handle_account_info(&self.config, req, ctx).await?,
90            )),
91            _ => Ok(None),
92        }
93    }
94}
95
96/// Check if the path matches `/callback/{provider}` (with optional query string).
97fn path_matches_callback(path: &str) -> bool {
98    let path_without_query = path.split('?').next().unwrap_or(path);
99    path_without_query.starts_with("/callback/") && path_without_query.len() > "/callback/".len()
100}
101
102/// Extract the provider name from `/callback/{provider}?...`.
103fn extract_provider_from_callback(path: &str) -> String {
104    let path_without_query = path.split('?').next().unwrap_or(path);
105    path_without_query["/callback/".len()..].to_string()
106}