Guide for setting up a secure backend proxy with Turnkey authentication, focusing on JWT implementation and user data management.
When integrating Turnkey into an application with an existing authentication system, you’ll need to establish a secure communication pattern between your frontend, backend, and the Turnkey API. This guide explains how to implement a backend proxy pattern that leverages your existing user authentication while enabling Turnkey operations.
There are several benefits to proxying Turnkey requests through your own backend:
JSON Web Tokens (JWTs) provide a secure, stateless way to authenticate requests between your frontend and backend. Here’s how to implement a JWT-based flow with Turnkey:
The first step in integrating Turnkey with JWT authentication is handling user login and signup. Both processes follow similar patterns but differ in how they establish the user’s identity with Turnkey.
Before you can authenticate a user with Turnkey, you need their sub-organization ID. There are multiple ways to obtain this:
Database Lookup: Query your database using the user’s email or ID
Turnkey API Lookup (Alternative) If you don’t have the sub-organization ID stored alongside your user record, you can query Turnkey using the user’s email to find associated sub-organization IDs.
Handling API Lookup Results
The example above (const subOrgId = organizationIds[0];
) simply takes the first ID found. This approach might not be suitable for all applications.
subOrgId
via email lookup is only the first step and does not grant access. Your application must authenticate the user (e.g., via passkey) after the lookup. Only then should you verify if the authenticated user is authorized for that subOrgId
, typically by checking your user database. Blindly trusting the lookup result is insecure.organizationIds
is empty, it might indicate the user doesn’t have an existing sub-organization. Your application should handle this, potentially by initiating a signup flow or failing the login.JWT Cookie: Extract the user ID from an existing JWT in cookies (for returning users)
If a user has previously logged in, their JWT might be stored in a cookie. You can verify this token and extract the user ID to confirm their identity.
Client-side
Generate a signed Turnkey request
Send to Backend
Forward the signed request
Backend Verification
Process the request and issue JWT
For new users, the flow is similar but involves creating a new sub-organization:
Client-side
Collect user information
Backend Processing
Create sub-organization and issue JWT
Turnkey allows users to authenticate via email using a secure One-Time Passcode (OTP). Your backend initiates this by calling sendOtp
. Turnkey emails the code to the user. The user enters the code in the frontend, which sends it (along with identifiers) to your backend. Your backend then calls verifyOtp
. Upon successful Turnkey verification, your backend issues its own application JWT to manage the proxied session.
Note: This flow generally assumes the user and their associated Turnkey Sub-Organization already exist, as it’s primarily for authentication rather than initial signup.
Request OTP
The user provides their email. The frontend gets the Turnkey iframe’s public key (referred to as targetPublicKey
) and sends both to the backend. The backend finds the user’s sub-organization and asks Turnkey to send the OTP email by calling sendOtp
, passing the iframe’s public key as the targetPublicKey
parameter (used by Turnkey for credential encryption upon successful verification).
Verify OTP & Issue Backend JWT
The user submits the received OTP code. The frontend gets the iframe’s public key again (as targetPublicKey
for encryption) and sends the code, email, original otpId
, and targetPublicKey
to the backend. The backend asks Turnkey to verify the code using verifyOtp
. If successful, the backend generates and sets its own application session JWT cookie and returns the authSession
object from Turnkey to the frontend. The frontend then uses this authSession
to establish the Turnkey session within the iframe via loginWithSession
.
This provides a secure authentication flow using Turnkey’s Email OTP service, integrated with your backend’s JWT-based session management and Turnkey’s frontend iframe session management.
Once the user is logged in (via passkey, OTP, or other methods) and has a valid JWT, subsequent requests from the frontend to your backend should include this JWT.
Crucial Security Note on Proxied Requests (Read and Write):
When proxying requests to Turnkey through your backend, it’s critical to ensure that the authenticated user (identified by userId
from the JWT) is actually authorized to perform the requested action on the target Turnkey sub-organization (subOrgId
). This applies to both write operations (like signing transactions) and read operations (like fetching balances or activities).
Simply verifying the JWT authenticates the user, but it doesn’t authorize them for a specific sub-organization. Your backend must:
userId
.subOrgId
(s) associated with that userId
in your application’s database.subOrgId
from the incoming request against the one
associated with the authenticated user in your database.subOrgId
matches the one(s) associated with the authenticated user.Failure to perform this check, especially on read operations, could allow a user to potentially access information from sub-organizations they do not belong to.
Client-side: Include JWT in requests to your backend
Backend Verification: Your backend verifies the JWT and processes the request
When implementing JWT authentication:
Many applications need to store additional user data and associate it with their Turnkey activities. Here’s how to implement this:
Store a mapping between your application’s user IDs and their corresponding Turnkey organization IDs:
When a user first authenticates with Turnkey (either by creating a new sub-organization or linking to an existing one):
When processing a proxied Turnkey request:
Next.js Server Actions provide a convenient way to implement secure backend operations:
For standard Node.js applications using Express:
When implementing this pattern, keep these security considerations in mind:
Learn about advanced patterns like multi-signature setups requiring approvals from both the user and the backend.
Guide for setting up a secure backend proxy with Turnkey authentication, focusing on JWT implementation and user data management.
When integrating Turnkey into an application with an existing authentication system, you’ll need to establish a secure communication pattern between your frontend, backend, and the Turnkey API. This guide explains how to implement a backend proxy pattern that leverages your existing user authentication while enabling Turnkey operations.
There are several benefits to proxying Turnkey requests through your own backend:
JSON Web Tokens (JWTs) provide a secure, stateless way to authenticate requests between your frontend and backend. Here’s how to implement a JWT-based flow with Turnkey:
The first step in integrating Turnkey with JWT authentication is handling user login and signup. Both processes follow similar patterns but differ in how they establish the user’s identity with Turnkey.
Before you can authenticate a user with Turnkey, you need their sub-organization ID. There are multiple ways to obtain this:
Database Lookup: Query your database using the user’s email or ID
Turnkey API Lookup (Alternative) If you don’t have the sub-organization ID stored alongside your user record, you can query Turnkey using the user’s email to find associated sub-organization IDs.
Handling API Lookup Results
The example above (const subOrgId = organizationIds[0];
) simply takes the first ID found. This approach might not be suitable for all applications.
subOrgId
via email lookup is only the first step and does not grant access. Your application must authenticate the user (e.g., via passkey) after the lookup. Only then should you verify if the authenticated user is authorized for that subOrgId
, typically by checking your user database. Blindly trusting the lookup result is insecure.organizationIds
is empty, it might indicate the user doesn’t have an existing sub-organization. Your application should handle this, potentially by initiating a signup flow or failing the login.JWT Cookie: Extract the user ID from an existing JWT in cookies (for returning users)
If a user has previously logged in, their JWT might be stored in a cookie. You can verify this token and extract the user ID to confirm their identity.
Client-side
Generate a signed Turnkey request
Send to Backend
Forward the signed request
Backend Verification
Process the request and issue JWT
For new users, the flow is similar but involves creating a new sub-organization:
Client-side
Collect user information
Backend Processing
Create sub-organization and issue JWT
Turnkey allows users to authenticate via email using a secure One-Time Passcode (OTP). Your backend initiates this by calling sendOtp
. Turnkey emails the code to the user. The user enters the code in the frontend, which sends it (along with identifiers) to your backend. Your backend then calls verifyOtp
. Upon successful Turnkey verification, your backend issues its own application JWT to manage the proxied session.
Note: This flow generally assumes the user and their associated Turnkey Sub-Organization already exist, as it’s primarily for authentication rather than initial signup.
Request OTP
The user provides their email. The frontend gets the Turnkey iframe’s public key (referred to as targetPublicKey
) and sends both to the backend. The backend finds the user’s sub-organization and asks Turnkey to send the OTP email by calling sendOtp
, passing the iframe’s public key as the targetPublicKey
parameter (used by Turnkey for credential encryption upon successful verification).
Verify OTP & Issue Backend JWT
The user submits the received OTP code. The frontend gets the iframe’s public key again (as targetPublicKey
for encryption) and sends the code, email, original otpId
, and targetPublicKey
to the backend. The backend asks Turnkey to verify the code using verifyOtp
. If successful, the backend generates and sets its own application session JWT cookie and returns the authSession
object from Turnkey to the frontend. The frontend then uses this authSession
to establish the Turnkey session within the iframe via loginWithSession
.
This provides a secure authentication flow using Turnkey’s Email OTP service, integrated with your backend’s JWT-based session management and Turnkey’s frontend iframe session management.
Once the user is logged in (via passkey, OTP, or other methods) and has a valid JWT, subsequent requests from the frontend to your backend should include this JWT.
Crucial Security Note on Proxied Requests (Read and Write):
When proxying requests to Turnkey through your backend, it’s critical to ensure that the authenticated user (identified by userId
from the JWT) is actually authorized to perform the requested action on the target Turnkey sub-organization (subOrgId
). This applies to both write operations (like signing transactions) and read operations (like fetching balances or activities).
Simply verifying the JWT authenticates the user, but it doesn’t authorize them for a specific sub-organization. Your backend must:
userId
.subOrgId
(s) associated with that userId
in your application’s database.subOrgId
from the incoming request against the one
associated with the authenticated user in your database.subOrgId
matches the one(s) associated with the authenticated user.Failure to perform this check, especially on read operations, could allow a user to potentially access information from sub-organizations they do not belong to.
Client-side: Include JWT in requests to your backend
Backend Verification: Your backend verifies the JWT and processes the request
When implementing JWT authentication:
Many applications need to store additional user data and associate it with their Turnkey activities. Here’s how to implement this:
Store a mapping between your application’s user IDs and their corresponding Turnkey organization IDs:
When a user first authenticates with Turnkey (either by creating a new sub-organization or linking to an existing one):
When processing a proxied Turnkey request:
Next.js Server Actions provide a convenient way to implement secure backend operations:
For standard Node.js applications using Express:
When implementing this pattern, keep these security considerations in mind:
Learn about advanced patterns like multi-signature setups requiring approvals from both the user and the backend.