Custom providers
If SuperTokens doesn't support a provider out of the box, you can add your own custom provider as shown below.
note
If you think that this provider should be supported by SuperTokens by default, make sure to let us know here.
#
Frontend- ReactJS
- Angular
- Vue
Important
supertokens-auth-react
SDK and will inject the React components to show the UI. Therefore, the code snippet below refers to the supertokens-auth-react
SDK.import React from "react";import SuperTokens from "supertokens-auth-react";import ThirdParty from "supertokens-auth-react/recipe/thirdparty";SuperTokens.init({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "..." }, recipeList: [ ThirdParty.init({ signInAndUpFeature: { providers: [ { id: "custom", name: "X", // Will display "Continue with X"
// optional // you do not need to add a click handler to this as // we add it for you automatically. buttonComponent: (props: {name: string}) => <div style={{ cursor: "pointer", border: "1", paddingTop: "5px", paddingBottom: "5px", borderRadius: "5px", borderStyle: "solid" }}>{"Login with " + props.name}</div> } ], // ... }, // ... }), // ... ]});
Important
supertokens-auth-react
SDK and will inject the React components to show the UI. Therefore, the code snippet below refers to the supertokens-auth-react
SDK.import React from "react";import SuperTokens from "supertokens-auth-react";import ThirdParty from "supertokens-auth-react/recipe/thirdparty";SuperTokens.init({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "..." }, recipeList: [ ThirdParty.init({ signInAndUpFeature: { providers: [ { id: "custom", name: "X", // Will display "Continue with X"
// optional // you do not need to add a click handler to this as // we add it for you automatically. buttonComponent: (props: {name: string}) => <div style={{ cursor: "pointer", border: "1", paddingTop: "5px", paddingBottom: "5px", borderRadius: "5px", borderStyle: "solid" }}>{"Login with " + props.name}</div> } ], // ... }, // ... }), // ... ]});
import React from "react";import SuperTokens from "supertokens-auth-react";import ThirdParty from "supertokens-auth-react/recipe/thirdparty";SuperTokens.init({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "..." }, recipeList: [ ThirdParty.init({ signInAndUpFeature: { providers: [ { id: "custom", name: "X", // Will display "Continue with X"
// optional // you do not need to add a click handler to this as // we add it for you automatically. buttonComponent: (props: {name: string}) => <div style={{ cursor: "pointer", border: "1", paddingTop: "5px", paddingBottom: "5px", borderRadius: "5px", borderStyle: "solid" }}>{"Login with " + props.name}</div> } ], // ... }, // ... }), // ... ]});
#
Backend#
Via OAuth endpointsThere are a couple of ways you can define a custom provider. The simplest method is to provide the config for the AuthorizationEndpoint
, TokenEndpoint
, and the mapping for how the user's ID and email from the provider's profile information endpoint. This can be seen below:
- NodeJS
- GoLang
- Python
TODO
import ( "github.com/supertokens/supertokens-golang/recipe/thirdpartypasswordless" "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: "custom", Name: "Custom provider", Clients: []tpmodels.ProviderClientConfig{ { ClientID: "...", ClientSecret: "...", Scope: []string{"profile", "email"}, }, }, AuthorizationEndpoint: "https://example.com/oauth/authorize", AuthorizationEndpointQueryParams: map[string]interface{}{ // optional "someKey1": "value1", "someKey2": nil, }, TokenEndpoint: "https://example.com/oauth/token", TokenEndpointBodyParams: map[string]interface{}{ // optional "someKey1": "value1", }, UserInfoEndpoint: "https://example.com/oauth/userinfo", UserInfoMap: tpmodels.TypeUserInfoMap{ FromUserInfoAPI: tpmodels.TypeUserInfoFields{ UserId: "id", Email: "email", EmailVerified: "email_verified", }, }, }, }, }, }, }), }, })}
TODO
The
thirdPartyId
is a unique ID for this provider which helps SuperTokens identify the provider. For example, thethirdPartyId
for the built in Google provider is"google"
.The
name
field is sent to the frontend and can be used to render the login button on the frontend UI. For example, if you set thename
to"XYZ"
, the pre built UI on the frontend will display the text"Login using XYZ"
on the button for this provider.The
clients
array represents the client credentials / settings for each of your frontend clients. Most of the time, one client item is enough, but if the provider requires to use different client ID / secret for web vs mobile apps, you would need to add two items to this array - one for web, and one for mobile. In this case, you would also need to add theclientType: string
config for each of the items in theclients
array.AuthorizationEndpoint
corresponds to the URL to which the user should be redirected to for the login. For example for Google, this is"https://accounts.google.com/o/oauth2/v2/auth"
.AuthorizationEndpointQueryParams
is an optional config, but it can be used to modify the query params added to theAuthorizationEndpoint
. You can use this config to add new keys, or to modify or remove (by setting to null) query params added by SuperTokens.
TokenEndpoint
corresponds to the API that is used to exchange Authorization Code for the user's Access Token or the ID Token. For example for Google, the value of this is"https://oauth2.googleapis.com/token"
.TokenEndpointBodyParams
is an optional config, but it can be used to modify the request body sent to theTokenEndpoint
. You can use this config to add new keys, or to modify or remove (by setting to null) body params added by SuperTokens.
UserInfoEndpoint
corresponds to the API that provides user information using the AccessToken. For Google, the value of this is"https://www.googleapis.com/oauth2/v1/userinfo"
. Once this endpoint is called, SuperTokens fetches user info (like their userID and email ID) from the JSON response based on the config forUserInfoMap.FromUserInfoAPI
map. For example, the value of the map for Google is:{ userId: "id", email: "email", emailVerified: "email_verified"}
This means that when SuperTokens gets back the JSON from the
/userinfo
endpoint, it will read thejsonResponse.id
field to get the user's Google ID, thejsonResponse.email
field to get back the user's email and thejsonResponse.email_verified
field to know if the user's email is verified or not.If you want to fetch a value from a nested JSON object, you can specify something like
userId: "user.id"
, in which case the provider's user ID will be fetched usingjsonResponse.user.id
.
#
Via Open ID connect (OIDC) endpointsIf the provider is Open ID Connect (OIDC) compatible, you can provide url for the OIDCDiscoverEndpoint
config. The backend SDK will automatically discover authorization endpoint, token endpoint and the user info endpoint by querying the <OIDCDiscoverEndpoint>/.well-known/openid-configuration
.
Below is an example of how to set the OIDC discovery endpoint:
- NodeJS
- GoLang
- Python
TODO
import ( "github.com/supertokens/supertokens-golang/recipe/thirdpartypasswordless" "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: "custom", Name: "Custom provider", Clients: []tpmodels.ProviderClientConfig{ { ClientID: "...", ClientSecret: "...", Scope: []string{"profile", "email"}, }, }, OIDCDiscoveryEndpoint: "https://example.com/openid", AuthorizationEndpointQueryParams: map[string]interface{}{ // optional "someKey1": "value1", "someKey2": nil, }, UserInfoMap: tpmodels.TypeUserInfoMap{ FromIdTokenPayload: tpmodels.TypeUserInfoFields{ UserId: "id", Email: "email", EmailVerified: "email_verified", }, }, }, }, }, }, }), }, })}
TODO
- The config values are similar to the ones in the "Via OAuth endpoints" method. Please read that section to understand the
thirdPartyId
,name
,clients
config. - Unlike the "Via OAuth endpoints", we extract the user's info from the ID token payload using the config specified by you in the
UserInfoMap.FromIdTokenPayload
map. - You can also add the
UserInfoMap.FromUserInfoAPI
map as done in the "Via OAuth endpoints" section. SuperTokens will auto merge the user information.
#
Handling non standard providers.Sometimes, one of the steps in the providers interaction may not be as per a standard. Therefore just providing the config like shown above may not work. To handle this case, we allow you to override any the steps that happen during the OAuth exchange.
For example, the API call made to get the user's profile info makes a GET
call to the UserInfoEndpoint
with the user's access token. If your provider requires a different method or requires multiple calls to different endpoints to get the profile info, then you can override the default implementation as shown below:
- NodeJS
- GoLang
- Python
import SuperTokens from "supertokens-node";import Session from "supertokens-node/recipe/session";import ThirdParty from "supertokens-node/recipe/thirdpartypasswordless";
TODO
SuperTokens.init({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "..." }, supertokens: { connectionURI: "...", }, recipeList: [ ThirdParty.init({ signInAndUpFeature: { providers: [ { id: "custom", get: (redirectURI, authCodeFromRequest) => { return { accessTokenAPI: { // this contains info about the token endpoint which exchanges the auth code with the access token and profile info. url: "https://oauth.example.com/token", params: { // example post params client_id: "<CLIENT ID>", client_secret: "<CLIENT SECRET>", grant_type: "authorization_code", redirect_uri: redirectURI || "", code: authCodeFromRequest || "", //... } }, authorisationRedirect: { // this contains info about forming the authorisation redirect URL without the state params and without the redirect_uri param url: "https://oauth.example.com", params: { client_id: "<CLIENT ID>", scope: "<SCOPES>", response_type: "code", //... } }, getClientId: () => { return "<CLIENT ID>"; }, getProfileInfo: async (accessTokenAPIResponse) => { /* accessTokenAPIResponse is the JSON response from the accessTokenAPI POST call. Using this, you need to return an object of the following type: { id: string, // user ID as provided by the third party provider email?: { // optional id: string, // emailID isVerified: boolean // true if the email is verified already } } */ return { id: "..." }; } } } } ] } }), Session.init({}) ]});
import ( "github.com/supertokens/supertokens-golang/recipe/thirdpartypasswordless" "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: "custom", Clients: []tpmodels.ProviderClientConfig{ { ClientID: "...", ClientSecret: "...", Scope: []string{"profile", "email"}, }, }, AuthorizationEndpoint: "https://example.com/oauth/authorize", AuthorizationEndpointQueryParams: map[string]interface{}{ "response_type": "token", // Changing an existing parameter "response_mode": "form", // Adding a new parameter "scope": nil, // Removing a parameter }, TokenEndpoint: "https://example.com/oauth/token", }, Override: func(originalImplementation *tpmodels.TypeProvider) *tpmodels.TypeProvider { // ... originalImplementation.GetUserInfo = func(config tpmodels.ProviderConfigForClientType, oAuthTokens tpmodels.TypeOAuthTokens, userContext supertokens.UserContext) (tpmodels.TypeUserInfo, error) { // Call provider's APIs to get profile info // ... return tpmodels.TypeUserInfo{ ThirdPartyUserId: "...", Email: &tpmodels.EmailStruct{ ID: "...", IsVerified: true, }, RawUserInfoFromProvider: tpmodels.TypeRawUserInfoFromProvider{ FromUserInfoAPI: map[string]interface{}{ "first_name": "...", "last_name": "...", // ... }, }, }, nil } return originalImplementation }, }, }, }, }), }, })}
from supertokens_python import init, InputAppInfofrom supertokens_python.recipe import thirdpartypasswordlessfrom supertokens_python.recipe.thirdparty.provider import Providerfrom supertokens_python.recipe.thirdparty.types import UserInfo, AccessTokenAPI, AuthorisationRedirectAPIfrom typing import Dict, Union, Anyfrom supertokens_python.recipe.thirdparty.types import UserInfo, UserInfoEmail
TODO
class CustomProvider(Provider): def __init__(self): super().__init__('<PROVIDER_ID>', False)
async def get_profile_info(self, auth_code_response: Dict[str, Any], user_context: Dict[str, Any]) -> UserInfo: # TODO: fetch user info from provider based on auth_code_response return UserInfo(user_id="...", email=UserInfoEmail(email="..", email_verified=True))
def get_authorisation_redirect_api_info(self, user_context: Dict[str, Any]) -> AuthorisationRedirectAPI: params: Dict[str, Any] = { 'scope': '<SCOPES>', 'response_type': 'code', 'client_id': "<CLIENT ID>", } return AuthorisationRedirectAPI(url="https://oauth.example.com", params=params)
def get_access_token_api_info( self, redirect_uri: str, auth_code_from_request: str, user_context: Dict[str, Any]) -> AccessTokenAPI: params = { 'client_id': "<CLIENT ID>", 'client_secret': "<CLIENT SECRET>", 'grant_type': 'authorization_code', 'code': auth_code_from_request, 'redirect_uri': redirect_uri } return AccessTokenAPI(url="https://oauth.example.com/token", params=params)
def get_redirect_uri(self, user_context: Dict[str, Any]) -> Union[None, str]: return None def get_client_id(self, user_context: Dict[str, Any]) -> str: return "<CLIENT ID>"
init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), framework='...', recipe_list=[ thirdparty.init( sign_in_and_up_feature=thirdparty.SignInAndUpFeature(providers=[ CustomProvider() ]) ) ])
The original implementation has 4 functions which can be overidden:
GetConfigForClientType
Selects the client config from the list of clients provided and returns the complete provider config. This is a good place to override config dynamically. For example, if
login_hint
is provided in the request, it can be added to theAuthorizationEndpointQueryParams
by overriding this function.GetAuthorisationRedirectURL
This function returns the full URL (along with query params) to which the user needs to be navigated to in order to login.
ExchangeAuthCodeForOAuthTokens
This function is responsible for exchanging one time use
Authorization Code
with the user's tokens, such asAccess Token
,ID Token
, etc.GetUserInfo
This function is responsible for fetching the user information such as
UserId
,Email
andEmailVerified
using the tokens returned from the previous function.