File length: 19719 # Authentication - Passkeys - Quickstart Source: https://supertokens.com/docs/authentication/passkeys/initial-setup ## Overview This page shows you how to add the **Passkeys** authentication method to your project. The tutorial creates a login flow, rendered by either the **Prebuilt UI** components or by your own **Custom UI**. ## Before you start These instructions assume that you already have gone through the main [quickstart guide](/docs/quickstart/introduction). If you have skipped that page please follow the tutorial and return here once you're done. :::info SDK Support WebAuthn (Passkeys) authentication is available in the **Node.js SDK** and **Python SDK**. ::: ## Steps ### 1. Initialize the frontend SDK #### 1.1 Add the `WebAuthn` recipe in your main configuration file. ```tsx // highlight-next-line SuperTokens.init({ appInfo: { apiDomain: "...", websiteDomain: "...", appName: "...", }, recipeList: [ // highlight-start WebAuthn.init(), // highlight-end Session.init() ] }); ``` #### 1.2 Include the pre-built UI components in your application. In order for the **pre-built UI** to render inside your application, you have to specify which routes show the authentication components. The **React SDK** uses [**React Router**](https://reactrouter.com/en/main) under the hood to achieve this. Based on whether you already use this package or not in your project, there are two different ways of configuring the routes. ```tsx // highlight-next-line class App extends React.Component { render() { return ( {/*This renders the login UI on the route*/} // highlight-next-line {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [WebauthnPreBuiltUI])} {/*Your app routes*/} ); } } ``` :::important If you are using `useRoutes`, `createBrowserRouter` or have routes defined in a different file, you need to adjust the code sample. Please see [this issue](https://github.com/supertokens/supertokens-auth-react/issues/581#issuecomment-1246998493) for further details. ```tsx function AppRoutes() { const authRoutes = getSuperTokensRoutesForReactRouterDom( reactRouterDom, [/* Add your UI recipes here e.g. EmailPasswordPrebuiltUI, PasswordlessPrebuiltUI, ThirdPartyPrebuiltUI */] ); const routes = useRoutes([ ...authRoutes.map(route => route.props), // Include the rest of your app routes ]); return routes; } function App() { return ( ); } ``` ::: ```tsx // highlight-next-line class App extends React.Component { render() { // highlight-start if (canHandleRoute([WebauthnPreBuiltUI])) { // This renders the login UI on the route return getRoutingComponent([WebauthnPreBuiltUI]) } // highlight-end return ( {/*Your app*/} ); } } ``` Add the `WebAuthn` recipe in your `AuthComponent`. ```tsx title="/app/auth/auth.component.ts" @Component({ selector: "app-auth", template: '
', }) export class AuthComponent implements OnDestroy, AfterViewInit { constructor( private renderer: Renderer2, @Inject(DOCUMENT) private document: Document ) { } ngAfterViewInit() { this.loadScript('^{prebuiltUIVersion}'); } ngOnDestroy() { // Remove the script when the component is destroyed const script = this.document.getElementById('supertokens-script'); if (script) { script.remove(); } } private loadScript(src: string) { const script = this.renderer.createElement('script'); script.type = 'text/javascript'; script.src = src; script.id = 'supertokens-script'; script.onload = () => { supertokensUIInit({ appInfo: { appName: "", apiDomain: "", websiteDomain: "", apiBasePath: "", websiteBasePath: "", }, recipeList: [ // highlight-start supertokensUIWebAuthn.init(), // highlight-end supertokensUISession.init(), ], }); } this.renderer.appendChild(this.document.body, script); } } ```
Add the `WebAuthn` recipe in your `AuthView` file. ```tsx ```
Call the SDK init function at the start of your application. The invocation includes the [main configuration details](/docs/references/frontend-sdks/reference#sdk-configuration), as well as the **recipes** that you use in your setup. ```tsx SuperTokens.init({ appInfo: { apiDomain: "...", apiBasePath: "...", appName: "...", }, recipeList: [ Session.init(), WebAuthn.init(), ], }); ``` First, you need to add the recipe script tag. ```html ``` You can initialize the SDK. ```tsx supertokens.init({ appInfo: { apiDomain: "", apiBasePath: "", appName: "...", }, recipeList: [ supertokensSession.init(), supertokensWebAuthn.init(), ], }); ``` ```tsx SuperTokens.init({ apiDomain: "", apiBasePath: "", }); ``` ```kotlin void main() { SuperTokens.init( apiDomain: "", apiBasePath: "", ); } ``` ### 2. Add the passkeys UI #### 2.1 Add the sign up form Create a form in which the user can input their email address. When the user submits the form, call the `registerCredentialWithSignUp` method like in the next code snippet. Under the hood, the method communicates with the backend SDK to fetch the registration options. Once the backend responds, it uses the browser's APIs to begin the registration process. For a more detailed overview of the sign up flow check the [Important Concepts page](/docs/authentication/passkeys/important-concepts#signup). ```ts async function signUp(email: string) { try { let response = await registerCredentialWithSignUp({ email }); if ( response.status === "SIGN_UP_NOT_ALLOWED" || response.status === "INVALID_AUTHENTICATOR_ERROR" ) { // the reason string is a user friendly message // about what went wrong. It can also contain a support code which users // can tell you so you know why their sign in / up was not allowed. window.alert(response.reason) } else if ( response.status === "INVALID_EMAIL_ERROR" || response.status === "EMAIL_ALREADY_EXISTS_ERROR" ) { window.alert("Invalid email"); } else if ( response.status === "INVALID_CREDENTIALS_ERROR" || response.status === "OPTIONS_NOT_FOUND_ERROR" || response.status === "INVALID_OPTIONS_ERROR" || response.status === "INVALID_AUTHENTICATOR_ERROR" || response.status === "EMAIL_ALREADY_EXISTS_ERROR" || response.status === "AUTHENTICATOR_ALREADY_REGISTERED" || response.status === "FAILED_TO_REGISTER_USER" || response.status === "WEBAUTHN_NOT_SUPPORTED" ) { // These errors represent various issues with the authenticator, credential or the flow itself. // These should be handled individually by you. // The user should be informed that they should retry the sign up process or get in touch with you. window.alert("Please try again"); } else { // User signed up successfully. window.alert("You have been signed up successfully"); } } catch (err: any) { if (err.isSuperTokensGeneralError === true) { // this may be a custom error message sent from the API by you, // or if the input email / phone number is not valid. window.alert(err.message); } else { window.alert("Oops! Something went wrong."); } } } ``` ## Get the email address from the user Add a form where the user can input their email address. ## Fetch the registration options from the backend SDK When the user submits the form, call the `register options` API. Save the response to use it in the next step. ```bash curl --location --request POST '/webauthn/register/options' \ --header 'Content-Type: application/json; charset=utf-8' \ --data-raw '{ "email": "johndoe@gmail.com", "displayName": "John Doe" }' ``` :::caution The returned result matches the format required by the **browser's WebAuthn API**. You will have to map the properties to the correct format based on the requirements of your platform. ::: ## Register a new credential authenticator API Use the received options generate a new credential. The implementation will vary based on the platform you are using. - **React Native**: You can use the [`react-native-passkey`](https://github.com/f-23/react-native-passkey) library. - **iOS**: Use the [`Authentication Services`](https://developer.apple.com/documentation/authenticationservices) framework. - **Android**: Use the [`Android Credential Manager API`](https://developer.android.com/identity/sign-in/credential-manager). - **Flutter**: Use [platform channels](https://docs.flutter.dev/platform-integration/platform-channels#architecture) to access the native APIs. ## Call the sign up API Using the newly generate credential, call the sign up API to save the new authentication method. ```bash curl --location --request POST '/webauthn/signup' \ --header 'Content-Type: application/json; charset=utf-8' \ --data-raw '{ "webauthnGeneratedOptionsId": "opt_123...", "credential": { "id": "credential_id", "rawId": "raw_credential_id", "response": { "clientDataJSON": "base64_client_data_json", "attestationObject": "base64_attestation_object" }, "type": "public-key" }, "shouldTryLinkingWithSessionUser": true }' ``` #### 2.2 Add the login form Add a button that can trigger the sign in flow. This is all that you need in terms of UI. When the user clicks it, call the `authenticateCredentialWithSignIn` method to handle the whole process. The function uses the backend registration options to trigger the challenge signing action through the browser API. Then, it forwards the result to the backend for validation. For a more detailed overview of the login flow check the [Important Concepts page](/docs/authentication/passkeys/important-concepts#login). ```ts async function signIn(email: string) { try { let response = await authenticateCredentialWithSignIn(); if ( response.status === "SIGN_IN_NOT_ALLOWED" ) { // the reason string is a user friendly message // about what went wrong. It can also contain a support code which users // can tell you so you know why their sign in / up was not allowed. window.alert(response.reason) } else if (response.status === "WEBAUTHN_NOT_SUPPORTED") { // the user's browser does not support the WebAuthn standard window.alert("Login method not supported"); } else if ( response.status === "INVALID_CREDENTIALS_ERROR" || response.status === "FAILED_TO_AUTHENTICATE_USER" ) { // These errors represent various issues with the authenticator, credential or the flow itself. // These should be handled individually by you. // The user should be informed that they should retry the sign in process or get in touch with you. window.alert("Please try again"); } else { // User signed in successfully. window.alert("You have been signed in successfully"); } } catch (err: any) { if (err.isSuperTokensGeneralError === true) { // this may be a custom error message sent from the API by you, // or if the input email / phone number is not valid. window.alert(err.message); } else { window.alert("Oops! Something went wrong."); } } } ``` ## Add a button that can trigger the sign in flow ## Get the sign in options from the backend SDK When the user taps the sign in button, call the backend API to fetch the sign in options. ```bash curl --location --request POST '/webauthn/signin/options' \ --header 'Content-Type: application/json; charset=utf-8' ``` ## Use the authenticator to sign the challenge With the received options, invoke the authenticator to sign the challenge. The implementation will vary based on the platform you are using. ## Call the sign in API Send the signed challenge to the backend for validation. ```bash curl --location --request POST '/webauthn/signin' \ --header 'Content-Type: application/json; charset=utf-8' \ --data-raw '{ "webauthnGeneratedOptionsId": "opt_123...", "credential": { "id": "credential_id", "rawId": "raw_credential_id", "response": { "clientDataJSON": "base64_client_data_json", "attestationObject": "base64_attestation_object" }, "type": "public-key" }, "shouldTryLinkingWithSessionUser": true }' ``` ### 2. Initialize the backend SDK ### 3. Initialize the backend SDK Initialize the backend SDK and include the **WebAuthn** `recipe`. The init call includes [configuration details](/docs/references/backend-sdks/reference#sdk-configuration) for your app. It specifies how the backend connects to the **SuperTokens Core**, as well as the **Recipes** used in your setup. The recipe exposes the required endpoints that get accessed by the frontend code, and communicates with the **SuperTokens Core** to complete the authentication flow. You can [configure different aspects](/docs/authentication/passkeys/customization) of the recipe's behavior but, for the completion of this guide, use the default values. After you confirm that the flow works as expected you can explore more advanced customisations options. ```ts supertokens.init({ // Replace this with the framework you are using framework: "express", supertokens: { // We use try.supertokens for demo purposes. // At the end of the tutorial we will show you how to create // your own SuperTokens core instance and then update your config. connectionURI: "https://try.supertokens.io", // apiKey: }, appInfo: { appName: "", apiDomain: "", websiteDomain: "", apiBasePath: "/auth", websiteBasePath: "/auth", }, recipeList: [ WebAuthN.init(), Session.init() ] }); ``` :::caution At the moment there is no support for using passkeys authentication in the Go SDK. ::: ```python from supertokens_python import InputAppInfo, SupertokensConfig, init from supertokens_python.recipe import session, webauthn init( app_info=InputAppInfo( app_name="", api_domain="", website_domain="", api_base_path="/auth", website_base_path="/auth" ), supertokens_config=SupertokensConfig( # We use try.supertokens for demo purposes. # At the end of the tutorial we will show you how to create # your own SuperTokens core instance and then update your config. connection_uri="https://try.supertokens.io", # api_key="" ), framework='flask', # Replace this with the framework you are using recipe_list=[ webauthn.init(), session.init() ] ) ```