Validating JWTs Offline

Descope employs JSON Web Token (JWT) to ensure secure authentication, and authorization.

In web applications, it's essential to parse and validate these tokens to guarantee their integrity and authenticity.

Here's how to use Descope's backend SDKs to validate JWTs:

Backend SDK

Install SDK

Terminal
npm i --save @descope/node-sdk
Terminal
pip3 install descope
Terminal
go 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>
Terminal
gem install descope
Terminal
composer require descope/descope-php
Terminal
dotnet add package descope

Import 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,
};

Call the Validate JWT Function

After passing in the JWT from the frontend to your backend, you can simply call the validate JWT function.

You can optionally validate the aud claim by passing an audience parameter to prevent token reuse across applications. The parameter accepts either a string or an array of strings.

Note: Not all SDKs support audience validation - see code examples below for supported SDKs.

// Args:
//   sessionToken (str): The session token, which contains the signature that will be validated
const sessionToken = "xxxx"; // extract from request authorization header

try {
  // Basic validation without audience checking
  const authInfo = await descopeSdk.validateSession(sessionToken);
  
  // Or validate with a single audience (string)
  const authInfoWithAudience = await descopeSdk.validateSession(sessionToken, {
    audience: '__ProjectID__'
  });
  
  // Or validate with multiple audiences (array)
  const authInfoWithMultipleAudiences = await descopeSdk.validateSession(sessionToken, {
    audience: ['__ProjectID__', 'my-custom-audience']
  });
  
  console.log("Successfully validated user session:");
  console.log(authInfo);
} catch (error) {
  console.log("Could not validate user session " + error);
}
# Args:
#   session_token (str): The session token, which contains the signature that will be validated
session_token = "xxxx" # extract from request authorization header

try:
  # Basic validation without audience checking
  jwt_response = descope_client.validate_session(session_token=session_token)
  
  # Or validate with a single audience (string)
  jwt_response_with_audience = descope_client.validate_session(
    session_token=session_token, 
    audience="__ProjectID__"
  )
  
  # Or validate with multiple audiences (list)
  jwt_response_with_multiple_audiences = descope_client.validate_session(
    session_token=session_token, 
    audience=["__ProjectID__", "my-custom-audience"]
  )
  
  print("Successfully validated user session:")
  print(jwt_response)
except Exception as error:
  print("Could not validate user session. Error:")
  print(error)

# -------------- or (use a Python Decorator) --------------

import descope_validate_auth

@app.route('/protected', methods=['GET'])
@descope_validate_auth(descope_client, permissions=["read"], roles=["user"], audience="__ProjectID__")
def protected_route():
    return "Access to protected resource granted"
// 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()
//    sessionToken (str): The session token, which contains the signature that will be validated
sessionToken := "xxxx" // extract from request authorization header. The above sample code sends the the session token in authorization header.

authorized, userToken, err := descopeClient.Auth.ValidateSessionWithToken(ctx, sessionToken)
if (err != nil){
  fmt.Println("Could not validate user session: ", err)
} else {
  fmt.Println("Successfully validated user session: ", userToken)
}
// Validate the session. Will return an error if expired
AuthenticationService as = descopeClient.getAuthenticationServices().getAuthenticationService();
try {
    Token t = as.validateSessionWithToken(sessionToken);
} catch (DescopeException de) {
    // Handle the unauthorized error
}

// If ValidateSessionWithRequest raises an exception, you will need to refresh the session using
try {
    Token t = as.refreshSessionWithToken(refreshToken);
} catch (DescopeException de) {
    // Handle the unauthorized error
}

// Alternatively, you could combine the two and
// have the session validated and automatically refreshed when expired
try {
    Token t = as.validateAndRefreshSessionWithTokens(sessionToken, refreshToken);
} catch (DescopeException de) {
    // unauthorized error
}

# Validate the session. Will raise if expired
begin
    jwt_response = descope_client.validate_session('session_token')
rescue AuthException => e
    # Session expired
end

# If validate_session raises an exception, you will need to refresh the session using
jwt_response = descope_client.refresh_session('refresh_token')

# Alternatively, you could combine the two and
# have the session validated and automatically refreshed when expired
jwt_response = descope_client.validate_and_refresh_session('session_token', 'refresh_token')
if (isset($_POST["sessionToken"])) {
    if ($descopeSDK->verify($_POST["sessionToken"])) {
        $_SESSION["user"] = json_decode($_POST["userDetails"], true);
        $_SESSION["sessionToken"] = $_POST["sessionToken"];
        session_write_close();
 
        // User session validated and token saved
    } else {
        error_log("Session token verification failed.");
        $descopeSDK->logout();
 
        // Redirect to login page
    }
} else {
    error_log("Session token is not set in POST request.");
 
    // Redirect to login page
}

Offline

Validating JSON Web Tokens (JWTs) offline is crucial in situations where the server running the SDK does not have access to the internet. Descope SDKs allow you to handle this scenario with ease. This article explains how to validate JWTs offline by providing a custom public key.

Providing a Custom Public Key

Finding Your Public Key

Your public key can be located at https://api.descope.com/v2/keys/<your_project_id> for US-based projects. Use the localized baseURL for projects located outside of the US. Refer to the Descope Documentation and API reference page for additional details on locating and handling public keys.

Initializing the SDK with a Custom Public Key

To provide your own public key, you can do so by including the publicKey option when initializing the SDK. The public key must be a JSON object containing the appropriate algorithm and other details. Below are examples of initializing the SDK with a public key.

import DescopeClient from '@descope/node-sdk';
try{
    //  baseUrl="<URL>" // When initializing the Descope clientyou 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__', publicKey: '{"alg":"RS256", ... }'});
} 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 a custom domain within your Descope project."
    descope_client = DescopeClient(project_id='__ProjectID__',  public_key: '{"alg"="RS256", ... }')
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__"}, PublicKey:'{"alg":"RS256", ... }')
if err != nil {
    // handle the error
    log.Println("failed to initialize: " + err.Error())
}

Conclusion

Validating JWTs offline via SDK by providing a custom public key enhances security and functionality, especially when working in environments without internet access.

If you have any other questions about Descope, feel free to reach out to us!

Was this helpful?

On this page