better_auth_api/plugins/passkey/
mod.rs1use better_auth_core::{AuthContext, AuthError, AuthResult};
2use better_auth_core::{AuthRequest, AuthResponse};
3
4use better_auth_core::utils::cookie_utils::create_session_cookie;
5
6pub(super) mod handlers;
7pub(super) mod types;
8pub(super) mod webauthn;
9
10#[cfg(test)]
11mod tests;
12
13use handlers::*;
14use types::*;
15
16pub struct PasskeyPlugin {
21 config: PasskeyConfig,
22}
23
24#[derive(Debug, Clone, better_auth_core::PluginConfig)]
25#[plugin(name = "PasskeyPlugin")]
26pub struct PasskeyConfig {
27 #[config(default = String::new())]
28 pub rp_id: String,
29 #[config(default = "Better Auth".to_string())]
30 pub rp_name: String,
31 #[config(default = String::new())]
32 pub origin: String,
33 #[config(default = 300)]
34 pub challenge_ttl_secs: i64,
35}
36
37impl PasskeyPlugin {
40 async fn handle_generate_register_options(
44 &self,
45 req: &AuthRequest,
46 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
47 ) -> AuthResult<AuthResponse> {
48 let (user, _session) = ctx.require_session(req).await?;
49 let passkey_name = req.query.get("name").map(|s| s.as_str());
50 let authenticator_attachment = req.query.get("authenticatorAttachment").map(|s| s.as_str());
51 let (result, cookie_header) = generate_register_options_core(
52 &user,
53 passkey_name,
54 authenticator_attachment,
55 &self.config,
56 ctx,
57 )
58 .await?;
59 Ok(AuthResponse::json(200, &result)?.with_header("Set-Cookie", cookie_header))
60 }
61
62 async fn handle_verify_registration(
64 &self,
65 req: &AuthRequest,
66 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
67 ) -> AuthResult<AuthResponse> {
68 let (user, _session) = ctx.require_session(req).await?;
69 let body: VerifyRegistrationRequest = match better_auth_core::validate_request_body(req) {
70 Ok(v) => v,
71 Err(resp) => return Ok(resp),
72 };
73 match verify_registration_core(&body, req, &user, &self.config, ctx).await? {
74 PasskeyHandlerOutcome::Success(result) => {
75 AuthResponse::json(200, &result).map_err(AuthError::from)
76 }
77 PasskeyHandlerOutcome::Response(response) => Ok(response),
78 }
79 }
80
81 async fn handle_generate_authenticate_options(
83 &self,
84 req: &AuthRequest,
85 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
86 ) -> AuthResult<AuthResponse> {
87 let maybe_user = ctx.require_session(req).await.ok().map(|(u, _)| u);
88 let (result, cookie_header) =
89 generate_authenticate_options_core(maybe_user.as_ref(), &self.config, ctx).await?;
90 Ok(AuthResponse::json(200, &result)?.with_header("Set-Cookie", cookie_header))
91 }
92
93 async fn handle_verify_authentication(
95 &self,
96 req: &AuthRequest,
97 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
98 ) -> AuthResult<AuthResponse> {
99 let body: VerifyAuthenticationRequest = match better_auth_core::validate_request_body(req) {
100 Ok(v) => v,
101 Err(resp) => return Ok(resp),
102 };
103 let ip_address = req.headers.get("x-forwarded-for").cloned();
104 let user_agent = req.headers.get("user-agent").cloned();
105 match verify_authentication_core(&body, req, &self.config, ip_address, user_agent, ctx)
106 .await?
107 {
108 PasskeyHandlerOutcome::Success((response, token)) => {
109 let cookie_header = create_session_cookie(&token, &ctx.config);
110 Ok(AuthResponse::json(200, &response)?.with_header("Set-Cookie", cookie_header))
111 }
112 PasskeyHandlerOutcome::Response(response) => Ok(response),
113 }
114 }
115
116 async fn handle_list_user_passkeys(
118 &self,
119 req: &AuthRequest,
120 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
121 ) -> AuthResult<AuthResponse> {
122 let (user, _session) = ctx.require_session(req).await?;
123 let result = list_user_passkeys_core(&user, ctx).await?;
124 AuthResponse::json(200, &result).map_err(AuthError::from)
125 }
126
127 async fn handle_delete_passkey(
129 &self,
130 req: &AuthRequest,
131 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
132 ) -> AuthResult<AuthResponse> {
133 let (user, _session) = ctx.require_session(req).await?;
134 let body: DeletePasskeyRequest = match better_auth_core::validate_request_body(req) {
135 Ok(v) => v,
136 Err(resp) => return Ok(resp),
137 };
138 let result = delete_passkey_core(&body, &user, ctx).await?;
139 AuthResponse::json(200, &result).map_err(AuthError::from)
140 }
141
142 async fn handle_update_passkey(
144 &self,
145 req: &AuthRequest,
146 ctx: &AuthContext<impl better_auth_core::AuthSchema>,
147 ) -> AuthResult<AuthResponse> {
148 let (user, _session) = ctx.require_session(req).await?;
149 let body: UpdatePasskeyRequest = match better_auth_core::validate_request_body(req) {
150 Ok(v) => v,
151 Err(resp) => return Ok(resp),
152 };
153 let result = update_passkey_core(&body, &user, ctx).await?;
154 AuthResponse::json(200, &result).map_err(AuthError::from)
155 }
156}
157
158better_auth_core::impl_auth_plugin! {
159 PasskeyPlugin, "passkey";
160 routes {
161 get "/passkey/generate-register-options" => handle_generate_register_options, "passkey_generate_register_options";
162 post "/passkey/verify-registration" => handle_verify_registration, "passkey_verify_registration";
163 get "/passkey/generate-authenticate-options" => handle_generate_authenticate_options, "passkey_generate_authenticate_options";
164 post "/passkey/verify-authentication" => handle_verify_authentication, "passkey_verify_authentication";
165 get "/passkey/list-user-passkeys" => handle_list_user_passkeys, "passkey_list_user_passkeys";
166 post "/passkey/delete-passkey" => handle_delete_passkey, "passkey_delete_passkey";
167 post "/passkey/update-passkey" => handle_update_passkey, "passkey_update_passkey";
168 }
169}