Descope Flows and Styling

Flows

Descope Flows is a visual no-code interface to build screens and authentication flows for common user interactions with your application. Descope Flows simplifies authentication work for developers by abstracting away the implementation details of authentication methods, session management, risk management, and error handling. This helps you get your app in front of users faster and safer than before.

This overview of Descope Flows will cover three features: Flow Builder, Screen Builder, and Styles. The video below gives a quick overview of these capabilities.

Further details about flow builder shortcuts, custom flows, disabling flows, and exporting/importing flows can be found under Managing Flows.

Flow Builder

The Flow Builder gives you the tools to define authentication-related application logic in a visual workflow. You can create Flows in the Descope UI for user sign-up, sign-in, MFA, and other similar use cases. Once created, Flows can be called from your chosen Descope Client SDK.

Flows are made up of Steps, which can be of different types. For example:

  • An Action Step can activate authentication methods (like social login or magic link verification), update user information, or call third-party APIs.
  • A Condition Step can act like a decision tree and check an “if-then” type of condition.
  • A Screen Step can help you build login screens that adhere to your brand and styling.
Descope flow example.

Screen Builder

The Screen Builder is a widget-based interface that enables you to build user-facing login and sign-up screens from the Descope UI. Whenever you add a Screen Step while creating Flows, you can design the associated screen using the Screen Builder.

You can easily create screens with widgets such as:

  • Inputs that capture user details like email IDs and phone numbers
  • Buttons for users to select actions such as login methods (SSO, Social Logins, etc.)
  • Text boxes that can display login errors and success messages
  • Links that can point to your privacy policy, terms of service, or other destinations
  • Images that can display your brand’s logo or other custom images
  • Containers that help you layer and position other widgets
Descope screen builder example.

Alignment And Direction

Buttons, texts, icons, and other inputs support alignment and direction. Alignment can re-organize elements in three screen positions - left, center, and right. Configuring direction is used for input elements to support RTL (Right To Left) languages such as Hebrew, Arabic, Farsi, etc. Clicking on the component will reveal the options available.

Descope Text Direction and Allign example.

Available Configurations

  • In Text, Link, and error messages, you can control text alignment and direction.
  • In all other components, text alignment is defined according to text direction and alignment available in the component configuration.
  • The button’s text is always aligned to the middle, so only the Oauth logo changes direction.
  • Text in a divider is always aligned to the middle.
  • Inputs and boolean have only left or right alignment to make it easier for development.

Styles

Styles help you create and scale user-facing screens while adhering to your brand’s guidelines. You can add your brand’s logo, colors, and other design elements in a central location in the Descope UI. Any screens you create with the Screen Builder will take on the brand elements from Styles.

If your brand goes through updates, Styles also makes it easy to propagate those updates to all your user-facing screens.

Exporting and Importing Theme from Console

Once you have defined your styles, you can export or import the styles using the up and down arrows at the top right of the styles page. This feature allows you to backup your current styles, or migrate them between your projects.


Descope screen styling example.

Exporting and Importing Styles from the Management SDK

The Descope SDK allows you to import and export themes.

Install SDK

NodeJSPythonGoJava
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>

Import and initialize Management SDK

NodeJSPythonGoJava
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());

Export Theme

Use the code below to export a theme.

NodeJSPythonGo
import * as fs from 'fs';

// Args
//   None

const resp = await descopeClient.management.theme.export()
if (!resp.ok) {
  console.log(resp)
  console.log("Unable to export theme.")
  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 exported theme.")
  console.log(resp.data)
  let data = JSON.stringify(resp.data, null, 2);
  fs.writeFile('theme.json', data, (err) => {
      if (err) throw err;
      console.log('Theme written to file');
  });
}
import json

# Args
#   None
try:
  resp = descope_client.mgmt.flow.export_theme()
  print ("Successfully exported theme")
  print (resp)

  json_object = json.dumps(resp, indent=4)
  with open("theme.json", "w") as outfile:
    outfile.write(json_object)
except AuthException as error:
  print ("Failed to export theme")
  print ("Status Code: " + str(error.status_code))
  print ("Error: " + str(error.error_message))
import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"strings"
)

// Args
//   None
res, err := descopeClient.Management.Flow().ExportTheme()
if err != nil {
  fmt.Print("Failed to export theme", err)
} else {
  data, err :=json.Marshal(res)
  if err == nil {
    fileName := fmt.Sprintf("theme.json")
    if err == nil {
      err = os.WriteFile(fileName, data, 0644)
      fmt.Println("Successfully exported flow")
    }
  }
}

Import Theme

Use the code below to import a theme.

NodeJSPythonGo
import * as fs from 'fs';

// Args
// theme (Theme): the theme to import. dict in the format
//    {"id": "", "cssTemplate": {} }

let data = fs.readFileSync('theme.json');
let jsonData = JSON.parse(data);
const theme = jsonData["theme"]

const resp = await descopeClient.management.theme.import(theme)
if (!resp.ok) {
  console.log(resp)
  console.log("Unable to import theme.")
  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 imported theme.")
  console.log(resp.data)
}
import json

# Args
# theme (Theme): the theme to import. dict in the format
#    {"id": "", "cssTemplate": {} }

with open("theme.json", "r") as infile:
  themeJson = json.load(infile)
theme = themeJson["theme"]
try:
  resp = descope_client.mgmt.flow.import_theme(theme=theme)
  print ("Successfully imported theme")
  print (resp)
except AuthException as error:
  print ("Failed to import theme")
  print ("Status Code: " + str(error.status_code))
  print ("Error: " + str(error.error_message))
import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"strings"
)

// Args
// theme (Theme): the theme to import. dict in the format
//    {"id": "", "cssTemplate": {} }
raw, err := os.ReadFile("theme.json")
if err != nil {
  return err
}
theme := &descope.Theme{}
err = json.Unmarshal(raw, theme)
if err != nil {
  return err
  }
res, err := descopeClient.Management.Flow().ImportTheme(theme)
if err != nil {
fmt.Print("Failed to import flow", err)
} else {
  fmt.Print("Successfully imported flow", res)
}