SDK integration guide
Configure the SuperTokens Rownd backend plugin and frontend SDKs to migrate users, create SuperTokens sessions, and keep using Rownd-style APIs.
Overview
This tutorial configures the backend and client SDKs used during the Rownd migration. By the end, your backend exposes Rownd-compatible plugin routes, your frontend or mobile app uses the SuperTokens Rownd-compatible Hub, and OAuth/OIDC clients can be migrated if your Rownd app uses them.
Before you start
These instructions assume that you already have created an account in the SuperTokens dashboard and have deployed a SuperTokens Core service. After you have done that, enable the Account Linking feature in the created environment and copy the core connection information.
The Rownd compatibility plugin is only available with NodeJS or Python at the moment. If your main backend uses another language or unsupported framework, deploy the NodeJS or Python backend as an authentication sidecar and route Rownd/SuperTokens auth traffic to it. Read the complete guide for more information on how to set it up.
Steps
1. Configure the backend SDK
1.1 Install the SuperTokens SDK and Rownd plugin
Install the base SuperTokens backend SDK together with the Rownd migration plugin. The SuperTokens SDK adds the auth middleware, recipe APIs, and session handling. The Rownd plugin adds the Rownd-compatible migration, Hub, profile, and OAuth compatibility routes.
npm install supertokens-node @supertokens-plugins/rownd-nodejs
1.2 Initialize SuperTokens
Initialize the recipes that map to your Rownd auth methods, then add the Rownd plugin under experimental.plugins.
Contact the SuperTokens team before finalizing this setup for a complete plugin appConfig object based on your existing Rownd configuration.
The setup has four parts:
- supertokens: connects the backend SDK to SuperTokens Core.
- appInfo: defines the public API and website domains used by SuperTokens and the Rownd Hub.
- recipeList: enables the SuperTokens recipes used to replace Rownd auth behavior.
- experimental.plugins: mounts the Rownd migration plugin routes under apiBasePath.
import SuperTokens from "supertokens-node";
import AccountLinking from "supertokens-node/recipe/accountlinking";
import EmailVerification from "supertokens-node/recipe/emailverification";
import OAuth2Provider from "supertokens-node/recipe/oauth2provider";
import Passwordless from "supertokens-node/recipe/passwordless";
import Session from "supertokens-node/recipe/session";
import ThirdParty from "supertokens-node/recipe/thirdparty";
import UserMetadata from "supertokens-node/recipe/usermetadata";
import RowndMigrationPlugin from "@supertokens-plugins/rownd-nodejs";
SuperTokens.init({
supertokens: {
connectionURI: process.env.SUPERTOKENS_CONNECTION_URI!,
apiKey: process.env.SUPERTOKENS_API_KEY,
},
appInfo: {
appName: "My App",
apiDomain: "<API_DOMAIN>",
websiteDomain: process.env.WEBSITE_DOMAIN!,
apiBasePath: "<API_BASE_PATH>",
},
recipeList: [
AccountLinking.init({}),
Session.init(),
OAuth2Provider.init(),
UserMetadata.init(),
Passwordless.init({
contactMethod: "EMAIL_OR_PHONE",
flowType: "MAGIC_LINK",
}),
EmailVerification.init({ mode: "OPTIONAL" }),
ThirdParty.init({
signInAndUpFeature: {
providers: [
{
config: {
thirdPartyId: "google",
clients: [
{
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
],
},
},
{
config: {
thirdPartyId: "apple",
clients: [
{
clientId: process.env.APPLE_CLIENT_ID!,
clientSecret: process.env.APPLE_CLIENT_SECRET!,
},
],
},
},
],
},
}),
],
experimental: {
plugins: [
RowndMigrationPlugin.init({
rowndAppKey: process.env.ROWND_APP_KEY!,
rowndAppSecret: process.env.ROWND_APP_SECRET!,
enableDebugLogs: process.env.ROWND_ENABLE_DEBUG_LOGS === "true",
clientDomains: {
browser: process.env.WEBSITE_DOMAIN!,
browser_local: "http://localhost:3000",
mobile: "https://my-app.rownd-hub.supertokens.com",
},
appConfig: {
id: process.env.ROWND_APP_KEY!,
name: "My App",
signInMethods: [
{ method: "email" },
{ method: "phone" },
{ method: "google", clientId: process.env.GOOGLE_CLIENT_ID },
{ method: "apple", clientId: process.env.APPLE_CLIENT_ID },
{ method: "anonymous", type: "guest", displayName: "Continue as guest" },
],
profile: {
accountInformation: {
methods: {
email: { enabled: true },
phone: { enabled: true },
google: { enabled: true },
apple: { enabled: true },
},
},
personalInformation: { enabled: true },
preferences: { enabled: true },
signOutButton: { enabled: true },
deleteAccountButton: { enabled: true },
},
},
}),
],
},
});
1.3 Add CORS and middleware
Install SuperTokens middleware after CORS handling.
- Add the middleware BEFORE all your routes.
- Add the cors middleware BEFORE the SuperTokens middleware as shown below.
import express from "express";
import cors from "cors";
import supertokens from "supertokens-node";
import { middleware } from "supertokens-node/framework/express";
const app = express();
app.use(
cors({
origin: process.env.WEBSITE_DOMAIN,
allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()],
credentials: true,
}),
);
// IMPORTANT: CORS should be before this line.
app.use(middleware());
// ...your API routes
1.4 Configure client domains
By default, magic links are constructed using the websiteDomain that you pass in the SDK configuration. To test locally or to route the user to a mobile deep linked domain you can use the clientDomains plugin option.
RowndMigrationPlugin.init({
rowndAppKey: process.env.ROWND_APP_KEY!,
rowndAppSecret: process.env.ROWND_APP_SECRET!,
clientDomains: {
browser: "https://app.example.com",
browser_local: "http://localhost:3000",
mobile: "https://my-app.rownd-hub.supertokens.com",
},
});
The client sends a clientDomain key, not a URL. The plugin looks up the key in clientDomains and rewrites links to that base URL.
If no explicit key is sent:
- Mobile Hub flows use clientDomains.mobile.
- Browser Hub flows use clientDomains.browser.
- If the selected key is missing, the plugin keeps the link on the Hub URL and only rewrites the path.
2. Configure the frontend SDK
After the backend plugin is deployed and reachable, configure each client application to use the SuperTokens Rownd-compatible Hub.
Every client needs the same values configured on the backend:
- appKey: the Rownd app key used by the backend plugin.
- apiDomain: the public backend origin that hosts SuperTokens and the Rownd plugin routes.
- apiBasePath: the SuperTokens API base path, for example <API_BASE_PATH>.
- clientDomain: optional key from the backend clientDomains map.
2.1 Install the React SDK
npm install @supertokens/rownd-react
2.2 Add the provider
Replace imports from @rownd/react with @supertokens/rownd-react, then add RowndProvider near the root of your application.
import React from "react";
import ReactDOM from "react-dom/client";
import { RowndProvider } from "@supertokens/rownd-react";
import { App } from "./App";
ReactDOM.createRoot(document.getElementById("root")!).render(
<RowndProvider
appKey="<ROWND_APP_KEY>"
clientDomain="browser_local"
supertokens={{
appInfo: {
appName: "My App",
apiDomain: "<API_DOMAIN>",
apiBasePath: "<API_BASE_PATH>",
},
}}
>
<App />
</RowndProvider>,
);
Do not manually include the Hub script in your HTML when using the React SDK. The provider injects the Hub script for you.
2.3 Use Rownd-compatible APIs
import { RequireSignIn, SignedIn, SignedOut, useRownd } from "@supertokens/rownd-react";
export function AuthControls() {
const { requestSignIn, signOut, user } = useRownd();
return (
<div>
<SignedOut>
<button onClick={() => requestSignIn({ method: "email" })}>Email</button>
<button onClick={() => requestSignIn({ method: "phone" })}>Phone</button>
<button onClick={() => requestSignIn({ method: "google" })}>Google</button>
<button onClick={() => requestSignIn({ method: "apple" })}>Apple</button>
<button onClick={() => requestSignIn({ method: "anonymous" })}>Guest</button>
</SignedOut>
<SignedIn>
<p>{user.data?.email || user.data?.phone_number || user.id}</p>
<button onClick={() => signOut()}>Sign out</button>
</SignedIn>
<RequireSignIn>
<p>Protected content</p>
</RequireSignIn>
</div>
);
}
requestSignIn() supports Rownd-style options such as identifier, auto_sign_in, init_data, post_login_redirect, include_user_data, redirect, intent, group_to_join, prevent_closing, method, and method_options.
3. Validate client flows
Test the following flows to validate your client integration:
- Existing users can authenticate
- User logins and sign ups are migrated to SuperTokens
- Existing Rownd sessions migrate without forcing users to sign in again.
- Deep links work as expected on mobile
4. Migrate OAuth/OIDC clients Optional
If your Rownd application acts as an OAuth/OIDC provider, update clients to use SuperTokens discovery and endpoints after the SuperTokens team migrates your Rownd OAuth clients into SuperTokens Core.
4.1 Replace the discovery URL
Replace the Rownd discovery URL:
https://api.rownd.io/oidc/{rowndAppId}/.well-known/openid-configuration
with your SuperTokens discovery URL:
<API_DOMAIN>/<API_BASE_PATH>/.well-known/openid-configuration
4.2 Replace hardcoded endpoints
If a client hardcodes endpoints, update them like this:
Replace <API_BASE_PATH> with your configured backend API base path.
4.3 Confirm client IDs and tokens
Continue using the OAuth credential client_id and client_secret that Rownd issued and the SuperTokens team migrated. Do not use the Rownd OIDC client configuration id as the OAuth client_id.
Existing Rownd-issued OAuth tokens are not SuperTokens-issued tokens. After cutover, users should complete a new authorization flow against SuperTokens unless a separate token migration path is explicitly enabled for your project.
4.4 Validate OAuth
Check discovery and JWKS:
curl <API_DOMAIN>/<API_BASE_PATH>/.well-known/openid-configuration
curl <API_DOMAIN>/<API_BASE_PATH>/jwt/jwks.json
Start an authorization-code flow:
<API_DOMAIN>/<API_BASE_PATH>/oauth/auth?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&response_type=code&scope=openid%20profile%20email%20phone%20offline_access
Exchange the code:
curl -X POST <API_DOMAIN>/<API_BASE_PATH>/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "CLIENT_ID:CLIENT_SECRET" \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE" \
-d "redirect_uri=REDIRECT_URI"
Fetch userinfo:
curl <API_DOMAIN>/<API_BASE_PATH>/oauth/userinfo \
-H "Authorization: Bearer ACCESS_TOKEN"