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 have already created an account in the SuperTokens SaaS Dashboard and have deployed a SuperTokens Core service. After you have done that, select the relevant Managed deployment, enable Account Linking from Features, and copy the core connection information from Overview.

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: [
{
// Browser/Hub Apple login uses the Apple Services ID.
clientType: "web",
clientId: process.env.APPLE_WEB_CLIENT_ID!,
clientSecret: process.env.APPLE_CLIENT_SECRET!,
},
{
// Native iOS Apple login returns authorization codes for the app bundle ID.
clientType: "ios",
clientId: process.env.APPLE_IOS_BUNDLE_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_WEB_CLIENT_ID,
// These map Rownd platforms to the SuperTokens Apple clients above.
webClientType: "web",
iosClientType: "ios",
},
{ 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.

1.5 Configure Apple login for iOS Optional

If your iOS app uses native Sign in with Apple, configure Apple as multiple SuperTokens clients. Browser and Hub Apple login use the Apple Services ID, but native iOS Apple login returns authorization codes for your app bundle ID. The iOS bundle ID must therefore be configured as a separate Apple client in the SuperTokens backend SDK.

The Rownd plugin maps the Apple sign-in method to those SuperTokens client types. The plugin setting is iosClientType, which becomes ios_client_type in the Rownd app config. The iOS SDK reads that value and sends it as clientType when it exchanges the Apple authorization code with /signinup.

ThirdParty.init({
signInAndUpFeature: {
providers: [
{
config: {
thirdPartyId: "apple",
clients: [
{
// Browser/Hub Apple login uses the Apple Services ID.
clientType: "web",
clientId: process.env.APPLE_WEB_CLIENT_ID!,
clientSecret: process.env.APPLE_CLIENT_SECRET!,
},
{
// Native iOS Apple login returns authorization codes for the app bundle ID.
clientType: "ios",
clientId: process.env.APPLE_IOS_BUNDLE_ID!,
clientSecret: process.env.APPLE_CLIENT_SECRET!,
},
],
},
},
],
},
});

RowndMigrationPlugin.init({
rowndAppKey: process.env.ROWND_APP_KEY!,
rowndAppSecret: process.env.ROWND_APP_SECRET!,
appConfig: {
signInMethods: [
{
method: "apple",
clientId: process.env.APPLE_WEB_CLIENT_ID,
// These map Rownd platforms to the SuperTokens Apple clients above.
webClientType: "web",
iosClientType: "ios",
},
],
},
});

If Android uses a separate Apple client, add another SuperTokens Apple client with clientType: "android" and set androidClientType: "android" on the Rownd Apple sign-in method.

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"