Skip to main content
Migration

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.

info

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.

important
  • 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:

Rownd endpointSuperTokens endpoint
/oidc/{appId}/.well-known/openid-configuration<API_BASE_PATH>/.well-known/openid-configuration
/oidc/{appId}/auth<API_BASE_PATH>/oauth/auth
/oidc/{appId}/token<API_BASE_PATH>/oauth/token
/oidc/{appId}/me<API_BASE_PATH>/oauth/userinfo
/oidc/{appId}/jwks<API_BASE_PATH>/jwt/jwks.json
/oidc/{appId}/token/introspection<API_BASE_PATH>/oauth/introspect
/oidc/{appId}/token/revocation<API_BASE_PATH>/oauth/revoke
/oidc/{appId}/session/end<API_BASE_PATH>/oauth/end_session

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"