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 endpoints#
There 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
- Other Frameworks
Important
import SuperTokens from "supertokens-node";
import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword";
SuperTokens.init({
appInfo: {
appName: "...",
apiDomain: "...",
websiteDomain: "...",
},
recipeList: [
ThirdPartyEmailPassword.init({
providers: [{
config: {
thirdPartyId: "custom",
name: "Custom provider",
clients: [{
clientId: "...",
clientSecret: "...",
scope: ["profile", "email"]
}],
authorizationEndpoint: "https://example.com/oauth/authorize",
authorizationEndpointQueryParams: {
"someKey1": "value1",
"someKey2": null
},
tokenEndpoint: "https://example.com/oauth/token",
tokenEndpointBodyParams: {
"someKey1": "value1",
},
userInfoEndpoint: "https://example.com/oauth/userinfo",
userInfoMap: {
fromUserInfoAPI: {
userId: "id",
email: "email",
emailVerified: "email_verified",
}
}
}
}]
})
]
})
import (
"github.com/supertokens/supertokens-golang/recipe/thirdpartyemailpassword"
"github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
"github.com/supertokens/supertokens-golang/recipe/thirdpartyemailpassword/tpepmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
thirdpartyemailpassword.Init(&tpepmodels.TypeInput{
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{
FromIdTokenPayload: struct{UserId string "json:\"userId,omitempty\""; Email string "json:\"email,omitempty\""; EmailVerified string "json:\"emailVerified,omitempty\""} {
UserId: "id",
Email: "email",
EmailVerified: "email_verified",
},
},
},
},
},
}),
},
})
}
from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import thirdpartyemailpassword
from supertokens_python.recipe.thirdparty import ProviderInput, ProviderConfig, ProviderClientConfig
from supertokens_python.recipe.thirdparty.provider import UserInfoMap, UserFields
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework='...',
recipe_list=[
thirdpartyemailpassword.init(
providers=[
ProviderInput(
config=ProviderConfig(
third_party_id="custom",
name="Custom Provider",
clients=[
ProviderClientConfig(
client_id="...",
client_secret="...",
scope=["email", "profile"],
),
],
authorization_endpoint="https://example.com/oauth/authorize",
authorization_endpoint_query_params={
"someKey1": "value1",
"someKey2": None,
},
token_endpoint="https://example.com/oauth/token",
token_endpoint_body_params={
"someKey1": "value1",
},
user_info_endpoint="https://example.com/oauth/userinfo",
user_info_map=UserInfoMap(
from_id_token_payload=UserFields(
user_id="id",
email="email",
email_verified="email_verified",
),
from_user_info_api=UserFields(),
),
),
),
]
)
]
)
The
thirdPartyIdis a unique ID for this provider which helps SuperTokens identify the provider. For example, thethirdPartyIdfor the built in Google provider is"google".The
namefield is sent to the frontend and can be used to render the login button on the frontend UI. For example, if you set thenameto"XYZ", the pre built UI on the frontend will display the text"Login using XYZ"on the button for this provider.The
clientsarray 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: stringconfig for each of the items in theclientsarray.AuthorizationEndpointcorresponds 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".AuthorizationEndpointQueryParamsis 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.
TokenEndpointcorresponds 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".TokenEndpointBodyParamsis 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.
UserInfoEndpointcorresponds 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.FromUserInfoAPImap. 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
/userinfoendpoint, it will read thejsonResponse.idfield to get the user's Google ID, thejsonResponse.emailfield to get back the user's email and thejsonResponse.email_verifiedfield 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) endpoints#
If 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
- Other Frameworks
Important
import SuperTokens from "supertokens-node";
import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword";
SuperTokens.init({
appInfo: {
appName: "...",
apiDomain: "...",
websiteDomain: "...",
},
recipeList: [
ThirdPartyEmailPassword.init({
providers: [{
config: {
thirdPartyId: "custom",
name: "Custom provider",
clients: [{
clientId: "...",
clientSecret: "...",
scope: ["profile", "email"]
}],
oidcDiscoveryEndpoint: "https://example.com",
authorizationEndpointQueryParams: {
"someKey1": "value1",
"someKey2": null
},
userInfoMap: {
fromIdTokenPayload: {
userId: "id",
email: "email",
emailVerified: "email_verified",
}
}
}
}]
})
]
})
import (
"github.com/supertokens/supertokens-golang/recipe/thirdpartyemailpassword"
"github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
"github.com/supertokens/supertokens-golang/recipe/thirdpartyemailpassword/tpepmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
thirdpartyemailpassword.Init(&tpepmodels.TypeInput{
Providers: []tpmodels.ProviderInput{
{
Config: tpmodels.ProviderConfig{
ThirdPartyId: "custom",
Name: "Custom provider",
Clients: []tpmodels.ProviderClientConfig{
{
ClientID: "...",
ClientSecret: "...",
Scope: []string{"profile", "email"},
},
},
OIDCDiscoveryEndpoint: "https://example.com",
AuthorizationEndpointQueryParams: map[string]interface{}{ // optional
"someKey1": "value1",
"someKey2": nil,
},
UserInfoMap: tpmodels.TypeUserInfoMap{
FromIdTokenPayload: struct{UserId string "json:\"userId,omitempty\""; Email string "json:\"email,omitempty\""; EmailVerified string "json:\"emailVerified,omitempty\""} {
UserId: "id",
Email: "email",
EmailVerified: "email_verified",
},
},
},
},
},
}),
},
})
}
from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import thirdpartyemailpassword
from supertokens_python.recipe.thirdparty import ProviderInput, ProviderConfig, ProviderClientConfig
from supertokens_python.recipe.thirdparty.provider import UserInfoMap, UserFields
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework='...',
recipe_list=[
thirdpartyemailpassword.init(
providers=[
ProviderInput(
config=ProviderConfig(
third_party_id="custom",
name="Custom Provider",
clients=[
ProviderClientConfig(
client_id="...",
client_secret="...",
scope=["email", "profile"],
),
],
oidc_discovery_endpoint="https://example.com",
authorization_endpoint_query_params={
"someKey1": "value1",
"someKey2": None,
},
user_info_map=UserInfoMap(
from_user_info_api=UserFields(
user_id="id",
email="email",
email_verified="email_verified",
),
from_id_token_payload=UserFields(),
),
),
),
]
)
]
)
- The config values are similar to the ones in the "Via OAuth endpoints" method. Please read that section to understand the
thirdPartyId,name,clientsconfig. - 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.FromIdTokenPayloadmap. - You can also add the
UserInfoMap.FromUserInfoAPImap 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
- Other Frameworks
Important
import SuperTokens from "supertokens-node";
import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword";
SuperTokens.init({
appInfo: {
appName: "...",
apiDomain: "...",
websiteDomain: "...",
},
recipeList: [
ThirdPartyEmailPassword.init({
providers: [{
config: {
thirdPartyId: "custom",
name: "Custom provider",
clients: [{
clientId: "...",
clientSecret: "...",
scope: ["profile", "email"]
}],
authorizationEndpoint: "https://example.com/oauth/authorize",
authorizationEndpointQueryParams: {
"response_type": "token", // Changing an existing parameter
"response_mode": "form", // Adding a new parameter
"scope": null, // Removing a parameter
},
tokenEndpoint: "https://example.com/oauth/token"
},
override: (originalImplementation) => {
return {
...originalImplementation,
getUserInfo: async function (input) {
// Call provider's APIs to get profile info
// ...
return {
thirdPartyUserId: "...",
email: {
id: "...",
isVerified: true
},
rawUserInfoFromProvider: {
fromUserInfoAPI: {
"first_name": "...",
"last_name": "..."
},
}
}
}
}
}
}]
})
]
})
import (
"github.com/supertokens/supertokens-golang/recipe/thirdpartyemailpassword"
"github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels"
"github.com/supertokens/supertokens-golang/recipe/thirdpartyemailpassword/tpepmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
thirdpartyemailpassword.Init(&tpepmodels.TypeInput{
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(oAuthTokens map[string]interface{}, userContext *map[string]interface{}) (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, InputAppInfo
from supertokens_python.recipe import thirdpartyemailpassword
from supertokens_python.recipe.thirdparty.provider import ProviderClientConfig, ProviderConfig, ProviderInput, Provider, ProviderConfigForClient
from supertokens_python.recipe.thirdparty.types import UserInfo, UserInfoEmail, RawUserInfoFromProvider
from typing import Dict, Any
class CustomProvider(Provider):
def __init__(self):
super().__init__('<PROVIDER_ID>', ProviderConfigForClient(
client_id='<CLIENT_ID>',
))
async def get_user_info(self, oauth_tokens: Dict[str, Any], user_context: Dict[str, Any]) -> UserInfo:
return UserInfo(
third_party_user_id="...",
email=UserInfoEmail(
email="...",
is_verified=True,
),
raw_user_info_from_provider=RawUserInfoFromProvider(
from_id_token_payload={},
from_user_info_api={
"first_name": "...",
"last_name": "...",
},
),
)
def get_custom_provider(provider: Provider) -> Provider:
return CustomProvider()
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework='...',
recipe_list=[
thirdpartyemailpassword.init(
providers=[
ProviderInput(
config=ProviderConfig(
third_party_id="custom",
clients=[
ProviderClientConfig(
client_id="...",
client_secret="...",
scope=["profile", "email"],
),
],
authorization_endpoint="https://example.com/oauth/authorize",
authorization_endpoint_query_params={
"response_type": "token", # Changing an existing parameter
"response_mode": "form", # Adding a new parameter
"scope": None, # Removing a parameter
},
token_endpoint="https://example.com/oauth/token",
),
override=get_custom_provider
),
]
)
]
)
The original implementation has 4 functions which can be overidden:
GetConfigForClientTypeSelects 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_hintis provided in the request, it can be added to theAuthorizationEndpointQueryParamsby overriding this function.GetAuthorisationRedirectURLThis function returns the full URL (along with query params) to which the user needs to be navigated to in order to login.
ExchangeAuthCodeForOAuthTokensThis function is responsible for exchanging one time use
Authorization Codewith the user's tokens, such asAccess Token,ID Token, etc.GetUserInfoThis function is responsible for fetching the user information such as
UserId,EmailandEmailVerifiedusing the tokens returned from the previous function.