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.
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
1. Initialize the frontend SDK
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(),
],
});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.
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>
);
}
}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>;
}
}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>
);
}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.
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.
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.
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:
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.
Add the SuperTokens.init function call at the start of your application.
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()],
});import SuperTokens from "supertokens-react-native";
SuperTokens.init({
apiDomain: "<YOUR_API_DOMAIN>",
apiBasePath: "/auth",
});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()
}
}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
}
}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:
- Authorization Code
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.
- OAuth/Access Tokens
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
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:
Sign in with Apple example
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).
Fetching the authorization code on the frontend
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.
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.
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.");
}
}
}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.
}
}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
}
}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.
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:
-
Set up a route in your app that handles this callback. It’s recommended to use something like
https://<YOUR_WEBSITE_DOMAIN>/auth/callback/google(for Google). Regardless of what you make this path, remember to use that same path when calling thegetAuthorisationURLWithQueryParamsAndSetStatefunction in the first step. -
On that route, call the following function on page load
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:
- the app and configured SuperTokens
clientType; - the exact Apple callback URL and an allowlisted app deep-link target;
- a hash of the initiating app installation, authenticated session, or browser context when one is available; and
- an expiry no more than five minutes in the future.
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
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.
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";
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("");
},
};
},
},
});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.
Go
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"
)
func main() {
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
},
},
})
}Python
from supertokens_python.recipe import thirdparty
from supertokens_python.recipe.thirdparty.interfaces import APIInterface, APIOptions
from typing import Dict, Any
from urllib.parse import urlencode
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
),
)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:
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":"..."
}
}
}
}'The response body from the API call has a status property in it:
status: "OK": User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user.status: "NO_EMAIL_GIVEN_BY_PROVIDER": The provider did not return the email identity required by this configuration. Ask the user to choose another sign-in method. Do not invent an email address without first defining a stable, provider-specific identity and an account-linking policy: synthetic addresses can create duplicate accounts, link the wrong identities, and invalidate assumptions that an email belongs to or was verified by the user.status: "GENERAL_ERROR": This is only possible if you have overridden the backend API to send back a custom error message which should display on the frontend.status: "SIGN_IN_UP_NOT_ALLOWED": This can happen during automatic account linking or duringMFA. Thereasonprop that’s in the response body contains a support code using which you can see why the sign in / up was not allowed.
Sign in with Google example
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.
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.
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.
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.
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;
};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
}
}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
}
}
}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
}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:
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": "...",
}
}
}'The response body from the API call has a status property in it:
status: "OK": User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user.status: "NO_EMAIL_GIVEN_BY_PROVIDER": The provider did not return the email identity required by this configuration. Ask the user to choose another sign-in method. Do not invent an email address without first defining a stable, provider-specific identity and an account-linking policy: synthetic addresses can create duplicate accounts, link the wrong identities, and invalidate assumptions that an email belongs to or was verified by the user.status: "GENERAL_ERROR": This is only possible if you have overridden the backend API to send back a custom error message which should display on the frontend.status: "SIGN_IN_UP_NOT_ALLOWED": This can happen during automatic account linking or duringMFA. Thereasonprop that’s in the response body contains a support code using which you can see why the sign in / up was not allowed.
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:
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.
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.
Fetching the authorization code on the frontend
You can use the AppAuth-iOS library to use the PKCE flow.
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.
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": "..."
}
}'The response body from the API call has a status property in it:
status: "OK": User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user.status: "NO_EMAIL_GIVEN_BY_PROVIDER": The provider did not return the email identity required by this configuration. Ask the user to choose another sign-in method. Do not invent an email address without first defining a stable, provider-specific identity and an account-linking policy: synthetic addresses can create duplicate accounts, link the wrong identities, and invalidate assumptions that an email belongs to or was verified by the user.status: "GENERAL_ERROR": This is only possible if you have overridden the backend API to send back a custom error message which should display on the frontend.status: "SIGN_IN_UP_NOT_ALLOWED": This can happen during automatic account linking or duringMFA. Thereasonprop that’s in the response body contains a support code using which you can see why the sign in / up was not allowed.
OAuth/Access Tokens
Fetching the OAuth/Access tokens on the frontend
- 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.
- Get the access token on the frontend if it is available.
- 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:
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": "..."
},
}'The response body from the API call has a status property in it:
status: "OK": User sign in / up was successful. The response also contains more information about the user, for example their user ID, and if it was a new user or existing user.status: "NO_EMAIL_GIVEN_BY_PROVIDER": The provider did not return the email identity required by this configuration. Ask the user to choose another sign-in method. Do not invent an email address without first defining a stable, provider-specific identity and an account-linking policy: synthetic addresses can create duplicate accounts, link the wrong identities, and invalidate assumptions that an email belongs to or was verified by the user.status: "GENERAL_ERROR": This is only possible if you have overridden the backend API to send back a custom error message which should display on the frontend.status: "SIGN_IN_UP_NOT_ALLOWED": This can happen during automatic account linking or duringMFA. Thereasonprop that’s in the response body contains a support code using which you can see why the sign in / up was not allowed.
3. 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.
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())
}
}4. Add the authentication providers
Populate the providers array with the third-party authentication providers that you want.
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:
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"]
),
],
),
),
]
)
)
]
)Next steps
Having completed the main setup, you can explore more advanced topics related to the ThirdParty recipe.