Single Sign On (SSO) with Backend SDKs
This guide is meant for developers that are NOT using Descope on the frontend to design login screens and authentication methods.
If you'd like to use Descope Flows, Quick Start should be your starting point. If you'd like to use our Client SDKs, refer to our Client SDK docs.
Descope supports SSO as one of the authentication methods for your end-users. When using SSO, the SSO configuration can be different for each tenant. Descope supports OIDC and SAML identity providers, specific to each Tenant. Please refer to the article in the manage section for the configuration of SAML or OIDC for each tenant.
Backend SDK
Install SDK
npm i --save @descope/node-sdkpip3 install descopego get github.com/descope/go-sdk// Include the following in your `pom.xml` (Maven)
<dependency>
<artifactId>java-sdk</artifactId>
<groupId>com.descope</groupId>
<version>sdk-version</version> // Check https://github.com/descope/descope-java/releases for the latest versions
</dependency>gem install descopecomposer require descope/descope-phpdotnet add package descopeImport and initialize SDK
import DescopeClient from '@descope/node-sdk';
try{
// baseUrl="<URL>" // When initializing the Descope client, you can also configure the baseUrl ex: https://auth.company.com - this is useful when you utilize a custom domain within your Descope project.
const descopeClient = DescopeClient({ projectId: '__ProjectID__' });
} catch (error) {
// handle the error
console.log("failed to initialize: " + error)
}from descope import (
REFRESH_SESSION_TOKEN_NAME,
SESSION_TOKEN_NAME,
AuthException,
DeliveryMethod,
DescopeClient,
AssociatedTenant,
RoleMapping,
AttributeMapping,
LoginOptions
)
try:
# You can configure the baseURL by setting the env variable Ex: export DESCOPE_BASE_URI="https://auth.company.com - this is useful when you utilize custom domain within your Descope project."
descope_client = DescopeClient(project_id='__ProjectID__')
except Exception as error:
# handle the error
print ("failed to initialize. Error:")
print (error)import "github.com/descope/go-sdk/descope"
import "github.com/descope/go-sdk/descope/client"
// Utilizing the context package allows for the transmission of context capabilities like cancellation
// signals during the function call. In cases where context is absent, the context.Background()
// function serves as a viable alternative.
// Utilizing context within the Descope GO SDK is supported within versions 1.6.0 and higher.
import (
"context"
)
// DescopeBaseURL // within the client.Config, you can also configure the baseUrl ex: https://auth.company.com - this is useful when you utilize a custom domain within your Descope project.
descopeClient, err := client.NewWithConfig(&client.Config{ProjectID:"__ProjectID__"})
if err != nil {
// handle the error
log.Println("failed to initialize: " + err.Error())
}import com.descope.client.Config;
import com.descope.client.DescopeClient;
var descopeClient = new DescopeClient(Config.builder().projectId("__ProjectID__").build());require 'descope'
@project_id = ENV['__ProjectID__']
@client = Descope::Client.new({ project_id: @project_id})require 'vendor/autoload.php';
use Descope\SDK\DescopeSDK;
$descopeSDK = new DescopeSDK([
'projectId' => $_ENV['__ProjectID__'],
]);// appsettings.json
{
"Descope": {
"ProjectId": "__ProjectID__",
"ManagementKey": "DESCOPE_MANAGEMENT_KEY"
}
}
// Program.cs
using Descope;
using Microsoft.Extensions.Configuration;
// ... In your setup code
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var descopeProjectId = config["Descope:ProjectId"];
var descopeManagementKey = config["Descope:ManagementKey"];
var descopeConfig = new DescopeConfig(projectId: descopeProjectId);
var descopeClient = new DescopeClient(descopeConfig)
{
ManagementKey = descopeManagementKey,
};Start SSO
To initiate the SSO process, call the SSO initiation function after the user clicks the login button. This function returns a pre-formatted URL that the client can use to redirect the user and begin the login flow with the SSO Identity Provider.
// Args:
// tenant_id: ID of the tenant that the user is authenticating to. The tenant ID is assigned to tenant at the time of creation.
const tenant_name_id_or_email = "xxxx"
// redirect_url: URL to return to after successful authentication with the SSO identity provider. You need to implement this page to access the token and finish oauth process (token exchange). The token arrives as a query parameter named 'code'.
const redirect_url = "https://auth.company.com/token_exchange"
// loginOptions (LoginOptions): this allows you to configure behavior during the authentication process.
const loginOptions = {
"stepup": false,
"mfa": false,
"customClaims": {"claim": "Value1"},
"templateOptions": {"option": "Value1"}
}
// refreshToken (optional): the user's current refresh token in the event of stepup/mfa
const resp = await descopeClient.saml.start(tenant_name_id_or_email, redirect_url, loginOptions);
if (!resp.ok) {
console.log("Failed to start sso auth")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
const url = resp.data.url
console.log("Successfully started sso auth. URL: " + url)
}# Args:
# tenant_name_id_or_email: ID of the tenant that the user is authenticating to. The tenant ID is assigned to tenant at the time of creation.
tenant_name_id_or_email = "xxxx"
# redirect_url: URL to return to after successful authentication with the SSO identity provider. You need to implement this page to access the token and finish oauth process (token exchange). The token arrives as a query parameter named 'code'.
redirect_url = "https://auth.company.com/token_exchange"
# login_options (LoginOptions): this allows you to configure behavior during the authentication process.
login_options = {
"stepup": False,
"mfa": False,
"custom_claims": {"claim": "Value1"},
"template_options": {"option": "Value1"}
}
# refresh_token (optional): the user's current refresh token in the event of stepup/mfa
# prompt (optional): OIDC-only prompt value (e.g., "login", "consent")
# sso_id (optional): SSO configuration ID to use
# login_hint (optional): Hint about the user's login identifier
# force_authn (optional): SAML-only value that can force authentication even if the user already has a session
try:
resp = descope_client.sso.start(
tenant=tenant_name_id_or_email,
return_url=redirect_url,
login_options=login_options,
prompt=None,
force_authn=False
)
print("Successfully started sso auth. URL: ")
print(resp)
except AuthException as error:
print("Failed to start sso auth")
print("Status Code: " + str(error.status_code))
print("Error: " + str(error.error_message))// Args:
// ctx: context.Context - Application context for the transmission of context capabilities like
// cancellation signals during the function call. In cases where context is absent, the context.Background()
// function serves as a viable alternative.
// Utilizing context within the Descope GO SDK is supported within versions 1.6.0 and higher.
ctx := context.Background()
// tenant: Name of the tenant that the user is authenticating to. The tenant ID is assigned to tenant at the time of creation.
tenant = "xxxx"
// returnURL: url for redirecting the user after authentication with social oauth provider. This value will override the value in the console settings. You need to implement this page to access the token and finish oauth process (token exchange). The token arrives as a query parameter named 'code'.
returnURL := "https://auth.company.com/token_exchange"
// r: HttpRequest for the update call. This request should contain refresh token for the authenticated user.
// loginOptions: this allows you to configure behavior during the authentication process.
loginOptions := &descope.LoginOptions{
Stepup: true,
MFA: true,
CustomClaims: map[string]any{}{"test": "testClaim"},
TemplateOptions: map[string]any{"option": "Value1"}
}
// w: ResponseWriter to update with correct redirect url. You can return this to your client for redirect.
redirectURL, err:= descopeClient.Auth.SSO.Start(ctx, tenant, returnURL, r, loginOptions, w)
if (err != nil){
fmt.Println("Failed to initialize SSO flow: ", err)
} else {
fmt.Println("Successfully started SSO flow: ", redirectURL)
}// Choose which tenant to log into
// Redirect the user to the returned URL to start the SSO redirect chain
SAMLService ss = descopeClient.getAuthenticationServices().getSAMLService();
try {
String returnURL = "https://my-app.com/handle-sso";
String url = ss.start("my-tenant-ID", returnURL, loginOptions);
} catch (DescopeException de) {
// Handle the error
}descope_client.saml_sign_in(
tenant: 'my-tenant-ID', # Choose which tenant to log into
return_url: 'https://my-app.com/handle-saml', # Can be configured in the console instead of here
prompt: 'custom prompt here'
)$response = $descopeSDK->auth->sso->signIn(
"tenant",
"https://example.com/callback",
"prompt",
true,
true,
["custom" => "claim"],
"ssoAppId"
);
print_r($response);// Args:
// tenant (string): ID of the tenant that the user is authenticating to. The tenant ID is assigned to tenant at the time of creation.
var tenant = "my-tenant-ID";
// redirectUrl (string?): An optional parameter to generate the SSO link. If not given, the project default will be used.
string? redirectUrl = "https://my-app.com/handle-saml";
// prompt (string?): OIDC-only prompt value (e.g., "login", "consent").
string? prompt = null;
// forceAuthn (bool?): SAML-only value that can force auth even if the user already has a session.
bool? forceAuthn = false;
// loginOptions (LoginOptions?): Step-up / MFA / custom claims.
LoginOptions? loginOptions = new LoginOptions
{
StepupRefreshJwt = null, // or an existing refresh JWT for step-up
MfaRefreshJwt = null, // or an existing refresh JWT for MFA
CustomClaims = new Dictionary<string, object>
{
["attribute1"] = "value"
}
};
try
{
var redirectUrl = await descopeClient.Auth.Sso.Start(tenant: tenant, redirectUrl: redirectUrl, prompt: prompt, forceAuthn: forceAuthn, loginOptions: loginOptions);
}
catch (DescopeException ex)
{
// Handle the error
}SSO Exchange Code
After successful authentication with your IdP the user is redirected to the redirect_url that you provide in the sso start
function above. Your application should extract the code from the redirect_url and perform token exchange as shown below.
// Args:
// code: code extracted from the url after user is redirected to redirect_url. The code is in the url as a query parameter "code" of the page.
const code = "xxxxx"
const resp = await descopeClient.saml.exchange(code);
if (!resp.ok) {
console.log("Failed to verify sso code")
console.log("Status Code: " + resp.code)
console.log("Error Code: " + resp.error.errorCode)
console.log("Error Description: " + resp.error.errorDescription)
console.log("Error Message: " + resp.error.errorMessage)
}
else {
console.log("Successfully verified sso code.")
}# Args:
# code: code extracted from the url after user is redirected to redirect_url. The code is in the url of the page.
code = "xxxxx"
try:
resp = descope_client.sso.exchange_token(code=code)
print ("Successfully verified sso code.")
print (resp)
except AuthException as error:
print ("Failed to verify sso code")
print ("Status Code: " + str(error.status_code))
print ("Error: " + str(error.error_message))// Args:
// ctx: context.Context - Application context for the transmission of context capabilities like
// cancellation signals during the function call. In cases where context is absent, the context.Background()
// function serves as a viable alternative.
// Utilizing context within the Descope GO SDK is supported within versions 1.6.0 and higher.
ctx := context.Background()
// code (string): code should be extracted from the redirect URL of OAuth authentication from the query parameter `code`.
code := "xxxxxx"
// w: ResponseWriter to update with correct session details. You can return this to your client for setting the cookies which are used for session validation
authInfo, err := descopeClient.Auth.SSO.ExchangeToken(ctx, code, w)
if (err != nil){
fmt.Println("Failed to verify sso code: ", err)
} else {
fmt.Println("Successfully verified sso code: ", authInfo)
}// The optional `w http.ResponseWriter` adds the session and refresh cookies to the response automatically.
// Otherwise they're available via authInfo
SAMLService ss = descopeClient.getAuthenticationServices().getSAMLService();
try {
String url = ss.exchangeToken(code);
} catch (DescopeException de) {
// Handle the error
}jwt_response = descope_client.saml_exchange_token(code)
session_token = jwt_response[Descope::Mixins::Common::SESSION_TOKEN_NAME].fetch('jwt')
refresh_token = jwt_response[Descope::Mixins::Common::REFRESH_SESSION_TOKEN_NAME].fetch('jwt')$response = $descopeSDK->auth->sso->exchangeToken("code");
print_r($response);// Args:
// code (string): code extracted from the url after user is redirected to redirect_url. The code is in the url as a query parameter "code" of the page.
var code = "authorization-code";
try
{
var authRes = await descopeClient.Auth.Sso.Exchange(code: code);
}
catch (DescopeException ex)
{
// Handle the error
}Session Validation
The final step of completing the authentication with Descope is to validate the user session. Descope provides rich session management capabilities, including configurable session timeouts and logout functions. You can find the details and sample code for backend session validation here.
Checkpoint
Your application is now integrated with Descope. Please test with sign-up or sign-in use case.