One-Time Password
- Uses a 6-9 digit or bech32 alphanumeric one-time password sent via email
- Simple, and familiar user experience
One-Time Password Sandbox Environment
To test OTP codes in our sandbox environment you can use the following:alphanumeric
must be set tofalse
otpLength
must be set to6
- Email: user@example.com
- OTP Code:
000000
- Sends an encrypted API key credential directly via email
- Alternative method for specific use cases
- More secure, but requires copying the full credential to the client
Core mechanism
Email Authentication is built with expiring API keys as the foundation. The two delivery mechanisms are:OTP-based method
The authentication process happens in two steps:1
A 6-9 digit or alphanumeric OTP code is sent to the user’s verified email
address
2
Upon verification of the correct code, an API key credential is generated
and encrypted for the client
Credential bundle method
Note: on web, this method is only supported with the legacy iframe-based flow, if no hard requirement to use encrypted bundles, we suggest to use the IndexedDB-based OTP flow instead. The API key credential is encrypted and delivered directly through email to the user. Once the credential is live on the client side (within the context of an iframe), it is readily available to stamp (authenticate) requests. See the enclave to end-user secure channel for more info on how we achieve secure delivery.Prerequisites
Make sure you have set up your primary Turnkey organization with at least one API user that can programmatically initiate email auth and create suborganizations. Check out our Quickstart guide if you need help getting started. To allow an API user to initiate email auth, you’ll need the following policy in your main organization:User experience
OTP-based authentication flow
The flow begins with a new activity of typeACTIVITY_TYPE_INIT_OTP
using the parent organization id with these parameters:
otpType
: specify"OTP_TYPE_EMAIL"
contact
: user’s email addressemailCustomization
: optional parameters for customizing emailsuserIdentifier
: optional parameter for rate limiting SMS OTP requests per user. We recommend generating this server-side based on the user’s IP address or public key. See the OTP Rate Limits section below for more details.alphanumeric
: optional parameter for making this code bech32 alphanumeric or not. default: trueotpLength
: optional parameter for selecting the length of the OTP. default: 9expirationSeconds
: optional validity window (defaults to 5 minutes)sendFromEmailAddress
: optional custom email address from which to send the OTP emailsendFromEmailSenderName
: optional custom sender name for use with sendFromEmailAddress; if left empty, will default to ‘Notifications’replyToEmailAddress
: optional custom email address to use as reply-to
ACTIVITY_TYPE_VERIFY_OTP
using the parent organization id which returns a verificationToken JWT:
otpId
: ID from the init activityotpCode
: the 6-9 digit or alphanumeric code received via emailexpirationSeconds
: optional validity window (defaults to 1 hour)
ACTIVITY_TYPE_OTP_LOGIN
using the sub orgazanition ID associated with the contact from the first step:
publicKey
: public key to add to organization data associated with the signing key in IndexedDB or SecureStorage.verificationToken
: JWT returned from successfullVERIFY_OTP
activityexpirationSeconds
: optional validity window (defaults to 15 minutes)invalidateExisting
: optional boolean to invalidate previous login sessions

OTP rate limits
In order to safeguard users, Turnkey enforces rate limits for OTP auth activities. If auserIdentifier
parameter is provided, the following limits are enforced:
- 3 requests per 3 minutes per unique
userIdentifier
- 3 retries max per code, after which point that code will be locked
- 3 active codes per user, each with a 5 minute TTL
Credential bundle authentication flow
This alternative method usesACTIVITY_TYPE_EMAIL_AUTH
with these parameters:
email
: user’s email address (must match their registered email)targetPublicKey
: public key for credential encryptionapiKeyName
: optional name (defaults toEmail Auth - <Timestamp>
)expirationSeconds
: optional validity window (defaults to 15 minutes)emailCustomization
: optional parameters for customizing emailsinvalidateExisting
: optional boolean to invalidate previous Email Auth API keys

Email customization
We offer customization for the following:appName
: the name of the application. This will be used in the email’s subject, e.g.Sign in to ${appName}
logoUrl
: a link to a PNG with a max width of 340px and max height of 124pxmagicLinkTemplate
: a template for the URL to be used in the magic link button, e.g.https://dapp.xyz/%s
. The auth bundle will be interpolated into the%s
Email templates
We also support custom HTML email templates for Enterprise clients on the Scale tier. This allows you to inject arbitrary data from a JSON string containing key-value pairs. In this case, theemailCustomization
variable may look like:
alice and bob
can be interpolated into the email template using the key username
. The use of such template variables is purely optional.
Here’s an example of a custom HTML email containing an email auth bundle:

Bespoke HTML templates and custom email sender domains are available to Enterprise clients on the Scale tier or higher.
Custom email sender domain
Enterprise clients can also customize the email sender domain. To get set up, please reach out to your Turnkey rep to get started but here is what you’ll be able to configure:- Email has to be from a pre-whitelisted domain
- If there is no
sendFromEmailAddress
or it’s invalid, the other two fields are ignored - If
sendFromEmailSenderName
is absent, it defaults to “Notifications” (again, ONLY ifsendFromEmailAddress
is present and valid) - If
replyToEmailAddress
is absent, then there is no reply-to added. If it is present, it must ALSO be from a valid, whitelisted domain, but it doesn’t have to be the same email address as thesendFromEmailAddress
one (though once again, this first one MUST be present, or the other two feature are ignored)
Authorization
Authorization is managed through our policy engine:Authentication
Both OTP-based and credential bundle authentication activities:- Can be performed by root users and users with proper policy authorization
- Require the respective feature to be enabled in the organization and sub-organization
- Can target any user in the organization or sub-organizations
- For OTP-based auth:
ACTIVITY_TYPE_INIT_OTP
,ACTIVITY_TYPE_VERIFY_OTP
andACTIVITY_TYPE_OTP_LOGIN
- For credential bundle auth:
ACTIVITY_TYPE_EMAIL_AUTH

Example implementations
Implementation in organizations
For organizations accessed via dashboard:-
Ensure the required features are enabled:
FEATURE_NAME_OTP_EMAIL_AUTH
for OTP-based authenticationFEATURE_NAME_EMAIL_AUTH
for credential bundle authentication
- Users initiating the request must have appropriate permissions
Opting out
Organizations can disable email-based features if their security model requires it: UseACTIVITY_TYPE_REMOVE_ORGANIZATION_FEATURE
to disable:
FEATURE_NAME_OTP_EMAIL_AUTH
for OTP-based authenticationFEATURE_NAME_EMAIL_AUTH
for credential bundle authentication
disableOtpEmailAuth
parameter for OTP-based authenticationdisableEmailAuth
parameter for credential bundle authentication
Implementation notes
-
Users are limited to:
- 10 long-lived API keys
- 10 expiring API keys (oldest are discarded when limit is reached)
For top-level organizations
- Both authentication methods are disabled by default
- Must be enabled via
ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE
For sub-organizations
- Both authentication methods are enabled by default
- Can be disabled during creation using
CreateSubOrganizationIntentV7
activity parameters