Role-Based Access Control

Roles and Permissions

Descope allows you to assign roles and permissions to the application's end user. Users with Descope admin privileges can define roles and permissions in the Descope console. Permissions and roles are represented as strings in Descope. Your application must do the interpretation and enforcement of the roles and permissions.

Permission Creation

Permissions are used within roles. Within the Permissions tab of the console, under Authorization you can create and manage your permissions. Clicking the + Permission Button at the top right allows you to create the permission. You can also click the three dots to the right of the permission to delete the permission or change it's description.

Descope permissions page shown as an example.

Role Creation

Within the Roles tab of the console, under Authorization you can create and manage your roles. Clicking the + Role Button at the top right allows you to create the role. You can also click the three dots to the right of the role to delete the role, change it's description, or add permissions to the role.

Descope roles page shown as an example.

Configuring User's Roles

Each user that is created in your application (during sign-up) can be assigned roles and permissions. The user can get more than one role and will include the role's associated permissions. The assignment of roles and permissions can be done manually (not recommended) using the console, using the management SDK (sample code below), or can be automatically mapped if you use SSO (SAML) as the authentication method for your application.

You can edit and add roles to users within the Users page.

Descope editing user roles example.

Tenants and Roles

Roles have two main types, Tenant and Project. Every role that's configured in a specific project, will either be specific to a Tenant or the entire Project, depending on this type. When you create roles you have the option to assign them to a specific tenant with the respective Tenant ID, shown in the docs below.

If you are using tenants for user management, then the same user can be assigned different roles and permissions for each tenant (if you so desire). These can be automatically assigned using SAML configuration too. The Tenant Management section covers the steps to configure roles mapping using SAML.

Descope adding tenants and roles to a user.

JWT Example

After successful end-user authentication, the roles and permissions are delivered to your application as part of the JWT token. Below is a sample JWT token that contains roles and permissions with a user logged into a tenant.

{
  "amr": [
    "email"
  ],
  "drn": "DS",
  "exp": 1692304651,
  "iat": 1692304051,
  "iss": "P2RFvFexVaxxNFK6rhP0ePtaGfTK",
  "sub": "U2RG6grrbT3REKYqk5yC4SjkMqzA",
  "tenants": {
    "T2U7vUH1NPy4JzWHruoOVIGyzYlu": {
      "permissions": [
        "AppSecEngineer",
        "Marketing",
        "Support"
      ],
      "roles": [
        "Engineering",
        "Product Manager"
      ]
    },
    "T2U7vVBqyZv6HdGtGLdnkgCbNxrC": {
      "permissions": [
        "AppSecEngineer",
        "Support"
      ],
      "roles": [
        "Support"
      ]
    }
  }
}

Validating Roles and Permissions

Examples of how to validate roles and permissions using the backend SDK are covered in our Session Management articles.

Authorization management using the management SDK

Install SDK

NodeJSPythonGoJavaRuby
npm i --save @descope/node-sdk
pip3 install descope
go get github.com/descope/go-sdk
// Include the following in your `pom.xml` (for 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 descope

Import and initialize Management SDK

NodeJSPythonGoJavaRuby
import DescopeClient from '@descope/node-sdk';

const managementKey = "xxxx"

try{
    //  baseUrl="<URL>" // When initializing the Descope clientyou can also configure the baseUrl ex: https://auth.company.com  - this is useful when you utilize CNAME within your Descope project.
    const descopeClient = DescopeClient({ projectId: '__ProjectID__', managementKey: managementKey });
} catch (error) {
    // handle the error
    console.log("failed to initialize: " + error)
}

// Note that you can handle async operation failures and capture specific errors to customize errors.
//     An example can be found here: https://github.com/descope/node-sdk?tab=readme-ov-file#error-handling
from descope import (
    REFRESH_SESSION_TOKEN_NAME,
    SESSION_TOKEN_NAME,
    AuthException,
    DeliveryMethod,
    DescopeClient,
    AssociatedTenant,
    RoleMapping,
    AttributeMapping
)

management_key = "xxxx"

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 CNAME within your Descope project."
    descope_client = DescopeClient(project_id='__ProjectID__', management_key=management_key)
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"
import "fmt"

// 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"
)

managementKey = "xxxx"

// DescopeBaseURL // within the client.Config, you can also configure the baseUrl ex: https://auth.company.com  - this is useful when you utilize CNAME within your Descope project.
descopeClient, err := client.NewWithConfig(&client.Config{ProjectID:"__ProjectID__", managementKey:managementKey})
if err != nil {
    // handle the error
    log.Println("failed to initialize: " + err.Error())
}
import com.descope.client;

// Initialized after setting the DESCOPE_PROJECT_ID env var (and optionally DESCOPE_MANAGEMENT_KEY)
var descopeClient = new DescopeClient();

// ** Or directly **
var descopeClient = new DescopeClient(Config.builder()
        .projectId("__ProjectID__")
        .managementKey("management-key")
        .build());
require 'descope'


descope_client = Descope::Client.new(
  {
    project_id: '__ProjectID__',
    management_key: 'management_key'
  }
)

Load All Permissions

This Descope SDK allows administrators to return all details for permissions configured within the Descope instance. The response includes an array of permissions and the details of each permission.

NodeJSPythonGoJava
const resp = await descopeClient.management.permission.loadAll()
if (!resp.ok) {
  console.log("Failed to load permissions.")
  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 loaded permissions.")
  console.log(resp.data)
}
try:
  resp = descope_client.mgmt.permission.load_all()
  print("Successfully loaded permissions.")
  print(json.dumps(resp, indent=4))
except AuthException as error:
  print ("Unable to load permissions.")
  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()

res, err := descopeClient.Management.Permission().LoadAll(ctx)
if  (err != nil){
  fmt.Println("Unable to load permissions.", err)
} else {
  fmt.Println("Successfully loaded permissions.")
  for _, permission := range res {
    fmt.Println(permission)
  }
}
// You can optionally set a description for a permission.
PermissionService ps = descopeClient.getManagementServices().getPermissionService();

// Load all permissions
try {
    PermissionResponse resp = ps.loadAll();
    for (Permission p : resp.getPermissions()) {
        // Do something
    }
} catch (DescopeException de) {
    // Handle the error
}

Create a Permissions

This Descope SDK allows administrators to create a new permission.

NodeJSPythonGoJava
// Args:
// 		name (str): permission name.
const name = "Test Permission"
// 		description (str): Optional description to briefly explain what this permission allows.
const description = "My description"

const resp = await descopeClient.management.permission.create(name, description)
if (!resp.ok) {
  console.log("Failed to create permission.")
  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 created permission.")
}
# Args:
#   name (str): permission name.
name = "Test Permission"
#   description (str): Optional description to briefly explain what this permission allows.
description = "My description"

try:
  descope_client.mgmt.permission.create(name=name,description=description)
  print("Successfully created permission")
except AuthException as error:
  print ("Unable to create permission.")
  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()
// 		name (str): permission name.
name := "Test Permission"
// 		description (str): Optional description to briefly explain what this permission allows.
description := "My description"

err := descopeClient.Management.Permission().Create(ctx, name, description)
if  (err != nil){
  fmt.Println("Unable to create permission.", err)
} else {
  fmt.Println("Successfully created permission")
}
// You can optionally set a description for a permission.
PermissionService ps = descopeClient.getManagementServices().getPermissionService();

String name = "My Permission";
String description = "Optional description to briefly explain what this permission allows.";

try {
    ps.create(name, description);
} catch (DescopeException de) {
    // Handle the error
}

Update a Permissions

This Descope SDK allows administrators to update an existing permission with the given various fields. It is important to note that parameters are used as overrides to the existing permission; empty fields will override populated fields.

NodeJSPythonGoJava
// Args:
// 		name (str): permission name.
const name = "Test Permission"
// 		newName (str): permission updated name.
const newName = "Updated Test Permission"
// 		description (str): Optional description to briefly explain what this permission allows.
const description = "My updated description"

const resp = await descopeClient.management.permission.update(name, newName, description)
if (!resp.ok) {
  console.log("Failed to update permission.")
  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 updated permission.")
}
# Args:
#   name (str): permission name.
name = "Test Permission"
#   new_name (str): permission updated name.
new_name = "Updated Test Permission"
#   description (str): Optional description to briefly explain what this permission allows.
description = "My updated description"

try:
  descope_client.mgmt.permission.update(name=name, new_name=new_name, description=description)
  print("Successfully updated permission")
except AuthException as error:
  print ("Unable to update permission.")
  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()
// 		name (str): permission name.
name := "Test Permission"
// 		newName (str): permission updated name.
newName := "Updated Test Permission"
// 		description (str): Optional description to briefly explain what this permission allows.
description := "My updated description"

err := descopeClient.Management.Permission().Update(ctx, name, newName, description)
if  (err != nil){
  fmt.Println("Unable to update permission.", err)
} else {
  fmt.Println("Successfully updated permission")
}
// You can optionally set a description for a permission.
PermissionService ps = descopeClient.getManagementServices().getPermissionService();

// Update will override all fields as is. Use carefully.
String newName = "My Updated Permission";
description = "A revised description";

try {
    ps.update(name, newName, description);
} catch (DescopeException de) {
    // Handle the error
}

Delete a Permissions

This Descope SDK allows administrators to delete an existing permission. It is important to note that this action is irreversible.

NodeJSPythonGoJava
// Args:
// 		name (str): The name of the permission to be deleted.
const name = "Updated Test Permission"

const resp = await descopeClient.management.permission.delete(name)
if (!resp.ok) {
  console.log("Failed to delete permission.")
  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 deleted permission.")
}
# Args:
#   name (str): The name of the permission to be deleted.
name = "Updated Test Permission"

# Permission deletion cannot be undone. Use carefully.
try:
  descope_client.mgmt.permission.delete(name=name)
  print("Successfully deleted permission")
except AuthException as error:
  print ("Unable to delete permission.")
  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()
// 		name (str): The name of the permission to be deleted.
name := "Updated Test Permission"

// Permission deletion cannot be undone. Use carefully.
err := descopeClient.Management.Permission().Delete(ctx, name)
if  (err != nil){
  fmt.Println("Unable to delete permission.", err)
} else {
  fmt.Println("Successfully deleted permission")
}
// You can optionally set a description for a permission.
PermissionService ps = descopeClient.getManagementServices().getPermissionService();

// Permission deletion cannot be undone. Use carefully.
try {
    ps.delete(newName);
} catch (DescopeException de) {
    // Handle the error
}

Load All Roles

This Descope SDK allows administrators to return all details for roles configured within the Descope instance. The response includes an array of roles and the details of each role.

NodeJSPythonGoJava
const resp = await descopeClient.management.role.loadAll()
if (!resp.ok) {
  console.log("Failed to load roles.")
  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 loaded roles.")
  console.log(resp.data)
}
try:
  resp = roles_resp = descope_client.mgmt.role.load_all()
  print("Successfully loaded roles.")
  print(json.dumps(resp, indent=4))
except AuthException as error:
  print ("Unable to load roles.")
  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()

res, err := descopeClient.Management.Role().LoadAll(ctx)
if  (err != nil){
  fmt.Println("Unable to load roles.", err)
} else {
  fmt.Println("Successfully loaded roles.")
  for _, role := range res {
    fmt.Println(role)
  }
}
// You can optionally set a description and associated permission for a roles.
RolesService rs = descopeClient.getManagementServices().getRolesService();

// Load all roles
try {
    RoleResponse resp = rs.loadAll();
    for (Role r : resp.getRoles()) {
        // Do something
    }
} catch (DescopeException de) {
    // Handle the error
}

Create a Role

This Descope SDK allows administrators to create a new role.

NodeJSPythonGoJava
// Args:
//    name (str): role name.
const name = "My Test Role"
// description (str): Optional description to briefly explain what this role allows.
const description = "My Role Description"
// permissionNames (List[str]): Optional list of names of permissions this role grants.
const permissionNames = ["TestPermission"]
//    tenantId (str): Optional Tenant ID to assign new role to specific tenant
const tenantId = "Tenant ID"

const resp = await descopeClient.management.role.create(name, description, permissionNames, tenantId)
if (!resp.ok) {
  console.log("Failed to create role.")
  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 created role.")
}
# Args:
#   name (str): role name.
name = "My Test Role"
#   description (str): Optional description to briefly explain what this role allows.
description = "My Role Description"
#   permission_names (List[str]): Optional list of names of permissions this role grants.
permission_names = ["TestPermission"]
#   tenant_id (str): Optional Tenant ID to assign new role to specific tenant
tenant_id = "Tenant ID"

try:
  descope_client.mgmt.role.create(name=name, description=description, permission_names=permission_names, tenant_id=tenant_id)
  print("Successfully created role.")
except AuthException as error:
  print ("Unable to create role.")
  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()
//    name (str): role name.
name := "My Test Role"
// description (str): Optional description to briefly explain what this role allows.
description := "My Role Description"
// permissionNames (List[str]): Optional list of names of permissions this role grants.
permissionNames := []string{"TestPermission"}
//    tenantId (str): Optional Tenant ID to assign new role to specific tenant
tenantID := "Tenant ID"

err := descopeClient.Management.Role().Create(ctx, name, description, permissionNames, tenantID)
if  (err != nil){
  fmt.Println("Unable to create role.", err)
} else {
  fmt.Println("Successfully created role.")
}
// You can optionally set a description and associated permission for a roles.
RolesService rs = descopeClient.getManagementServices().getRolesService();

String name = "My Role";
String description = "Optional description to briefly explain what this role allows.";
List<String> permissionNames = Arrays.asList("My Updated Permission");
// Pending release of Optional Tenant ID

try {
    rs.create(name, description, permissionNames);
} catch (DescopeException de) {
    // Handle the error
}

Update a Role

This Descope SDK allows administrators to update an existing role with the given various fields. It is important to note that parameters are used as overrides to the existing role; empty fields will override populated fields.

NodeJSPythonGoJava
// Args:
//    name (str): role name.
const name = "My Test Role"
//    newName (str): role updated name.
const newName = "My Updated Test Role"
//    description (str): Optional description to briefly explain what this role allows.
const description = "My Updated Role Description"
//    permissionNames (List[str]): Optional list of names of permissions this role grants.
const permissionNames = ["TestPermission", "TestPermission2"]
//    tenantId (str): Optional Tenant ID to assign new role to specific tenant
const tenantId = "Tenant ID"

const resp = await descopeClient.management.role.update(name, newName, description, permissionNames, tenantId)
if (!resp.ok) {
  console.log("Failed to update role.")
  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 updated role.")
}
# Args:
#   name (str): role name.
name = "My Test Role"
#   new_name (str): role updated name.
new_name = "My Updated Test Role"
#   description (str): Optional description to briefly explain what this role allows.
description = "My Updated Role Description"
#   permission_names (List[str]): Optional list of names of permissions this role grants.
permission_names = ["TestPermission", "TestPermission2"]
#   tenant_id (str): Optional Tenant ID to assign new role to specific tenant
tenant_id = "Tenant ID"

# Update will override all fields as is. Use carefully.
try:
  descope_client.mgmt.role.update(name=name, new_name=new_name, description=description, permission_names=permission_names, tenant_id=tenant_id)
  print("Successfully updated role.")
except AuthException as error:
  print ("Unable to update role.")
  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()
//    name (str): role name.
name := "My Test Role"
//    newName (str): role updated name.
newName := "My Updated Test Role"
//    description (str): Optional description to briefly explain what this role allows.
description := "My Updated Role Description"
//    permissionNames (List[str]): Optional list of names of permissions this role grants.
permissionNames := []string{"TestPermission", "TestPermission2"}
//    tenantId (str): Optional Tenant ID to assign new role to specific tenant
tenantID := "Tenant ID"

// Update will override all fields as is. Use carefully.
err := descopeClient.Management.Role().Update(ctx, name, newName, description, permissionNames, tenantID)
if  (err != nil){
  fmt.Println("Unable to update role.", err)
} else {
  fmt.Println("Successfully updated role.")
}
// Update will override all fields as is. Use carefully.
String newName = "My Updated Role";
description = "A revised description";
permissionNames.add("Another Permission");
// Pending release of Optional Tenant ID

try {
    rs.update(name, newName, description, permissionNames);
} catch (DescopeException de) {
    // Handle the error
}

Delete a Role

This Descope SDK allows administrators to delete an existing role. It is important to note that this action is irreversible.

NodeJSPythonGoJava
// Args:
//    name (str): The name of the role to be deleted.
const name = "My Updated Test Role"
//    tenantId (str): Optional Tenant ID to assign new role to specific tenant
const tenantId = "Tenant ID"

const resp = await descopeClient.management.role.delete(name, tenantId)
if (!resp.ok) {
  console.log("Failed to delete role.")
  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 deleted role.")
}
# Args:
#   name (str): The name of the role to be deleted.
name = "My Updated Test Role"
#   tenant_id (str): Optional Tenant ID to assign new role to specific tenant
tenant_id = "Tenant ID"

try:
  descope_client.mgmt.role.delete(name=name, tenant_id=tenant_id)
  print("Successfully deleted role.")
except AuthException as error:
  print ("Unable to delete role.")
  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()
//    name (str): The name of the role to be deleted.
name := "My Updated Test Role"
//    tenantId (str): Optional Tenant ID to assign new role to specific tenant
tenantID := "Tenant ID"

err := descopeClient.Management.Role().Delete(ctx, name, tenantID)
if  (err != nil){
  fmt.Println("Unable to delete role.", err)
} else {
  fmt.Println("Successfully deleted role.")
}
// You can optionally set a description and associated permission for a roles.
RolesService rs = descopeClient.getManagementServices().getRolesService();
// Pending release of Optional Tenant ID

// Role deletion cannot be undone. Use carefully.
try {
    rs.delete(newName);
} catch (DescopeException de) {
    // Handle the error
}