Generally, these new users will take the form of a Turnkey Sub-Organization. This process involves using the following Turnkey SDK packages:
@turnkey/sdk-server: Used on the server-side to leverage the parent organization’s public/private API key pair to create the new user’s sub-organization.
@turnkey/sdk-browser: Used on the client-side to complete the email recovery process by adding an end-user passkey.
@turnkey/sdk-react: Used for Next.js applications to initialize the Turnkey SDK.
The process of creating a new sub-organization is split between client-side and server-side operations to prevent exposing the parent organization’s private API key.
For a refresher on the relationship between your application’s end users and Turnkey Sub-Organizations, see this page for more.
Wrap the root layout of your application with the TurnkeyProvider providing the required configuration options. This allows you to use the Turnkey client throughout your app via the useTurnkey() hook.
The NEXT_PUBLIC_ORGANIZATION_ID should be set to the parent organization ID which can be found in the Turnkey Dashboard.
The NEXT_PUBLIC_TURNKEY_RP_ID should be set to your application’s desired relying party ID; this is typically your domain, or localhost if developing locally. See this page for more details.
Wrap the root layout of your application with the TurnkeyProvider providing the required configuration options. This allows you to use the Turnkey client throughout your app via the useTurnkey() hook.
The NEXT_PUBLIC_ORGANIZATION_ID should be set to the parent organization ID which can be found in the Turnkey Dashboard.
The NEXT_PUBLIC_TURNKEY_RP_ID should be set to your application’s desired relying party ID; this is typically your domain, or localhost if developing locally. See this page for more details.
src/turnkey.ts
Copy
Ask AI
import { Turnkey } from "@turnkey/sdk-browser";// Initialize the Turnkey SDK with your organization ID and API base URLconst turnkeyBrowser = new Turnkey({ rpId: process.env.TURNKEY_RP_ID, apiBaseUrl: "https://api.turnkey.com", defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,});
The TURNKEY_ORGANIZATION_ID should be set to the parent organization ID which can be found in the Turnkey Dashboard.
The TURNKEY_RP_ID should be set to your application’s desired relying party ID; this is typically your domain, or localhost if developing locally. See this page for more details.
Next, we’ll initialize the passkeyClient, which will enable your application to interact with passkeys.
We add the "use client" directive to the Recovery component to as react hooks can only be used client-side.
app/create-suborg.tsx
Copy
Ask AI
"use client";import { useTurnkey } from "@turnkey/sdk-react";export default function CreateSubOrganization() { const { passkeyClient } = useTurnkey(); return <div>{/* ... rest of the code */}</div>;}
We add the "use client" directive to the Recovery component to as react hooks can only be used client-side.
app/create-suborg.tsx
Copy
Ask AI
"use client";import { useTurnkey } from "@turnkey/sdk-react";export default function CreateSubOrganization() { const { passkeyClient } = useTurnkey(); return <div>{/* ... rest of the code */}</div>;}
src/create-suborg.ts
Copy
Ask AI
import { Turnkey } from "@turnkey/sdk-browser";// Initialize the Turnkey SDK with your organization credentialsconst turnkey = new Turnkey({ rpId: process.env.TURNKEY_RP_ID, // Your relying party ID apiBaseUrl: process.env.TURNKEY_API_BASE_URL, // Turnkey API base URL defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID, // Your parent organization ID});// Initialize the Passkey Clientconst passkeyClient = turnkey.passkeyClient();// We'll add more functionality here in the following steps
In order to create a new passkey for a user, you can call the createUserPasskey SDK function. Calling this method will prompt the user to create a passkey, which will be securely stored by their browser. This credential will be associated with the user’s account (sub-organization) and used for future authentication. Once the credential is created, we’ll use it in the next step to create a new sub-organization that corresponds to the user.
The result of createUserPasskey includes an encoded challenge and attestation. The encoded challenge ensures the request is fresh and legitimate, while the attestation verifies the authenticity of the device creating the credential. For more information on how passkeys work, including details on the challenge and attestation objects, you can refer to the Passkeys Documentation.
app/create-suborg.tsx
Copy
Ask AI
// ... previous codeexport default function CreateSubOrganization() { const { passkeyClient } = useTurnkey(); const createNewPasskey = async () => { const credential = await passkeyClient?.createUserPasskey({ publicKey: { // This is the name of the passkey that will be displayed to the user rp: { name: "Wallet Passkey", }, user: { // We can use the username as the name and display name name: "Default User Name", displayName: "Default User Name", }, }, }); // we'll use this credential in the next step to create a new sub-organization return credential; }; // ... rest of the code return (/* ... */);}
app/create-suborg.tsx
Copy
Ask AI
// ... previous codeexport default function CreateSubOrganization() { const { passkeyClient } = useTurnkey(); const createNewPasskey = async () => { const credential = await passkeyClient?.createUserPasskey({ publicKey: { // This is the name of the passkey that will be displayed to the user rp: { name: "Wallet Passkey", }, user: { // We can use the username as the name and display name name: "Default User Name", displayName: "Default User Name", }, }, }); // we'll use this credential in the next step to create a new sub-organization return credential; }; // ... rest of the code return (/* ... */);}
src/create-suborg.ts
Copy
Ask AI
// ... previous codeconst createNewPasskey = async () => { const credential = await passkeyClient?.createUserPasskey({ publicKey: { // This is the name of the passkey that will be displayed to the user rp: { name: "Wallet Passkey", }, user: { // We can use the username as the name and display name name: "Default User Name", displayName: "Default User Name", }, }, }); // we'll use this credential in the next step to create a new sub-organization return credential;};
Initialize the Turnkey SDK on the server-side using the @turnkey/sdk-server package. This allows you to use the parent organization’s public/private API key pair to create sub-organizations.
For Next.js, add the "use server" directive at the top of the file where you’re initializing the Turnkey server client. This will ensure that the function is executed on the server-side and will have access to the server-side environment variables e.g. your parent organization’s public/private API key pair. For more information on Next.js server actions, see the Next.js documentation on Server Actions and Mutations.
app/actions.ts
Copy
Ask AI
"use server";import { Turnkey } from "@turnkey/sdk-server";// Initialize the Turnkey Server Client on the server-sideconst turnkeyServer = new Turnkey({ apiBaseUrl: "https://api.turnkey.com", apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY, apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY, defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,}).apiClient();
For Next.js, add the "use server" directive at the top of the file where you’re initializing the Turnkey server client. This will ensure that the function is executed on the server-side and will have access to the server-side environment variables e.g. your parent organization’s public/private API key pair. For more information on Next.js server actions, see the Next.js documentation on Server Actions and Mutations.
app/actions.ts
Copy
Ask AI
"use server";import { Turnkey } from "@turnkey/sdk-server";// Initialize the Turnkey Server Client on the server-sideconst turnkeyServer = new Turnkey({ apiBaseUrl: "https://api.turnkey.com", apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY, apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY, defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,}).apiClient();
src/turnkey.ts
Copy
Ask AI
import { Turnkey } from "@turnkey/sdk-server";// Initialize the Turnkey Server Client on the server-sideconst turnkeyServer = new Turnkey({ apiBaseUrl: "https://api.turnkey.com", apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY, apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY, defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,}).apiClient();
Next we’ll create a new function called createSubOrganization that will be used to create a new sub-organization from the server-side. This method will be called from the client-side with the end-user’s details.
We export the createSubOrganization server action to be called from the client-side.
"use client";import { useState } from "react";import { useTurnkey } from "@turnkey/sdk-react";import { useForm } from "react-hook-form";// Import the createSubOrganization server actionimport { createSubOrganization } from "./actions";type TSubOrgFormData = {email: string;};type TAttestation = {credentialId: string;clientDataJson: string;attestationObject: string;transports: ( | "AUTHENTICATOR_TRANSPORT_BLE" | "AUTHENTICATOR_TRANSPORT_INTERNAL" | "AUTHENTICATOR_TRANSPORT_NFC" | "AUTHENTICATOR_TRANSPORT_USB" | "AUTHENTICATOR_TRANSPORT_HYBRID")[];};export default function CreateSubOrganization() {const { passkeyClient } = useTurnkey();// Use form handler for suborg creationconst { register: subOrgFormRegister, handleSubmit: subOrgFormSubmit } = useForm<TSubOrgFormData>();// Maintain stateconst [createSubOrganizationResponse, setCreateSubOrganizationResponse] = useState(null);const createNewPasskey = async () => { const credential = await passkeyClient?.createUserPasskey({ publicKey: { // This is the name of the passkey that will be displayed to the user rp: { name: "Wallet Passkey", }, user: { // We can use the username as the name and display name name: "Default User Name", displayName: "Default User Name", }, }, }); // we'll use this credential in the next step to create a new sub-organization return credential;};const createSubOrg = async (data: TSubOrgFormData) => { const { encodedChallenge: challenge, attestation } = await createNewPasskey(); const createSubOrganizationResponse = await createSubOrganization( data.email, challenge, attestation, ); setCreateSubOrganizationResponse(createSubOrganizationResponse);};return ( <div> {createSubOrganizationResponse ? ( <h2>You've created a sub-organization!</h2> ) : ( <form onSubmit={subOrgFormSubmit(createSubOrg)}> <label> Email <input {...subOrgFormRegister("email")} placeholder="User Email" /> </label> <input type="submit" value="Create new sub-organization" /> </form> )} </div>);}
1
Import the server action
app/create-suborg.tsx
Copy
Ask AI
import { createSubOrganization } from "./actions";
2
Call createSubOrganization with the end-user's details
"use client";import { useState } from "react";import { useTurnkey } from "@turnkey/sdk-react";import { useForm } from "react-hook-form";// Import the createSubOrganization server actionimport { createSubOrganization } from "./actions";type TSubOrgFormData = {email: string;};type TAttestation = {credentialId: string;clientDataJson: string;attestationObject: string;transports: ( | "AUTHENTICATOR_TRANSPORT_BLE" | "AUTHENTICATOR_TRANSPORT_INTERNAL" | "AUTHENTICATOR_TRANSPORT_NFC" | "AUTHENTICATOR_TRANSPORT_USB" | "AUTHENTICATOR_TRANSPORT_HYBRID")[];};export default function CreateSubOrganization() {const { passkeyClient } = useTurnkey();// Use form handler for suborg creationconst { register: subOrgFormRegister, handleSubmit: subOrgFormSubmit } = useForm<TSubOrgFormData>();// Maintain stateconst [createSubOrganizationResponse, setCreateSubOrganizationResponse] = useState(null);const createNewPasskey = async () => { const credential = await passkeyClient?.createUserPasskey({ publicKey: { // This is the name of the passkey that will be displayed to the user rp: { name: "Wallet Passkey", }, user: { // We can use the username as the name and display name name: "Default User Name", displayName: "Default User Name", }, }, }); // we'll use this credential in the next step to create a new sub-organization return credential;};const createSubOrg = async (data: TSubOrgFormData) => { const { encodedChallenge: challenge, attestation } = await createNewPasskey(); const createSubOrganizationResponse = await createSubOrganization( data.email, challenge, attestation, ); setCreateSubOrganizationResponse(createSubOrganizationResponse);};return ( <div> {createSubOrganizationResponse ? ( <h2>You've created a sub-organization!</h2> ) : ( <form onSubmit={subOrgFormSubmit(createSubOrg)}> <label> Email <input {...subOrgFormRegister("email")} placeholder="User Email" /> </label> <input type="submit" value="Create new sub-organization" /> </form> )} </div>);}
1
Import the server action
app/create-suborg.tsx
Copy
Ask AI
import { createSubOrganization } from "./turnkey-server";
2
Call createSubOrganization with the end-user's details
src/turnkey.ts
Copy
Ask AI
import { createSubOrganization } from "./turnkey-server";// ... rest of the codeconst createSubOrganizationResponse = await createSubOrganization( email, attestation, challenge,);