Customization
Overview
Like the other SuperTokens authentication recipes, you can customize the WebAuthn flow through different configuration options and overrides.
The following page describes the options that you can change and the different scenarios enabled through customization.
Backend recipe configuration
import supertokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import WebAuthn from "supertokens-node/recipe/webauthn";
supertokens.init({
framework: "express",
supertokens: {
// https://try.supertokens.com is for demo purposes. Replace this with the address of your core instance (sign up on supertokens.com), or self host a core.
connectionURI: "https://try.supertokens.com",
// apiKey: <API_KEY(if configured)>,
},
appInfo: {
// learn more about this on https://supertokens.com/docs/references/backend-sdks/reference#sdk-configuration
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth",
},
recipeList: [
WebAuthn.init({
getOrigin: async () => {
return "https://example.com";
},
getRelyingPartyId: async () => {
return "example.com";
},
getRelyingPartyName: async () => {
return "example";
},
}),
Session.init(), // initializes session features
],
});import (
"net/http"
"github.com/supertokens/supertokens-golang/recipe/session"
"github.com/supertokens/supertokens-golang/recipe/webauthn"
"github.com/supertokens/supertokens-golang/recipe/webauthn/webauthnmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
apiBasePath := "/auth"
websiteBasePath := "/auth"
err := supertokens.Init(supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{ConnectionURI: "https://try.supertokens.com"},
AppInfo: supertokens.AppInfo{
AppName: "<YOUR_APP_NAME>",
APIDomain: "<YOUR_API_DOMAIN>",
WebsiteDomain: "<YOUR_WEBSITE_DOMAIN>",
APIBasePath: &apiBasePath,
WebsiteBasePath: &websiteBasePath,
},
RecipeList: []supertokens.Recipe{
webauthn.Init(&webauthnmodels.TypeInput{
GetOrigin: func(tenantID string, req *http.Request, userContext supertokens.UserContext) (string, error) {
return "https://example.com", nil
},
GetRelyingPartyId: func(tenantID string, req *http.Request, userContext supertokens.UserContext) (string, error) {
return "example.com", nil
},
GetRelyingPartyName: func(tenantID string, userContext supertokens.UserContext) (string, error) {
return "example", nil
},
}),
session.Init(nil),
},
})
if err != nil {
panic(err)
}
}from typing import Optional
from supertokens_python import InputAppInfo, SupertokensConfig, init
from supertokens_python.framework import BaseRequest
from supertokens_python.recipe import session, webauthn
from supertokens_python.recipe.webauthn import WebauthnConfig
from supertokens_python.types.base import UserContext
async def get_origin(*, tenant_id: str, request: Optional[BaseRequest], user_context: UserContext):
return "https://example.com"
async def get_relying_party_id(*, tenant_id: str, request: Optional[BaseRequest], user_context: UserContext):
return "example.com"
async def get_relying_party_name(*, tenant_id: str, user_context: UserContext):
return "example"
init(
app_info=InputAppInfo(
app_name="<YOUR_APP_NAME>",
api_domain="<YOUR_API_DOMAIN>",
website_domain="<YOUR_WEBSITE_DOMAIN>",
api_base_path="/auth",
website_base_path="/auth"
),
supertokens_config=SupertokensConfig(
# https://try.supertokens.com is for demo purposes. Replace this with the address of your core instance (sign up on supertokens.com), or self host a core.
connection_uri="https://try.supertokens.com",
# api_key="<API_KEY(if configured)>"
),
framework='flask', # Replace this with the framework you are using
recipe_list=[
webauthn.init(
config=WebauthnConfig(
get_origin=get_origin,
get_relying_party_id=get_relying_party_id,
get_relying_party_name=get_relying_party_name,
)
),
session.init() # initializes session features
]
)The backend recipe accepts the following properties during initialization:
| Option | Description | Default |
|---|---|---|
getRelyingPartyId |
Sets the domain name associated with the WebAuthn credentials. This helps ensure that only your domain uses the credentials. | Hostname of appInfo.apiDomain |
getRelyingPartyName |
Sets a human-readable name for your application. The name appears to users during the WebAuthn registration process. | The appName value that you have set in appConfig |
getOrigin |
Configures the frontend origin that WebAuthn credentials bind to. | appInfo.getOrigin(...), normally the configured website origin |
emailDelivery |
Configures how the system builds and sends account-recovery emails. Read the email delivery page for more information. | Default email service |
validateEmailAddress |
Adds custom validation logic for email addresses. | Basic email format validation |
All the properties are optional.
The backend recipe accepts the following properties during initialization:
| Option | Description | Default |
|---|---|---|
get_relying_party_id |
Sets the domain name associated with the WebAuthn credentials. This helps ensure that only your domain uses the credentials. | Hostname of app_info.api_domain |
get_relying_party_name |
Sets a human-readable name for your application. The name appears to users during the WebAuthn registration process. | The app_name value that you have set in app_config |
get_origin |
Configures the frontend origin that WebAuthn credentials bind to. | app_info.get_origin(...), normally the configured website origin |
email_delivery |
Configures how the system builds and sends account-recovery emails. Read the email delivery page for more information. | Default email service |
validate_email_address |
Adds custom validation logic for email addresses. | Basic email format validation |
All the properties are optional.
The backend recipe accepts the following optional properties in webauthnmodels.TypeInput:
| Option | Description | Default |
|---|---|---|
GetRelyingPartyId |
Sets the domain name associated with the WebAuthn credentials. | Hostname of AppInfo.APIDomain |
GetRelyingPartyName |
Sets a human-readable name for your application. | AppInfo.AppName |
GetOrigin |
Configures the frontend origin that WebAuthn credentials bind to. | AppInfo.GetOrigin(...), normally the configured website origin |
EmailDelivery |
Configures how the system builds and sends account-recovery emails. | Default email service |
ValidateEmailAddress |
Adds custom validation logic for email addresses. | Basic email format validation |
The RP ID must equal the frontend origin’s host or be a registrable domain suffix of it. It must not include a scheme, port, or path. The origin must include the exact scheme and host, plus the port when it is non-default. For example, RP ID example.com is valid for origin https://login.example.com, but api.example.net is not. If your API and website use unrelated hosts, the default RP ID derived from the API domain is invalid for the website origin; configure both values explicitly.
Credential generation
The client generates the credentials based on the options provided by the backend SDK.
The frontend SDK uses navigator.credentials.create() to start the registration ceremony.
To change the options used to generate credentials, you need to override the registerOptions function.
import supertokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import WebAuthn from "supertokens-node/recipe/webauthn";
supertokens.init({
framework: "express",
supertokens: {
// https://try.supertokens.com is for demo purposes. Replace this with the address of your core instance (sign up on supertokens.com), or self host a core.
connectionURI: "https://try.supertokens.com",
// apiKey: <API_KEY(if configured)>,
},
appInfo: {
// learn more about this on https://supertokens.com/docs/references/backend-sdks/reference#sdk-configuration
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth",
},
recipeList: [
WebAuthn.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
registerOptions: (input) => {
return originalImplementation.registerOptions({
...input,
attestation: "direct",
residentKey: "required",
timeout: 10 * 1000,
userVerification: "required",
userPresence: true,
displayName: "John Doe",
supportedAlgorithmIds: [-257],
relyingPartyId: "example.com",
relyingPartyName: "example",
origin: "https://example.com",
});
},
};
},
},
}),
Session.init(), // initializes session features
],
});config := &webauthnmodels.TypeInput{
Override: &webauthnmodels.OverrideStruct{
Functions: func(original webauthnmodels.RecipeInterface) webauthnmodels.RecipeInterface {
originalRegisterOptions := *original.RegisterOptions
registerOptions := func(
email, recoverAccountToken, displayName *string,
relyingPartyID, relyingPartyName, origin string,
timeout *int, attestation *webauthnmodels.Attestation,
residentKey *webauthnmodels.ResidentKey,
userVerification *webauthnmodels.UserVerification,
userPresence *bool,
supportedAlgorithmIDs []webauthnmodels.COSEAlgorithmIdentifier,
tenantID string, userContext supertokens.UserContext,
) (webauthnmodels.RegisterOptionsResponse, error) {
customTimeout := 10 * 1000
customAttestation := webauthnmodels.AttestationDirect
customResidentKey := webauthnmodels.ResidentKeyRequired
customUserVerification := webauthnmodels.UserVerificationRequired
customUserPresence := true
return originalRegisterOptions(
email, recoverAccountToken, displayName,
"example.com", "example", "https://example.com",
&customTimeout, &customAttestation, &customResidentKey,
&customUserVerification, &customUserPresence,
[]webauthnmodels.COSEAlgorithmIdentifier{-257}, tenantID, userContext,
)
}
original.RegisterOptions = ®isterOptions
return original
},
},
}from typing import List, Optional, cast
from typing_extensions import Unpack
from supertokens_python import InputAppInfo, SupertokensConfig, init
from supertokens_python.recipe import session, webauthn
from supertokens_python.recipe.webauthn import (
RecipeInterface,
WebauthnConfig,
WebauthnOverrideConfig,
)
from supertokens_python.recipe.webauthn.interfaces.recipe import (
Attestation,
RegisterOptionsKwargsInput,
ResidentKey,
UserVerification,
)
from supertokens_python.types.base import UserContext
def override_webauthn_functions(original_implementation: RecipeInterface):
original_register_options = original_implementation.register_options
async def register_options(
*,
relying_party_id: str,
relying_party_name: str,
origin: str,
resident_key: Optional[ResidentKey] = None,
user_verification: Optional[UserVerification] = None,
user_presence: Optional[bool] = None,
attestation: Optional[Attestation] = None,
supported_algorithm_ids: Optional[List[int]] = None,
timeout: Optional[int] = None,
tenant_id: str,
user_context: UserContext,
**kwargs: Unpack[RegisterOptionsKwargsInput],
):
return await original_register_options(
relying_party_id="example.com",
relying_party_name="example",
origin="https://example.com",
resident_key="required",
user_verification="required",
user_presence=True,
attestation="direct",
supported_algorithm_ids=[-257],
timeout=10 * 1000,
tenant_id=tenant_id,
user_context=user_context,
email=cast(str, kwargs.get("email")),
recover_account_token=cast(str, kwargs.get("recover_account_token")),
display_name="John Doe",
)
original_implementation.register_options = register_options
return original_implementation
init(
app_info=InputAppInfo(
app_name="<YOUR_APP_NAME>",
api_domain="<YOUR_API_DOMAIN>",
website_domain="<YOUR_WEBSITE_DOMAIN>",
api_base_path="/auth",
website_base_path="/auth",
),
supertokens_config=SupertokensConfig(
# https://try.supertokens.com is for demo purposes. Replace this with the address of your core instance (sign up on supertokens.com), or self host a core.
connection_uri="https://try.supertokens.com",
# api_key="<API_KEY(if configured)>"
),
framework="flask", # Replace this with the framework you are using
recipe_list=[
webauthn.init(
config=WebauthnConfig(
override=WebauthnOverrideConfig(functions=override_webauthn_functions)
)
),
session.init(), # initializes session features
],
)Input properties
| Name | Type | Description | Default Value |
|---|---|---|---|
relyingPartyId |
string |
The domain name of your application that the system uses for validating the credential. | Uses getRelyingPartyId from the recipe configuration, which defaults to the hostname of appInfo.apiDomain |
relyingPartyName |
string |
The human-readable name of your application. | Uses getRelyingPartyName from the recipe configuration, which defaults to appName |
origin |
string |
The frontend origin where the credential is created. | Uses getOrigin from the recipe configuration, which normally defaults to the configured website origin |
timeout |
number |
The time in milliseconds that the user has to complete the credential generation process. | 60000 |
attestation |
"none" | "indirect" | "direct" | "enterprise" |
The attestation conveyance preference requested from the authenticator. | none |
supportedAlgorithmIds |
number[] |
The cryptographic algorithms that can generate credentials. Different authenticators support different algorithms. | [-8, -7, -257] |
residentKey |
"discouraged" | "preferred" | "required" |
Whether the authenticator creates a discoverable credential. A discoverable credential may be synced or device-bound. | required |
userVerification |
"discouraged" | "preferred" | "required" |
Whether user verification (like PIN or biometrics) is necessary. |
preferred |
userPresence |
boolean |
Whether the ceremony requires evidence of user interaction. This is separate from user verification. | true |
displayName |
string |
The display name of the user. | The user’s email property |
Input properties
| Name | Type | Description | Default Value |
|---|---|---|---|
relying_party_id |
str |
The domain name of your application that the system uses for validating the credential. | Uses get_relying_party_id from the recipe configuration, which defaults to the hostname of app_info.api_domain |
relying_party_name |
str |
The human-readable name of your application. | Uses get_relying_party_name from the recipe configuration which defaults to the app_name |
origin |
str |
The frontend origin where the credential is created. | Uses get_origin from the recipe configuration, which normally defaults to the configured website origin |
timeout |
int |
The time in milliseconds that the user has to complete the credential generation process. | 60000 |
attestation |
"none" | "indirect" | "direct" | "enterprise" |
The attestation conveyance preference requested from the authenticator. | none |
supported_algorithm_ids |
List[int] |
The cryptographic algorithms that can generate credentials. Different authenticators support different algorithms. | [-8, -7, -257] |
resident_key |
"discouraged" | "preferred" | "required" |
Whether the authenticator creates a discoverable credential. A discoverable credential may be synced or device-bound. | required |
user_verification |
"discouraged" | "preferred" | "required" |
Whether user verification (like PIN or biometrics) is necessary. |
preferred |
user_presence |
bool |
Whether the ceremony requires evidence of user interaction. This is separate from user verification. | True |
display_name |
str |
The display name of the user. | The user’s email property |
Input properties
| Name | Type | Description | Default Value |
|---|---|---|---|
relyingPartyId |
string |
The domain name used to validate the credential. | Uses GetRelyingPartyId, which defaults to the hostname of AppInfo.APIDomain |
relyingPartyName |
string |
The human-readable name of your application. | Uses GetRelyingPartyName, which defaults to AppInfo.AppName |
origin |
string |
The frontend origin where the credential is created. | Uses GetOrigin, which normally defaults to the configured website origin |
timeout |
*int |
The time in milliseconds available to complete credential creation. | 60000 |
attestation |
*webauthnmodels.Attestation |
The attestation conveyance preference requested from the authenticator. | AttestationNone |
supportedAlgorithmIds |
[]webauthnmodels.COSEAlgorithmIdentifier |
The allowed credential algorithms. | [-8, -7, -257] |
residentKey |
*webauthnmodels.ResidentKey |
Whether the authenticator creates a discoverable credential. | ResidentKeyRequired |
userVerification |
*webauthnmodels.UserVerification |
Whether user verification, such as a PIN or biometrics, is necessary. | UserVerificationPreferred |
userPresence |
*bool |
Whether the ceremony requires evidence of user interaction. | true |
displayName |
*string |
The display name of the user. | The user’s email |
Keep the default attestation: "none" unless your relying party has a specific attestation policy. "direct" can expose identifying authenticator information and still requires you to validate the attestation statement and its certificate chain against trust anchors you maintain. Requesting direct attestation does not by itself make an authenticator trusted.
Credential validation
When a user attempts to sign in, the authenticator uses their credential to sign an assertion on the client.
The frontend SDK uses navigator.credentials.get() to start the authentication ceremony.
The server generates the options for signing the challenge through the backend SDK, and then sends them to the client.
To change those, you need to override the signInOptions function.
import supertokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import WebAuthn from "supertokens-node/recipe/webauthn";
supertokens.init({
framework: "express",
supertokens: {
// https://try.supertokens.com is for demo purposes. Replace this with the address of your core instance (sign up on supertokens.com), or self host a core.
connectionURI: "https://try.supertokens.com",
// apiKey: <API_KEY(if configured)>,
},
appInfo: {
// learn more about this on https://supertokens.com/docs/references/backend-sdks/reference#sdk-configuration
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth",
},
recipeList: [
WebAuthn.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
signInOptions: (input) => {
return originalImplementation.signInOptions({
...input,
timeout: 10 * 1000,
userVerification: "required",
userPresence: true,
relyingPartyId: "example.com",
origin: "https://example.com",
});
},
};
},
},
}),
Session.init(), // initializes session features
],
});config := &webauthnmodels.TypeInput{
Override: &webauthnmodels.OverrideStruct{
Functions: func(original webauthnmodels.RecipeInterface) webauthnmodels.RecipeInterface {
originalSignInOptions := *original.SignInOptions
signInOptions := func(
relyingPartyID, relyingPartyName, origin string,
timeout *int,
userVerification *webauthnmodels.UserVerification,
userPresence *bool,
tenantID string, userContext supertokens.UserContext,
) (webauthnmodels.SignInOptionsResponse, error) {
customTimeout := 10 * 1000
customUserVerification := webauthnmodels.UserVerificationRequired
customUserPresence := true
return originalSignInOptions(
"example.com", "example", "https://example.com",
&customTimeout, &customUserVerification, &customUserPresence,
tenantID, userContext,
)
}
original.SignInOptions = &signInOptions
return original
},
},
}from typing import Any
from supertokens_python import InputAppInfo, SupertokensConfig, init
from supertokens_python.recipe import session, webauthn
from supertokens_python.recipe.webauthn import (
RecipeInterface,
WebauthnConfig,
WebauthnOverrideConfig,
)
from supertokens_python.types.base import UserContext
def override_webauthn_functions(original_implementation: RecipeInterface):
original_sign_in_options = original_implementation.sign_in_options
async def sign_in_options(
*,
tenant_id: str,
user_context: UserContext,
**kwargs: Any
):
return await original_sign_in_options(
tenant_id=tenant_id,
user_context=user_context,
timeout=10 * 1000,
user_verification="required",
user_presence=True,
relying_party_id='example.com',
relying_party_name='Example',
origin='https://example.com',
)
original_implementation.sign_in_options = sign_in_options
return original_implementation
init(
app_info=InputAppInfo(
app_name="<YOUR_APP_NAME>",
api_domain="<YOUR_API_DOMAIN>",
website_domain="<YOUR_WEBSITE_DOMAIN>",
api_base_path="/auth",
website_base_path="/auth"
),
supertokens_config=SupertokensConfig(
# https://try.supertokens.com is for demo purposes. Replace this with the address of your core instance (sign up on supertokens.com), or self host a core.
connection_uri="https://try.supertokens.com",
# api_key="<API_KEY(if configured)>"
),
framework='flask', # Replace this with the framework you are using
recipe_list=[
webauthn.init(
config=WebauthnConfig(
override=WebauthnOverrideConfig(
functions=override_webauthn_functions
)
)
),
session.init() # initializes session features
]
)Input properties
| Name | Type | Description | Default |
|---|---|---|---|
relyingPartyId |
string |
The domain name of your application that the system uses for validating the credential. | Uses getRelyingPartyId from the recipe configuration, which defaults to the hostname of appInfo.apiDomain |
relyingPartyName |
string |
The human-readable name of your application. | Uses getRelyingPartyName from the recipe configuration, which defaults to appName |
origin |
string |
The expected frontend origin for the authentication response. | Uses getOrigin from the recipe configuration, which normally defaults to the configured website origin |
timeout |
number |
The time in milliseconds that the user has to complete the credential validation process. | 60000 |
userVerification |
"discouraged" | "preferred" | "required" |
The parameter controls whether user verification (like PIN or biometrics) is necessary. |
preferred |
userPresence |
boolean |
Whether the ceremony requires evidence of user interaction. This is separate from user verification. | true |
Input properties
| Name | Type | Description | Default |
|---|---|---|---|
relying_party_id |
str |
The domain name of your application that the system uses for validating the credential. | Uses get_relying_party_id from the recipe configuration, which defaults to the hostname of app_info.api_domain |
relying_party_name |
str |
The human-readable name of your application. | Uses get_relying_party_name from the recipe configuration which defaults to the app_name |
origin |
str |
The expected frontend origin for the authentication response. | Uses get_origin from the recipe configuration, which normally defaults to the configured website origin |
timeout |
int |
The time in milliseconds that the user has to complete the credential validation process. | 60000 |
user_verification |
"discouraged" | "preferred" | "required" |
The parameter controls whether user verification (like PIN or biometrics) is necessary. |
preferred |
user_presence |
bool |
Whether the ceremony requires evidence of user interaction. This is separate from user verification. | True |
Input properties
| Name | Type | Description | Default |
|---|---|---|---|
relyingPartyId |
string |
The domain name used to validate the credential. | Uses GetRelyingPartyId, which defaults to the hostname of AppInfo.APIDomain |
relyingPartyName |
string |
The human-readable name of your application. | Uses GetRelyingPartyName, which defaults to AppInfo.AppName |
origin |
string |
The expected frontend origin for the authentication response. | Uses GetOrigin, which normally defaults to the configured website origin |
timeout |
*int |
The time in milliseconds available to complete authentication. | 60000 |
userVerification |
*webauthnmodels.UserVerification |
Whether user verification, such as a PIN or biometrics, is necessary. | UserVerificationPreferred |
userPresence |
*bool |
Whether the ceremony requires evidence of user interaction. | true |