Descope Security: Best Practices

Using Descope for your authentication is a great start to implementing customizable and secure authentication in your applications. However, there are some best practices that you should be aware of when building out your application, to ensure that it is as secure as possible.

This guide includes a list of some of the most common vulnerabilities, as well as how to customize your Descope project and application to protect against them.

Topics


  1. Handling Refresh Tokens in Cookies
  2. How to Personalize Email Template
  3. What to Store in Custom Claims
  4. Usage of Access Keys
  5. JWK Key Rotation
  6. Scope of Descope Management Keys
  7. Using RBAC to Implement Resource Permissions
  8. Using Approved Domains
  9. Managing Descope Components Not in Use
  10. SAML Authentication for Enterprise Customers
  11. Using Custom Apps with OAuth
  12. Using SSR Frameworks
  13. Using Content Security Policy

Handling Refresh Tokens in Cookies


It is recommended to store your refresh tokens in cookies, rather than in the browser localStorage. This is important is because it will help mitigate the risk of XSS (Cross-site-scripting) attacks, since tokens in localStorage are accessible via JavaScript. Since Descope uses httpOnly cookies, the refresh token will not be accessible using JavaScript. Descope cookies are also sameSite=strict and protected against cross-site request forgery attacks.In the Console, under Token Response Method you can configure Descope to store your tokens in an httpOnly cookie instead. In order to use cookies, you must provide a custom domain and configure a CNAME for this as well.

Descope manage session in cookies.

As a best practice, make sure to limit your custom domain scope as much as possible (e.g. app.company.com instead of company.com). This more specific scope will limit the number of places your browser will send the cookie. If your cookie only needs to be sent with requests using the domain app.company.com, then it should be that rather than the entire website (company.com).

Note: If you are using cookies to store the refresh token, you might encounter a 401 Unauthorized when testing your app locally since localhost will differ from the custom domain you configure a CNAME for. To handle this, it's recommended that you follow this guide to configure multiple descope developer environments.

How to Personalize Email Template


You should make sure that emails being sent out to your users for things like OTP and Magic Link are using your specific website domain. You can do this by establishing a Sendgrid or SMTP Connector under the Authentication Methods page. That way, you have full control over both the email contents and restrictions imposed on your specific domain.

To create a Sendgrid Connector, sign up for Sendgrid here.

What to Store in Custom Claims

You can add custom claims to your JWT, by adding the Custom Claims action in your flow.

Descope custom claims within JWT.

However, you will need to consider these two things when using custom claims in your flow:

  1. Cookie Size - Since cookies have a maximum size of 4kb supported by browsers, storing information in a custom claim while using cookies (which is the recommended way to handle refresh tokens) might be a problem. It’s best to not store too much in the JWT for this reason.
  2. Sensitive Information - You should never store any sensitive user information in a JWT, as they are just Base 64 encoded tokens, and that sensitive information could be exposed if a JWT was intercepted by a malicious hacker.

How to Use the Risk Calculator


When designing your flow, it is worth considering the use of our built-in risk calculator, to determine whether or not a user should re-authenticate. The risk calculator is baked into flows, as an Action called, Risk Calculator.

Descope risk calculator settings..

This will return a risk score that can be applied to custom logic using a Condition block, like this:


Descope risk calculator conditional.

Typically speaking, an MFA auth flow will be attached to this block if the risk score is sufficiently high, like this:


Descope risk based MFA flow.

The threat risk will be calculated via a variety of parameters, such as the duration between the current time and the last login, as well as various other factors. It is important to include this in your flow, to maintain total security between user sessions.

Usage of Access Keys


Access Keys are primarily used for OIDC provider integrations as well as M2M (machine-to-machine) authentication.

If you are utilizing access keys, make sure that you use the expiry mechanism and rotate the keys regularly, to make sure that your server and data are secure. Access Keys can be created by any one of our SDKs, or through the Descope Console here.


Descope access key generation.

JWK Key Rotation


With Descope, all the public keys accessible via a public JWKS endpoint, and your private keys are controlled in the Descope Console under Project Settings.

JWK Key rotation occurs regularly, once a day by default, ensuring smooth a transition to the next key without customer disruption. This ensures ongoing security with minimal impact on active sessions. On-demand JWK key rotation is also available, often used in case of a security incident or other custom security procedure, allowing management of JWKs at the project level by Company and Project Admins.

Note: After 12 JWK rotations, users with active sessions will have to re-login as JWKs that are more than 12 rotations away from the current one will be invalidated.


Descope jwk rotation.

When you click on Rotate Key, you will be able to either Rotate (will not affect user sessions) or Rotate and Revoke (will force all users to re-login).
Descope jwk rotate or rotate and revoke.

Scope of Descope Management Keys


When handling User Management requests with our SDKs or APIs, you will need a Management Key. You can read more about our management keys on our docs page.

When using the management keys, it’s important to use best practices with these secret keys, such as not embedding them in your frontend, and rotating them frequently. However you should also make sure that they are scoped to a specific set of projects, rather than all of your projects, to limit access to the User Management requests if the key is uncovered by a malicious entity. You can set the scope of the management key in the Console:


Descope management key generation.

Using RBAC to Implement Resource Permissions


When controlling resource permissions on your website, you will want to usually implement some kind of Authorization protocol, typically RBAC (Role-Based Access Control). Role-based access control (RBAC) is a mechanism that authorizes users to access protected resources based on their role within an organization. In order to implement RBAC with Descope, you will need to follow the steps in the guide, on our docs page.

Make sure that you have defined the tenants, roles, and permissions for each role explicitly. Doing so, will ensure that your website is secure and access to protected resources is properly controlled.

Using Approved Domains


Descope recommends that you configure a list of approved domains for your site redirects. Under Project Settings, you can create a list of approved domains that are allowed for redirect and verification URLs. If your application has a security vulnerability, this will prevent a malicious hacker from generating redirect links to unsafe websites.


Descope approved domains configuration.

Managing Descope Components Not in Use


When you are not actively using a Flow or an authentication method in Descope, you can disable it from the Console.

To disable a flow, navigate to the Flows page, and select disable:


Descope disable components not in use flows.

To disable an authentication method,

To disable an authentication method, navigate to the Authentication Methods page, and un-select Enable method at the top of the settings menu for each of the methods:

Descope disable components not in use auth methods.

You can also do this with the SDKs

SAML Authentication for Enterprise Customers


If you are an enterprise customer, who wants to implement Descope flows as part of your B2B company-side authentication, we recommend using SAML SSO rather than OIDC or any other authentication method. The reason is because of the security advantages that SAML provides, in this particular enterprise use case.

When using SSO with SAML, the identity provider stores all login information and thus the application does not need to store any user credentials on their system. Furthermore, since identity providers specialize in SAML, IdPs have comprehensive solutions to protect against common password attacks, such as MFA. If you would like to read more about SAML and how it works, you can refer to our learning center article.

You can configure SAML SSO for your organization from the Company Settings page:


Descope configure SAML on company level.

Using Custom Apps with OAuth

It is recommended by Descope that you create a custom login application if you're using OAuth as an authentication method.

For example, if you're using Facebook Social Login, rather than using our app, create a custom app with Facebook to handle the OAuth login, to avoid any potential differences between your specific implementation and app policies set by the generic app managers (such as Facebook).

Using SSR Frameworks

If you're using frameworks that use server-side rendering (SSR), such as Next.js, Nuxt, etc, you will need to be aware of some differences in how the Descope SDKs are used. The following items are things to keep in mind, as there are security concerns with SSR, as well as SDK component limitations:

  1. In a next-app, you will need both React and Node SDKs.
  2. The Descope Flow / Web component should only be rendered on the client side. There are two main ways you can do this, as shown below:
Next.js support dynamic imports, which allow you to import JavaScript modules dynamically and works collaboratively with SSR as well. With next/dynamic, you can use the ssr: false object to disable server-side rendering of your dynamically imported component:
const DescopeWC = dynamic(
  async () => {
    const { Descope } = await import("@descope/react-sdk");
    const EnhancedDescope = (props: React.ComponentProps<typeof Descope>) => (
      <Descope {...props} />

    )
    EnhancedDescope.displayName = "EnhancedDescope"
    return EnhancedDescope;
  },
  {
    ssr: false,
  }
);

If you're using Next 13, you can also use "use client" directive instead of dynamic imports at the top of your file:

"use client";

export function ... {
  return (
    <Descope
      flowId="sign-up-or-in"
      onSuccess = {(e) => console.log(e.detail.user)}
      onError={(e) => console.log('Could not log in!')}
      theme="light"
    />
  )
}

  1. Manage your session tokens in cookies, as passing your tokens to the Next backend isn't necessary. You can do this by wrapping AuthProvider like so:
  <AuthProvider
    projectId={process.env.NEXT_PUBLIC_DESCOPE_PROJECT_ID!}
    // Use this option when you need to use session token in the SSR render cycle
    sessionTokenViaCookie
  >
      <Component {...pageProps} />
  </AuthProvider>

If you're interested on how we've implemented Descope with Next.js, we have a sample app on our GitHub page.



If you have any other questions about Descope or our flows, feel reach to reach out to us!

Content Security Policy (CSP)

Content Security Policy (CSP) is a security standard introduced to prevent various attacks, including Cross-Site Scripting (XSS) and data injection attacks. It allows web developers to specify the domains the browser should consider valid sources of executable scripts for a given webpage. By doing this, CSP can effectively reduce the risk of XSS attacks by specifying which sources are trusted, preventing browsers from executing scripts not approved as part of the policy.

If you choose to utilize CSP with Descope flows, below is an example of a valid CSP configuration, including the necessary references to static.descope.com.
<meta
    http-equiv="Content-Security-Policy"
    content="connect-src 'self' static.descope.com api.descope.com;
    style-src 'unsafe-inline' fonts.googleapis.com;
    img-src static.descope.com content.app.descope.com;
    font-src fonts.gstatic.com;
    script-src 'self' static.descope.com;"
/>