Set Up Social Login - SuperTokens Docs

Set Up Social Login

Integrate Google, Apple, and other OAuth providers with ThirdParty and Session recipes, callback routes, and prebuilt or custom UI.

Add social login providers to an existing application.

Copy promptCopied

Add SuperTokens social login to this existing application. Inspect the project stack and existing recipes, then ask which providers are required if they cannot be inferred. Configure the frontend and backend ThirdParty and Session recipes, provider client IDs, callback URLs, auth routes, and environment variables. Keep client secrets out of source control, preserve existing conventions, and validate successful login, denied consent, callback failures, and session creation.

Overview

This page shows you how to authenticate, using ThirdParty Providers, with SuperTokens. The tutorial creates a login flow, rendered by either the Prebuilt UI components or by your own Custom UI.

Steps

UI type

Pre-built UIDrop-in components that handle the flows for you.Custom UIBuild your own UI and call the SDK directly.

1. Initialize the frontend SDK

React

1.1 Add the ThirdParty recipe in your main configuration file.

React

import React from "react";

import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react";
import ThirdParty, { Github, Google, Facebook, Apple } from "supertokens-auth-react/recipe/thirdparty";
import Session from "supertokens-auth-react/recipe/session";

SuperTokens.init({
  appInfo: {
    // learn more about this on https://supertokens.com/docs/references/frontend-sdks/reference#sdk-configuration
    appName: "<YOUR_APP_NAME>",
    apiDomain: "<YOUR_API_DOMAIN>",
    websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
    apiBasePath: "/auth",
    websiteBasePath: "/auth",
  },
  recipeList: [\
    ThirdParty.init({\
      signInAndUpFeature: {\
        providers: [Github.init(), Google.init(), Facebook.init(), Apple.init()],\
      },\
    }),\
    Session.init(),\
  ],
});

React

1.2 Include the pre-built UI components in your application.

In order for the pre-built UI to render inside your application, you have to specify which routes show the authentication components. The React SDK uses React Router under the hood to achieve this. Based on whether you already use this package or not in your project, there are two different ways of configuring the routes.

React

Do you use react-router-dom?

With React Router▼

With React Router

import React from "react";
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";

import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui";
import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react";
import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui";
import * as reactRouterDom from "react-router-dom";

class App extends React.Component {
  render() {
    return (
      <SuperTokensWrapper>
        <BrowserRouter>
          <Routes>
            {/*This renders the login UI on the /auth route*/}
            {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [ThirdPartyPreBuiltUI])}
            {/*Your app routes*/}
          </Routes>
        </BrowserRouter>
      </SuperTokensWrapper>
    );
  }
}

Without React Router

import React from "react";
import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react";
import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui";
import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui";

class App extends React.Component {
  render() {
    if (canHandleRoute([ThirdPartyPreBuiltUI])) {
      // This renders the login UI on the /auth route
      return getRoutingComponent([ThirdPartyPreBuiltUI]);
    }

return <SuperTokensWrapper>{/*Your app*/}</SuperTokensWrapper>;
  }
}

React

With React Router

React

With React Router

import React from "react";

import { BrowserRouter, useRoutes } from "react-router-dom";
import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react";
import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui";
import * as reactRouterDom from "react-router-dom";

function AppRoutes() {
  const authRoutes = getSuperTokensRoutesForReactRouterDom(reactRouterDom, [\
    /* Add your UI recipes here e.g. EmailPasswordPrebuiltUI, PasswordlessPrebuiltUI, ThirdPartyPrebuiltUI */\
  ]);

const routes = useRoutes([\
    ...authRoutes.map((route) => route.props),\
    // Include the rest of your app routes\
  ]);

return routes;
}

function App() {
  return (
    <SuperTokensWrapper>
      <BrowserRouter>
        <AppRoutes />
      </BrowserRouter>
    </SuperTokensWrapper>
  );
}

React

With React Router

Change the button style

On the frontend, you can provide a button component to the in-built providers defining your own UI. The component you add is clickable by default.

React

import SuperTokens from "supertokens-auth-react";
import ThirdParty, { Google, Github, Facebook, Apple } from "supertokens-auth-react/recipe/thirdparty";
SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  recipeList: [\
    ThirdParty.init({\
      signInAndUpFeature: {\
        providers: [\
          Github.init({\
            buttonComponent: (props: { name: string }) => <div></div>,\
          }),\
          Google.init({\
            buttonComponent: (props: { name: string }) => <div></div>,\
          }),\
          Facebook.init({\
            buttonComponent: (props: { name: string }) => <div></div>,\
          }),\
          Apple.init({\
            buttonComponent: (props: { name: string }) => <div></div>,\
          }),\
        ],\
        // ...\
      },\
      // ...\
    }),\
    // ...\
  ],
});

2. Initialize the backend SDK

You have to initialize the Backend Software Development Kit (SDK) alongside the code that starts your server. The init call includes configuration details for your app. It specifies how the backend connects to the SuperTokens Core, as well as the Recipes used in your setup.

Node.jsPythonGo

Language

Node.js▼

import supertokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
import ThirdParty from "supertokens-node/recipe/thirdparty";

supertokens.init({
  // Replace this with the framework you are using
  framework: "express",
  supertokens: {
    // We use try.supertokens for demo purposes.
    // At the end of the tutorial we will show you how to create
    // your own SuperTokens core instance and then update your config.
    connectionURI: "https://try.supertokens.io",
    // apiKey: <YOUR_API_KEY>
  },
  appInfo: {
    // learn more about this on https://supertokens.com/docs/references/backend-sdks/reference#sdk-configuration
    appName: "<YOUR_APP_NAME>",
    apiDomain: "<YOUR_API_DOMAIN>",
    websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
    apiBasePath: "/auth",
    websiteBasePath: "/auth",
  },
  recipeList: [\
    ThirdParty.init({\
      /*TODO: See next step*/\
    }),\
    Session.init(),\
  ],
});
from supertokens_python import init, InputAppInfo, SupertokensConfig
from supertokens_python.recipe import thirdparty, session

init(
    app_info=InputAppInfo(
        app_name="<YOUR_APP_NAME>",
        api_domain="<YOUR_API_DOMAIN>",
        website_domain="<YOUR_WEBSITE_DOMAIN>",
        api_base_path="/auth",
        website_base_path="/auth"
    ),
    supertokens_config=SupertokensConfig(
        # We use try.supertokens for demo purposes.
        # At the end of the tutorial we will show you how to create
        # your own SuperTokens core instance and then update your config.
        connection_uri="https://try.supertokens.io",
        # api_key: <YOUR_API_KEY>
    ),
    framework='fastapi',
    recipe_list=[\
        session.init(), # initializes session features\
        thirdparty.init(\
           # TODO: See next step\
        )\
    ],
    mode='asgi' # use wsgi if you are running using gunicorn
)
import (
    "github.com/supertokens/supertokens-golang/recipe/session"
    "github.com/supertokens/supertokens-golang/recipe/thirdparty"
    "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
    "github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
  apiBasePath := "/auth"
  websiteBasePath := "/auth"
  err := supertokens.Init(supertokens.TypeInput{
    Supertokens: &supertokens.ConnectionInfo{
          // We use try.supertokens for demo purposes.
          // At the end of the tutorial we will show you how to create
          // your own SuperTokens core instance and then update your config.
          ConnectionURI: "https://try.supertokens.io",
          // APIKey: <YOUR_API_KEY>
    },
    AppInfo: supertokens.AppInfo{
            AppName: "<YOUR_APP_NAME>",
            APIDomain: "<YOUR_API_DOMAIN>",
            WebsiteDomain: "<YOUR_WEBSITE_DOMAIN>",
            APIBasePath: &apiBasePath,
            WebsiteBasePath: &websiteBasePath,
    },
    RecipeList: []supertokens.Recipe{
            thirdparty.Init(&tpmodels.TypeInput{/*TODO: See next step*/}),
      session.Init(nil), // initializes session features
    },
  })

if err != nil {
        panic(err.Error())
    }
}

3. Add the authentication providers

Populate the providers array with the third-party authentication providers that you want.

Node.jsGoPython

Language

Node.js▼

import SuperTokens from "supertokens-node";
import ThirdParty from "supertokens-node/recipe/thirdparty";

SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  recipeList: [\
    ThirdParty.init({\
      signInAndUpFeature: {\
        // Load these credentials from environment variables or a secret manager.\
        providers: [\
          {\
            config: {\
              thirdPartyId: "google",\
              clients: [\
                {\
                  clientId: "<GOOGLE_CLIENT_ID>",\
                  clientSecret: "<GOOGLE_CLIENT_SECRET>",\
                },\
              ],\
            },\
          },\
          {\
            config: {\
              thirdPartyId: "github",\
              clients: [\
                {\
                  clientId: "<GITHUB_CLIENT_ID>",\
                  clientSecret: "<GITHUB_CLIENT_SECRET>",\
                },\
              ],\
            },\
          },\
          {\
            config: {\
              thirdPartyId: "apple",\
              clients: [\
                {\
                  clientId: "<APPLE_CLIENT_ID>",\
                  additionalConfig: {\
                    keyId: "<APPLE_KEY_ID>",\
                    privateKey: "<APPLE_PRIVATE_KEY>",\
                    teamId: "<APPLE_TEAM_ID>",\
                  },\
                },\
              ],\
            },\
          },\
        ],\
      },\
    }),\
    // ...\
  ],
});
import (
    "github.com/supertokens/supertokens-golang/recipe/thirdparty"
    "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
)

func main() {
    // Inside supertokens.Init
    thirdparty.Init(&tpmodels.TypeInput{
        SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{
            Providers: []tpmodels.ProviderInput{
                // Load these credentials from environment variables or a secret manager.
                {
                    Config: tpmodels.ProviderConfig{
                        ThirdPartyId: "google",
                        Clients: []tpmodels.ProviderClientConfig{
                            {
                                ClientID: "<GOOGLE_CLIENT_ID>",
                                ClientSecret: "<GOOGLE_CLIENT_SECRET>",
                            },
                        },
                    },
                },
                {
                    Config: tpmodels.ProviderConfig{
                        ThirdPartyId: "github",
                        Clients: []tpmodels.ProviderClientConfig{
                            {
                                ClientID:     "<GITHUB_CLIENT_ID>",
                                ClientSecret: "<GITHUB_CLIENT_SECRET>",
                            },
                        },
                    },
                },
                {
                    Config: tpmodels.ProviderConfig{
                        ThirdPartyId: "apple",
                        Clients: []tpmodels.ProviderClientConfig{
                            {
                                ClientID: "<APPLE_CLIENT_ID>",
                                AdditionalConfig: map[string]interface{}{
                                    "keyId":      "<APPLE_KEY_ID>",
                                    "privateKey": "<APPLE_PRIVATE_KEY>",
                                    "teamId":     "<APPLE_TEAM_ID>",
                                },
                            },
                        },
                    },
                },
            },
        },
    })
}
from supertokens_python.recipe.thirdparty.provider import ProviderInput, ProviderConfig, ProviderClientConfig
from supertokens_python.recipe import thirdparty

# Inside init
thirdparty.init(
    sign_in_and_up_feature=thirdparty.SignInAndUpFeature(providers=[\
        # Load these credentials from environment variables or a secret manager.\
        ProviderInput(\
            config=ProviderConfig(\
                third_party_id="google",\
                clients=[\
                    ProviderClientConfig(\
                        client_id="<GOOGLE_CLIENT_ID>",\
                        client_secret="<GOOGLE_CLIENT_SECRET>",\
                    ),\
                ],\
            ),\
        ),\
        ProviderInput(\
            config=ProviderConfig(\
                third_party_id="github",\
                clients=[\
                    ProviderClientConfig(\
                        client_id="<GITHUB_CLIENT_ID>",\
                        client_secret="<GITHUB_CLIENT_SECRET>",\
                    )\
                ],\
            ),\
        ),\
        ProviderInput(\
            config=ProviderConfig(\
                third_party_id="apple",\
                clients=[\
                    ProviderClientConfig(\
                        client_id="<APPLE_CLIENT_ID>",\
                        additional_config={\
                            "keyId": "<APPLE_KEY_ID>",\
                            "privateKey": "<APPLE_PRIVATE_KEY>",\
                            "teamId": "<APPLE_TEAM_ID>"\
                        },\
                    ),\
                ],\
            ),\
        ),\
    ])
)

Set OAuth scopes

To add additional OAuth scopes when accessing your third-party provider, add them to the configuration when initializing the backend SDK.

For example, if you are using Google as your third-party provider, you can add an additional scope as follows:

Node.jsGoPython

Language

Node.js▼

import SuperTokens from "supertokens-node";
import ThirdParty from "supertokens-node/recipe/thirdparty";

SuperTokens.init({
  supertokens: {
    connectionURI: "...",
  },
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  recipeList: [\
    ThirdParty.init({\
      signInAndUpFeature: {\
        providers: [\
          {\
            config: {\
              thirdPartyId: "google",\
              clients: [\
                {\
                  clientId: "TODO: GOOGLE_CLIENT_ID",\
                  clientSecret: "TODO: GOOGLE_CLIENT_SECRET",\
                  scope: ["scope1", "scope2"],\
                },\
              ],\
            },\
          },\
        ],\
      },\
    }),\
  ],
});
import (
    "github.com/supertokens/supertokens-golang/recipe/thirdparty"
    "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
    "github.com/supertokens/supertokens-golang/supertokens"
)

func main() {
    supertokens.Init(supertokens.TypeInput{
        RecipeList: []supertokens.Recipe{
            thirdparty.Init(&tpmodels.TypeInput{
                SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{
                    Providers: []tpmodels.ProviderInput{
                        {
                            Config: tpmodels.ProviderConfig{
                                ThirdPartyId: "google",
                                Clients: []tpmodels.ProviderClientConfig{
                                    {
                                        ClientID:     "TODO: GOOGLE_CLIENT_ID",
                                        ClientSecret: "TODO: GOOGLE_CLIENT_SECRET",
                                        Scope: []string{
                                            "scope1", "scope2",
                                        },
                                    },
                                },
                            },
                        },
                    },
                },
            }),
        },
    })
}
from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import thirdparty
from supertokens_python.recipe.thirdparty import ProviderInput, ProviderConfig, ProviderClientConfig, SignInAndUpFeature

init(
    app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
    framework='...',
    recipe_list=[\
        thirdparty.init(\
\
            sign_in_and_up_feature=SignInAndUpFeature(\
                providers=[\
                    ProviderInput(\
                        config=ProviderConfig(\
                            third_party_id="google",\
                            clients=[\
                                ProviderClientConfig(\
                                    client_id="GOOGLE_CLIENT_ID",\
                                    client_secret="GOOGLE_CLIENT_SECRET",\
                                    scope=["scope1", "scope2"]\
                                ),\
                            ],\
                        ),\
                    ),\
                ]\
            )\
        )\
    ]
)

1. Initialize the frontend SDK

Call the SDK init function at the start of your application. The invocation includes the main configuration details, as well as the recipes that you are using in your setup.

Mobile

Android

Add the SuperTokens.init function call at the start of your application.

WebMobile

Platform

Web▼

import SuperTokens from "supertokens-web-js";
import Session from "supertokens-web-js/recipe/session";
import ThirdParty from "supertokens-web-js/recipe/thirdparty";

SuperTokens.init({
  appInfo: {
    apiDomain: "<YOUR_API_DOMAIN>",
    apiBasePath: "/auth",
    appName: "...",
  },
  recipeList: [ThirdParty.init(), Session.init()],
});

React Native

import SuperTokens from "supertokens-react-native";

SuperTokens.init({
  apiDomain: "<YOUR_API_DOMAIN>",
  apiBasePath: "/auth",
});

Android

import android.app.Application
import com.supertokens.session.SuperTokens

class MainApplication: Application() {
    override fun onCreate() {
        super.onCreate()

SuperTokens.Builder(this, "<YOUR_API_DOMAIN>")
            .apiBasePath("/auth")
            .build()
    }
}

iOS

import UIKit
import SuperTokensIOS

fileprivate class ApplicationDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        do {
            try SuperTokens.initialize(
                apiDomain: "<YOUR_API_DOMAIN>",
                apiBasePath: "/auth"
            )
        } catch SuperTokensError.initError(let message) {
            // TODO: Handle initialization error
        } catch {
            // Some other error
        }

return true
    }

}

Flutter

import 'package:supertokens_flutter/supertokens.dart';

void main() {
    SuperTokens.init(
        apiDomain: "<YOUR_API_DOMAIN>",
        apiBasePath: "/auth",
    );
}

2. Add the login UI

The ThirdParty flow involves creating a button for each configured provider so that the user can initiate login.

After the user clicks one of those buttons the actions that you need to take differ based on which type of authentication scenario you are using:

This option can either involve a Client Secret configured on the backend or rely on Proof Key for Code Exchange (PKCE) exchange. The difference between the two is that the first option uses a private secret, on the backend, to get the access token. Whereas the second one makes use of the Proof Key for Code Exchange (PKCE) flow to perform the token exchange. Regardless of which authentication type you are using, in the end, the access token fetches the user info and logs them in.

This option only applies to mobile/desktop apps. The frontend obtains the access token and then sends it to the backend. SuperTokens then fetches user info using the access token and logs them in.

Authorization Code

Web

Redirecting to a social/single sign-on provider

The first step is to fetch the URL on which the user authenticates. You can do this by querying the backend API exposed by SuperTokens (as shown below). The backend SDK automatically appends the right query params to the URL (like scope, client ID etc).

After getting the URL, redirect the user there. In the code below, an example of login with Google appears:

Mobile

Sign in with Apple example

React Native

Fetching the authorization code on the frontend

For React Native apps, set up the react-native-apple-authentication library. Follow its README, and request the email scope when your application uses email identity. Apple may return the user’s actual address or a private relay address, and the native credential may include it only on the first authorization.

Once the integration is complete, call appleAuth.performRequest on iOS or appleAuthAndroid.signIn on Android. Send the one-time authorization code to your backend as shown in the next step.

A full example of this is available in the example app.

If you use Expo, you can use the expo-apple-authentication library instead (note that this library only works on iOS).

Android

Fetching the authorization code on the frontend

iOS

Fetching the authorization code on the frontend

For iOS, use the native Sign in with Apple flow, then send the authorization code to SuperTokens. You can see a full example of this in the onAppleClicked function in the example app.

Flutter

Fetching the authorization code on the frontend

For Flutter, use the sign_in_with_apple package. Make sure to follow the prerequisite steps to get the package setup. After setup, use the snippet below to trigger the apple sign-in flow. You can see a full example of this in the loginWithApple function in the example app.

WebMobile

Platform

Web▼

import { getAuthorisationURLWithQueryParamsAndSetState } from "supertokens-web-js/recipe/thirdparty";

async function googleSignInClicked() {
  try {
    const authUrl = await getAuthorisationURLWithQueryParamsAndSetState({
      thirdPartyId: "google",

// This is where Google should redirect the user back after login or error.
      // Configure this URL on the Google provider dashboard as well.
      frontendRedirectURI: "https://<YOUR_WEBSITE_DOMAIN>/auth/callback/google",
    });

/*
        Example value of authUrl: https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&include_granted_scopes=true&response_type=code&client_id=<GOOGLE_CLIENT_ID>&state=5a489996a28cafc83ddff&redirect_uri=https%3A%2F%2Fsupertokens.io%2Fdev%2Foauth%2Fredirect-to-app&flowName=GeneralOAuthFlow
        */

// Redirect the user to Google for authentication.
    window.location.assign(authUrl);
  } catch (err: any) {
    if (err.isSuperTokensGeneralError === true) {
      // this may be a custom error message sent from the API by you.
      window.alert(err.message);
    } else {
      window.alert("Oops! Something went wrong.");
    }
  }
}

iOS

import UIKit
import AuthenticationServices

fileprivate class ViewController: UIViewController, ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate {
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return view.window!
    }

func loginWithApple() {
        let authorizationRequest = ASAuthorizationAppleIDProvider().createRequest()
        authorizationRequest.requestedScopes = [.email, .fullName]

let authorizationController = ASAuthorizationController(authorizationRequests: [authorizationRequest])

authorizationController.presentationContextProvider = self
        authorizationController.delegate = self
        authorizationController.performRequests()
    }

func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        guard let credential: ASAuthorizationAppleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential,
            let authorizationCode = credential.authorizationCode,
            let authorizationCodeString = String(data: authorizationCode, encoding: .utf8) else { return }

let email = credential.email
        let firstName = credential.fullName?.givenName
        let lastName = credential.fullName?.familyName

// Send the required authorization code and any profile values Apple returned to the backend.
        // Persist first-login profile values if your application needs them; Apple may omit them later.
    }
}

Flutter

import 'dart:convert';
import 'dart:io';

import 'package:http/http.dart' as http;
import 'package:sign_in_with_apple/sign_in_with_apple.dart';

Future<String> createAppleMobileTransaction() async {
  final response = await http.post(
    Uri.parse("<YOUR_API_DOMAIN>/apple-mobile-transactions"),
    headers: {
      "Authorization": "Bearer <YOUR_APP_SESSION_TOKEN>",
      "Content-Type": "application/json",
      "X-App-Installation-ID": "<YOUR_APP_INSTALLATION_ID>",
    },
    body: jsonEncode({
      "appType": "android",
      "clientType": "<APPLE_ANDROID_CLIENT_TYPE>",
    }),
  );
  if (response.statusCode != 201) {
    throw StateError("Could not create Apple login transaction");
  }

return jsonDecode(response.body)["transactionId"] as String;
}

void loginWithApple() async {
  try {
    String? transactionId;
    if (Platform.isAndroid) {
      transactionId = await createAppleMobileTransaction();
      // Keep a copy in memory until the callback returns.
    }

var credential = await SignInWithApple.getAppleIDCredential(
        scopes: [\
            AppleIDAuthorizationScopes.email,\
            AppleIDAuthorizationScopes.fullName,\
        ],
        state: transactionId,
        // Required for Android only
        webAuthenticationOptions: WebAuthenticationOptions(
            clientId: "<CLIENT_ID>",
            redirectUri: Uri.parse(
            "<API_DOMAIN>/<API_BASE_PATH>/callback/apple",
            ),
        ),
    );

String authorizationCode = credential.authorizationCode;
    String? idToken = credential.identityToken;
    String? email = credential.email;
    String? firstname = credential.givenName;
    String? lastName = credential.familyName;

if (transactionId != null && credential.state != transactionId) {
      throw StateError("Apple login transaction mismatch");
    }

// Send the user information and auth code to the backend. Refer to the next step.
  } catch (e) {
    // Sign in aborted or failed
  }
}

React Native

Android

Apple may return the user’s email and full name only the first time the user authorizes your app. Treat those fields as optional, but require the one-time authorization code. If your application needs the profile values, store them during the first successful login rather than requiring Apple to return them again.

Web

Handling the auth callback on your frontend

Once the third-party provider redirects your user back to your app, you need to consume the information to sign in the user. This requires you to:

Mobile

Additional steps for Android

For Android, a way for the web login flow to redirect back to the app is also needed. By default, the API provided by the backend SDKs redirects to the website domain you provide when initializing the SDK. The API can be overridden to redirect to the app instead. For example, if using the Node.js SDK:

Before starting authorization, have the mobile app request an Apple login transaction from an application endpoint on your backend. This is not a SuperTokens API. Generate at least 256 random bits, prefix the identifier with mobile., and store only the opaque identifier server-side with:

Return the identifier to the initiating app over HTTPS and pass it through the provider’s state field. The sign_in_with_apple API exposes state but no separate transaction field, so state transports this app-defined, namespaced transaction identifier. Do not treat SuperTokens’ web state as this mobile transaction. The app must also compare the returned identifier with the value it stored locally before sending the authorization code to /signinup. Generate the random portion with crypto.randomBytes(32) in Node.js, crypto/rand.Read in Go, or secrets.token_urlsafe(32) in Python. Never accept a callback or deep-link URI directly from the mobile request; select both from a server-side allowlist for the requested app and client type.

At the Apple callback, reject a missing state. Values in the mobile. namespace must be consumed with one atomic database operation that verifies every binding and expiry. Reject invalid, mismatched, expired, or previously consumed transactions; never fall back to the web handler for one of these failures. Non-mobile state values can be passed to the original SuperTokens web handler.

For example, the application transaction store can atomically consume a PostgreSQL row with:

DELETE FROM apple_login_transactions
WHERE id = $1
  AND app_type = $2
  AND client_type = $3
  AND expected_callback = $4
  AND expires_at > CURRENT_TIMESTAMP
RETURNING app_redirect_uri, initiating_context_hash;

Create id as a primary key and never reinsert an identifier. The delete and validation must be one database statement, not a read followed by a delete. The consumeAppleMobileTransaction functions referenced below are application code that execute this query and return the stored, allowlisted redirect URI; they are not SuperTokens SDK APIs. Apple’s provider POST does not contain the originating app’s local context. Bind that context when creating the row, redirect only to the stored target, and require the app to compare the returned transaction identifier with its locally stored value before continuing. For browser flows, keep using the SuperTokens web state and original callback handler.

Node.js

Flutter

In the snippet above for Android, you need an additional webAuthenticationOptions property when signing in with Apple. This is because on Android the library uses the web login flow and requires the client ID and redirection URI. The redirectUri property here is the URL to which Apple makes a POST request after the user has logged in. The SuperTokens backend SDKs provide an API for this at <API_DOMAIN>/<API_BASE_PATH>/callback/apple. Set the app-defined transaction identifier as the state argument to SignInWithApple.getAppleIDCredential before starting authorization.

WebMobile

import { signInAndUp } from "supertokens-web-js/recipe/thirdparty";

async function handleGoogleCallback() {
  try {
    const response = await signInAndUp();

if (response.status === "OK") {
      console.log(response.user);
      if (response.createdNewRecipeUser && response.user.loginMethods.length === 1) {
        // sign up successful
      } else {
        // sign in successful
      }
      window.location.assign("/home");
    } else if (response.status === "SIGN_IN_UP_NOT_ALLOWED") {
      // the reason string is a user friendly message
      // about what went wrong. It can also contain a support code which users
      // can tell you so you know why their sign in / up was not allowed.
      window.alert(response.reason);
    } else {
      // The provider did not supply the email identity required by this configuration.
      window.alert("No email provided by social login. Please use another form of login");
      window.location.assign("/auth"); // redirect back to login page
    }
  } catch (err: any) {
    if (err.isSuperTokensGeneralError === true) {
      // this may be a custom error message sent from the API by you.
      window.alert(err.message);
    } else {
      window.alert("Oops! Something went wrong.");
    }
  }
}
import ThirdParty from "supertokens-node/recipe/thirdparty";

declare function consumeAppleMobileTransaction(input: {
  id: string;
  appType: "android";
  clientType: string;
  expectedCallback: string;
}): Promise<{ appRedirectURI: string } | undefined>;

ThirdParty.init({
  override: {
    apis: (original) => {
      return {
        ...original,
        appleRedirectHandlerPOST: async (input) => {
          if (original.appleRedirectHandlerPOST === undefined) {
            throw Error("Should never come here");
          }

const transactionId = input.formPostInfoFromProvider.state;
          if (typeof transactionId !== "string" || transactionId.length === 0) {
            input.options.res.setStatusCode(400);
            input.options.res.sendHTMLResponse("Invalid Apple login transaction");
            return;
          }

if (!transactionId.startsWith("mobile.")) {
            return await original.appleRedirectHandlerPOST(input);
          }

const transaction = await consumeAppleMobileTransaction({
            id: transactionId,
            appType: "android",
            clientType: "<APPLE_ANDROID_CLIENT_TYPE>",
            expectedCallback: "<YOUR_API_DOMAIN>/auth/callback/apple",
          });

if (transaction === undefined) {
            input.options.res.setStatusCode(400);
            input.options.res.sendHTMLResponse("Invalid Apple login transaction");
            return;
          }

const query = new URLSearchParams();
          for (const [key, value] of Object.entries(input.formPostInfoFromProvider)) {
            query.set(key, `${value}`);
          }

const redirectUrl = `${transaction.appRedirectURI}?${query.toString()}#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end`;
          input.options.res.setHeader("Location", redirectUrl, false);
          input.options.res.setStatusCode(303);
          input.options.res.sendHTMLResponse("");
        },
      };
    },
  },
});

Web

Special case for login with Apple

Unlike other providers, Apple does not redirect your user back to your frontend app. Instead, it redirects the user to your backend with a FORM POST request. This means that the URL you configure on Apple’s dashboard should point to your backend API layer. Here, middleware handles the request and redirects the user to your frontend app. Your frontend app should then call the signInAndUp API on that page as shown previously.

To tell SuperTokens which frontend route to redirect the user back to, set the frontendRedirectURI to the frontend route. Also, set the redirectURIOnProviderDashboard to point to your backend API route, to which Apple sends a POST request.

Follow Apple’s official Configure Sign in with Apple for the web guide when creating the Services ID and registering the return URL.

Mobile

Go

Pass your transaction-consumption implementation to initThirdPartyWithAppleMobile, and include the returned recipe in your SuperTokens RecipeList. The helper must return an error for missing, expired, or mismatched transactions. Add your Apple provider configuration to the TypeInput below.

WebMobile

import { getAuthorisationURLWithQueryParamsAndSetState } from "supertokens-web-js/recipe/thirdparty";

async function appleSignInClicked() {
  try {
    const authUrl = await getAuthorisationURLWithQueryParamsAndSetState({
      thirdPartyId: "apple",

frontendRedirectURI: "https://<YOUR_WEBSITE_DOMAIN>/auth/callback/apple", // This is an example callback URL on your frontend. You can use another path as well.
      redirectURIOnProviderDashboard: "<YOUR_API_DOMAIN>/auth/callback/apple", // Configure this URL on the Apple developer dashboard.
    });

// Redirect the user to Apple for authentication.
    window.location.assign(authUrl);
  } catch (err: any) {
    if (err.isSuperTokensGeneralError === true) {
      // this may be a custom error message sent from the API by you.
      window.alert(err.message);
    } else {
      window.alert("Oops! Something went wrong.");
    }
  }
}
import (
    "net/http"
    "net/url"
    "strings"

"github.com/supertokens/supertokens-golang/recipe/thirdparty"
    "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
    "github.com/supertokens/supertokens-golang/supertokens"
)

type AppleMobileTransaction struct {
    AppRedirectURI string
}

func initThirdPartyWithAppleMobile(consumeAppleMobileTransaction func(transactionID, appType, clientType, expectedCallback string) (AppleMobileTransaction, error)) supertokens.Recipe {
    return thirdparty.Init(&tpmodels.TypeInput{
        Override: &tpmodels.OverrideStruct{
            APIs: func(originalImplementation tpmodels.APIInterface) tpmodels.APIInterface {
                originalAppleRedirectPost := *originalImplementation.AppleRedirectHandlerPOST

*originalImplementation.AppleRedirectHandlerPOST = func(formPostInfoFromProvider map[string]interface{}, options tpmodels.APIOptions, userContext *map[string]interface{}) error {
                    transactionID, ok := formPostInfoFromProvider["state"].(string)
                    if !ok || transactionID == "" {
                        http.Error(options.Res, "Invalid Apple login transaction", http.StatusBadRequest)
                        return nil
                    }

if !strings.HasPrefix(transactionID, "mobile.") {
                        return originalAppleRedirectPost(formPostInfoFromProvider, options, userContext)
                    }

transaction, err := consumeAppleMobileTransaction(
                        transactionID,
                        "android",
                        "<APPLE_ANDROID_CLIENT_TYPE>",
                        "<YOUR_API_DOMAIN>/auth/callback/apple",
                    )
                    if err != nil {
                        http.Error(options.Res, "Invalid Apple login transaction", http.StatusBadRequest)
                        return nil
                    }

queryParams := url.Values{}
                    for key, value := range formPostInfoFromProvider {
                        if stringValue, ok := value.(string); ok {
                            queryParams.Set(key, stringValue)
                        }
                    }

redirectURI := transaction.AppRedirectURI + "?" + queryParams.Encode() + "#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end"
                    options.Res.Header().Set("Location", redirectURI)
                    options.Res.WriteHeader(http.StatusSeeOther)
                    return nil
                }

return originalImplementation
            },
        },
    })
}

Web

Mobile

Python

Mobile

from dataclasses import dataclass
from typing import Any, Dict, Optional
from urllib.parse import urlencode

from supertokens_python.recipe import thirdparty
from supertokens_python.recipe.thirdparty.interfaces import APIInterface, APIOptions

@dataclass
class AppleMobileTransaction:
    app_redirect_uri: str

async def consume_apple_mobile_transaction(
    id: str,
    app_type: str,
    client_type: str,
    expected_callback: str,
) -> Optional[AppleMobileTransaction]:
    # Atomically consume and return the matching transaction from your database.
    raise NotImplementedError

def override_thirdparty_apis(original_implementation: APIInterface):
    original_apple_redirect_post = original_implementation.apple_redirect_handler_post

async def apple_redirect_handler_post(
        form_post_info: Dict[str, Any],
        api_options: APIOptions,
        user_context: Dict[str, Any]
    ):
        transaction_id = form_post_info.get("state")
        if not isinstance(transaction_id, str) or not transaction_id:
            api_options.response.set_status_code(400)
            api_options.response.set_html_content("Invalid Apple login transaction")
            return

if not transaction_id.startswith("mobile."):
            return await original_apple_redirect_post(form_post_info, api_options, user_context)

transaction = await consume_apple_mobile_transaction(
            id=transaction_id,
            app_type="android",
            client_type="<APPLE_ANDROID_CLIENT_TYPE>",
            expected_callback="<YOUR_API_DOMAIN>/auth/callback/apple",
        )
        if transaction is None:
            api_options.response.set_status_code(400)
            api_options.response.set_html_content("Invalid Apple login transaction")
            return

redirect_url = transaction.app_redirect_uri + "?" + urlencode(form_post_info) + "#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end"
        api_options.response.set_header("Location", redirect_url)
        api_options.response.set_status_code(303)
        api_options.response.set_html_content("")

original_implementation.apple_redirect_handler_post = apple_redirect_handler_post
    return original_implementation

thirdparty.init(
    override=thirdparty.InputOverrideConfig(
        apis=override_thirdparty_apis
    ),
)

Mobile

In the code above, the appleRedirectHandlerPOST API rejects missing state. The explicit mobile. namespace selects the mobile flow; absence of state never does. A namespaced value must match and atomically consume a bound transaction before the handler redirects to the allowlisted deep link. Any transaction failure returns 400 instead of falling back to the web flow. Other non-empty values remain SuperTokens web state and go to the original handler. Follow the sign_in_with_apple README to configure the Android deep link.

Calling the signinup API to consume the authorization code

Once you have the authorization code from the auth provider, you need to call the /signinup API exposed by the backend SDK as shown below:

Mobile

curl --location --request POST '<YOUR_API_DOMAIN>/auth/signinup' \
--header 'Content-Type: application/json; charset=utf-8' \
--data-raw '{
    "thirdPartyId": "apple",
    "clientType": "...",
    "redirectURIInfo": {
        "redirectURIOnProviderDashboard": "<YOUR_API_DOMAIN>/auth/callback/apple",
        "redirectURIQueryParams": {
            "code": "...",
            "user": {
                "name":{
                    "firstName":"...",
                    "lastName":"..."
                },
                "email":"..."
            }
        }
    }
}'

Mobile

The response body from the API call has a status property in it:

Sign in with Google example

React Native

Fetching the authorization code on the frontend

This involves setting up the @react-native-google-signin/google-signin in your app. See their README for steps on how to integrate their SDK into your application. The minimum scope required by SuperTokens is the one that gives the user’s email.

Once you configure the library, use GoogleSignin.configure and GoogleSignin.signIn to trigger the login flow and sign the user in with Google. Refer to the example app to see the full code for this.

Android

Fetching the authorization code on the frontend

Follow the official Google Sign In guide to set up their library and sign the user in with Google. Fetch the authorization code from the Google sign-in result. For a full example, refer to the signInWithGoogle function in the example app.

iOS

Fetching the authorization code on the frontend

For iOS, use the GoogleSignIn library. Follow the official guide to set up the library and sign the user in with Google. Use the result of Google sign-in to get the authorization code. For a full example, refer to the onGoogleCliked function in the example app.

Flutter

Fetching the authorization code on the frontend

For Flutter, use the google_sign_in package. Make sure to follow the prerequisite steps to get the package setup. After setup, use the snippet below to trigger the Google sign-in flow. For a full example, refer to the loginWithGoogle in the example app.

Mobile

React Native

import { GoogleSignin } from "@react-native-google-signin/google-signin";

export const performGoogleSignIn = async (): Promise<boolean> => {
  GoogleSignin.configure({
    webClientId: "GOOGLE_WEB_CLIENT_ID",
    iosClientId: "GOOGLE_IOS_CLIENT_ID",
  });

try {
    const response = await GoogleSignin.signIn({});
    const authCode = response.data?.serverAuthCode;

// Refer to step 2

return true;
  } catch (e) {
    console.log("Google sign in failed with error", e);
  }

return false;
};

Android

import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import android.content.Intent

class LoginActivity : AppCompatActivity() {
    private lateinit var googleResultLauncher: ActivityResultLauncher<Intent>

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

googleResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
            onGoogleResultReceived(it)
        }
    }

private fun signInWithGoogle() {
        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestServerAuthCode("GOOGLE_WEB_CLIENT_ID")
            .requestEmail()
            .build()

val googleClient = GoogleSignIn.getClient(this, gso)
        val signInIntent = googleClient.signInIntent

googleResultLauncher.launch(signInIntent)
    }

private fun onGoogleResultReceived(it: ActivityResult) {
        val task = GoogleSignIn.getSignedInAccountFromIntent(it.data)
        val account = task.result
        val authCode = account.serverAuthCode

// Refer to step 2
    }
}

iOS

import UIKit
import GoogleSignIn

fileprivate class LoginScreenViewController: UIViewController {
    @IBAction func onGoogleCliked() {
        GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in
            guard error == nil else { return }

guard let authCode: String = signInResult?.serverAuthCode as? String else {
                print("Google login did not return an authorization code")
                return
            }

// Refer to step 2
        }
    }
}

Flutter

import 'package:google_sign_in/google_sign_in.dart';
import 'dart:io';

Future<void> loginWithGoogle() async {
    GoogleSignIn googleSignIn;

if (Platform.isAndroid) {
      googleSignIn = GoogleSignIn(
        serverClientId: "GOOGLE_WEB_CLIENT_ID",
        scopes: [\
          'email',\
        ],
      );
    } else {
      googleSignIn = GoogleSignIn(
        clientId: "GOOGLE_IOS_CLIENT_ID",
        serverClientId: "GOOGLE_WEB_CLIENT_ID",
        scopes: [\
          'email',\
        ],
      );
    }

GoogleSignInAccount? account = await googleSignIn.signIn();

if (account == null) {
        print("Google sign in was aborted");
        return;
    }

String? authCode = account.serverAuthCode;

if (authCode == null) {
        print("Google sign in did not return a server auth code");
        return;
    }

// Refer to step 2
  }

Mobile

Step 2) Calling the signinup API to consume the authorization code

Once you have the authorization code from the auth provider, you need to call the signinup API exposed by the backend SDK as shown below:

Mobile

curl --location --request POST '<YOUR_API_DOMAIN>/auth/signinup' \
--header 'Content-Type: application/json; charset=utf-8' \
--data-raw '{
    "thirdPartyId": "google",
    "clientType": "...",
    "redirectURIInfo": {
        "redirectURIOnProviderDashboard": "",
        "redirectURIQueryParams": {
            "code": "...",
        }
    }
}'

Mobile

The response body from the API call has a status property in it:

Authorization code grant flow with PKCE

This is similar to the first one, except that you do not need to provide a client secret during backend init. This flow only works for providers which support the PKCE flow.

Calling the signinup API to consume the authorization code

Once you have the authorization code and PKCE verifier from the auth provider, you need to call the /signinup API exposed by the backend SDK as shown below:

React Native

Fetching the authorization code on the frontend

You can use the react native auth library to also return the PKCE code verifier along with the authorization code. Achieve this by setting the usePKCE boolean to true and also by setting the skipCodeExchange to true when configuring the react native auth library.

Android

Fetching the authorization code on the frontend

You can use the AppAuth-Android library to use the PKCE flow by using the setCodeVerifier method when creating a AuthorizationRequest.

iOS

Fetching the authorization code on the frontend

You can use the AppAuth-iOS library to use the PKCE flow.

Flutter

Fetching the authorization code on the frontend

You can use flutter_appauth to use the PKCE flow by providing a codeVerifier when you call the appAuth.token function.

Mobile

curl --location --request POST '<YOUR_API_DOMAIN>/auth/signinup' \
--header 'Content-Type: application/json' \
--data-raw '{
    "thirdPartyId": "THIRD_PARTY_ID",
    "clientType": "...",
    "redirectURIInfo": {
        "redirectURIOnProviderDashboard": "REDIRECT_URI",
        "redirectURIQueryParams": {
            "code": "...",
        },
        "pkceCodeVerifier": "..."
    }
}'

Mobile

The response body from the API call has a status property in it:

OAuth/Access Tokens

Web

Mobile

Fetching the OAuth/Access tokens on the frontend
  1. Sign in with the social provider. The minimum required scope is the one that provides access to the user’s email. You can use any library to sign in with the social provider.
  2. Get the access token on the frontend if it is available.
  3. Get the id token from the sign in result if it is available.
Calling the signinup API to use the OAuth tokens

Once you have the access_token or the id_token from the auth provider, you need to call the /signinup API exposed by the backend SDK as shown below:

WebMobile

Platform

Web▼

curl --location --request POST '<YOUR_API_DOMAIN>/auth/signinup' \
--header 'Content-Type: application/json; charset=utf-8' \
--data-raw '{
    "thirdPartyId": "google",
    "clientType": "...",
    "oAuthTokens": {
        "access_token": "...",
        "id_token": "..."
    },
}'

Mobile

The response body from the API call has a status property in it:

3. Initialize the backend SDK

Node.jsPythonGo

Language

Node.js▼


from supertokens_python import init, InputAppInfo, SupertokensConfig from supertokens_python.recipe import thirdparty, session


if err != nil {
        panic(err.Error())
    }
}

4. Add the authentication providers

Populate the providers array with the third-party authentication providers that you want.

Node.jsGoPython

Language

Node.js▼

import SuperTokens from "supertokens-node";
import ThirdParty from "supertokens-node/recipe/thirdparty";
import (
    "github.com/supertokens/supertokens-golang/recipe/thirdparty"
    "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
)

#### [Set OAuth scopes](/content/docs/authentication/social/initial-setup?q=BWt-ep9AB\#set-oauth-scopes-1/index.html)

To add additional OAuth scopes when accessing your third-party provider, add them to the configuration when initializing the backend `SDK`.

For example, if you are using Google as your third-party provider, you can add an additional scope as follows:

Node.jsGoPython

Language

Node.js▼

import SuperTokens from "supertokens-node"; import ThirdParty from "supertokens-node/recipe/thirdparty";



## [Next steps](/content/docs/authentication/social/initial-setup?q=BWt-ep9AB\#next-steps/index.html)

Having completed the main setup, you can explore more advanced topics related to the **ThirdParty** recipe.

[Built-in Providers\\
\\
Read more about the common providers exposed by the recipe.](/content/docs/authentication/social/built-in-providers-config/index.html) [Custom Providers\\
\\
See how you can create your own custom provider.](/content/docs/authentication/social/custom-providers/index.html) [Custom Invite Flow\\
\\
Disable public sign ups and use your own invite flow.](/content/docs/authentication/social/custom-invite-flow/index.html) [Hooks and Overrides\\
\\
Add custom logic after the logs in or signs up.](/content/docs/authentication/social/hooks-and-overrides/index.html)