Deployments and TestingCI/CD Tools

Terraform Provider

Terraform is an infrastructure-as-code tool that lets you define your environment configuration in .tf files and apply it consistently across development, staging, and production. Instead of configuring environments by hand, you declare a desired state and let Terraform manage it. You can read more in the Terraform introduction.

Descope publishes a Terraform provider for managing projects and their configuration.

Configurations written for provider versions before v1

Provider v1 introduced a new model, so configurations written for earlier versions might need substantial changes to work with it. Earlier versions nested project settings inside descope_project, in blocks such as authentication = {...} or connectors = {...}.

If your configuration uses those blocks, follow the upgrading from v0.3 guide before you apply the examples below.

Important

Terraform is best suited for managing infrastructure and configuration that should be consistent across environments. Dynamic elements of a project, such as individual users, tenants, SSO connections, and SCIM configurations, are not typically managed by Terraform. These are unique to each project or environment and are generally handled through the Descope Console, SDKs, or APIs, and not as infrastructure-as-code.

Project-level SSO settings, SSO Setup Suite options, and the hosted Admin Portal are managed in Terraform. See SSO Settings and Admin Portal below.

How Resources Map to a Project

descope_project manages only the project itself: its name, environment, tags, and deletion_protection. Everything inside the project has its own resource. Settings resources such as descope_session_settings are singletons, so each project has at most one of each. Entity resources such as descope_role or descope_http_connector describe one object each.

Terraform only manages the objects you declare. If your configuration has no descope_sso_settings resource, Terraform leaves the project's SSO settings alone. If you declare a settings resource, Terraform manages every attribute of it, and an apply can reset attributes you leave out to the provider defaults.

Import existing settings before you manage them

Creating a settings resource, descope_styles, descope_fga_schema, a flow, or a widget in an existing project overwrites the configuration already there. Import these objects first, as described in Importing an Existing Project. The provider warns during planning when one of them is about to be created in an existing project.

Prerequisites

The Terraform provider requires a paid Descope license (Pro+). Contact support@descope.com with any licensing questions.

  • Terraform CLI 1.0 or later installed. Adopting an existing project with import blocks needs 1.5 or later.
  • A Management Key from Company Settings. To create or manage projects via Terraform, the key needs a company-level role with the appropriate permissions (see Management Key Roles).

Using the Terraform Provider

Provider Configuration

The required_providers block below tells Terraform to download the Descope provider and pins its version:

terraform {
  required_providers {
    descope = {
      source  = "descope/descope"
      version = "~> 1.0"
    }
  }
}

Warning

Never hardcode your management key in Terraform configuration files, because doing so risks exposing it in version control. Use environment variables or a secrets manager instead.

The provider reads its credentials from these environment variables:

VariableDescription
DESCOPE_MANAGEMENT_KEYA valid management key for your Descope company
DESCOPE_BASE_URLOverride the Descope API base URL (optional, for testing)

You can export the management key in the shell that runs Terraform:

export DESCOPE_MANAGEMENT_KEY="K2..."

With those set, the provider block needs no additional configuration:

provider "descope" {}

You would then run terraform init to download the provider:

terraform init

If you need to configure credentials explicitly, for example in a module, you can pass the key as a sensitive variable:

variable "descope_management_key" {
  type      = string
  sensitive = true
}

provider "descope" {
  management_key = var.descope_management_key
}

Creating a Project

The descope_project resource creates the project. Other resources reference it through descope_project.myproject.id.

resource "descope_project" "myproject" {
  name        = "project-name"
  environment = "production"
  tags        = ["foo", "bar"]
}

Attributes like tags support dynamically computed values. The next example appends tags from a variable:

variable "additional_project_tags" {
    type     = list(string)
    nullable = false
}

resource "descope_project" "myproject" {
    name = "project-name"
    tags = [
      "foo",
      ...var.additional_project_tags
    ]
}

Deletion protection

The provider protects projects from deletion by default. A terraform destroy of a descope_project fails until you set deletion_protection = false and apply that change first. Federated apps and inbound apps have the same protection.

Importing an Existing Project

If you already have a Descope project and want to bring it under Terraform management, you can generate the configuration from the live project instead of writing it by hand. Because the provider splits a project into many resources, the provider repository ships a tool called tfexport that writes all of them at once.

Note

This workflow requires Terraform CLI 1.5 or later, because import blocks are not available in earlier versions.

1. Export the project

The tfexport tool reads your project and writes a directory of .tf files, with one resource for each supported object and an import block for each resource. Its README explains how to install it. The command below exports project P... into ./my-project:

export DESCOPE_MANAGEMENT_KEY="K..."

tfexport -project P... -out ./my-project

If your configuration already has a descope_project resource, you can pass -project-address descope_project.myproject so the generated resources reference it instead of declaring a new project.

tfexport doesn't export access keys, management keys, Descopers, engines, users, or tenants.

2. Fill in secrets

The Descope API never returns secrets such as connector credentials, so tfexport declares them as sensitive variables. You would set their values in a terraform.tfvars file that you keep out of version control, or as TF_VAR_<name> environment variables.

3. Plan and review

Run a plan and check that it imports everything and changes nothing except secrets:

terraform plan

Plans that update secrets in place are normal. The API reports that the project holds a secret but not its value, so each imported resource with a stored secret plans an update that resends it from its variable. You should not apply the plan if it creates a resource that already exists, changes a value you configured, or clears a secret.

4. Apply to import the project into state

Once the plan looks right, apply it:

terraform apply

The import blocks tell Terraform to attach the existing objects to state rather than create new ones. Once the apply succeeds, you can delete the import blocks, and later plan and apply runs manage the project as usual.

Importing a single resource

You can also adopt one object at a time with an import block and terraform plan -generate-config-out="generated.tf". The import ID depends on the resource's shape, as the following table shows.

Resource shapeImport IDExamples
Project and company-level resources<id>descope_project, descope_management_key, descope_descoper
Singleton settings<project_id>descope_project_settings, descope_sso_settings, descope_styles, descope_fga_schema, descope_admin_portal
Entity in a project<project_id>/<id>descope_role, descope_http_connector, descope_flow, descope_inbound_app, descope_access_key, descope_engine
Entity scoped to a method or app<project_id>/<scope>/<id>descope_email_template (<project_id>/invite/<template_id>), descope_app_role

The import block below adopts an existing project's SSO settings:

import {
  to = descope_sso_settings.main
  id = "P..." # your project ID
}

Inbound apps and client_type

When you import an existing inbound app, you can leave client_type out, and Terraform keeps the app's current type. If you do set it, set it to the app's current type, because some changes replace the app with a new client ID and secret. See Inbound App Client Type Changes.

Examples

Each example below is a standalone resource that references descope_project.myproject through project_id.

Project Settings

Project-level settings are split across two singleton resources. descope_project_settings holds domain, security, and test user settings. descope_session_settings holds token lifetimes and inactivity rules.

resource "descope_project_settings" "main" {
  project_id       = descope_project.myproject.id
  app_url          = "https://app.example.com"
  approved_domains = ["example.com", "app.example.com"]
}

resource "descope_session_settings" "main" {
  project_id               = descope_project.myproject.id
  refresh_token_expiration = "3 weeks"
  session_token_expiration = "15 minutes"
  refresh_token_rotation   = true
  enable_inactivity        = true
  inactivity_time          = "1 hour"
}

Full project settings schema reference · Full session settings schema reference

Invite Settings

The descope_invite_settings resource controls user invitation behavior. A custom invitation email is a separate descope_email_template resource with method = "invite", which the invite settings select by ID.

resource "descope_email_template" "invite" {
  project_id = descope_project.myproject.id
  method     = "invite"
  name       = "Invite Email"
  subject    = "You've been invited"
  html_body  = "<p>Click <a href='{{link}}'>here</a> to accept your invitation.</p>"
}

resource "descope_invite_settings" "main" {
  project_id           = descope_project.myproject.id
  require_invitation   = true
  invite_url           = "https://example.com/invite"
  add_magiclink_token  = true
  expire_invited_users = true
  invite_expiration    = "2 weeks"
  send_email           = true
  email_template_id    = descope_email_template.invite.id
}

The expire_invited_users flag causes invited user accounts to expire if the invitation is not accepted within the invite_expiration duration. The invite_expiration field accepts human-readable durations such as "2 weeks" or "4 days", with a minimum value of "1 hour".

To send invitations through your own email provider, you can set email_connector_id to the ID of an email connector resource. An empty value selects the built-in Descope delivery service.

Full invite settings schema reference · Full email template schema reference

Authorization

Each permission and each role is its own resource. A role lists its permissions by name, so referencing descope_permission.<name>.name lets Terraform create the permission before the role.

resource "descope_permission" "read_data" {
  project_id  = descope_project.myproject.id
  name        = "read:data"
  description = "Read access to application data"
}

resource "descope_role" "viewer" {
  project_id  = descope_project.myproject.id
  name        = "viewer"
  description = "Can read data"
  permissions = [descope_permission.read_data.name]
}

Full role schema reference · Full permission schema reference

FGA Schema

The descope_fga_schema resource manages your project's Fine-Grained Authorization schema as code. In the Descope console, you can open the Authorization > FGA page and copy the schema text from the editor. The example below loads the schema from a file:

resource "descope_fga_schema" "main" {
  project_id = descope_project.myproject.id
  schema     = file("${path.module}/fga-schema.txt")
}

Note

The schema value must start with model AuthZ. Setting an empty schema, or destroying the resource, clears the project's FGA schema and removes any FGA relations that depend on it.

Full FGA schema reference

Authentication

Each authentication method has its own singleton settings resource. The example below configures magic link expiration and password policy:

resource "descope_magiclink_settings" "main" {
  project_id      = descope_project.myproject.id
  expiration_time = "1 hour"
}

resource "descope_password_settings" "main" {
  project_id            = descope_project.myproject.id
  lock                  = true
  lock_attempts         = 3
  min_length            = 8
  disallow_email_match  = true
  disallowed_characters = "'\""
  enforce_strength      = "very_strong"
}

The other methods follow the same pattern with descope_otp_settings, descope_enchantedlink_settings, descope_embeddedlink_settings, descope_totp_settings, descope_oauth_settings, and descope_sso_settings. Custom social login providers use descope_oauth_provider.

Full magic link settings schema reference · Full password settings schema reference

Passkeys

The descope_passkey_settings resource configures passkey authentication. The android_fingerprints field restricts which Android apps may use passkeys by their SHA-256 APK key hash fingerprint (colon-separated hex format). An empty list places no restriction on Android apps.

Once you list a fingerprint, only the Android apps you list can use passkeys, so you should include every signing key your apps use. Web and iOS clients are unaffected. See Passkeys Settings for details.

resource "descope_passkey_settings" "main" {
  project_id       = descope_project.myproject.id
  top_level_domain = "example.com"
  display_name     = "Example App"
  android_fingerprints = [
    "AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89"
  ]
}

Full passkey settings schema reference

Authentication Access Policy

The descope_auth_access_rule and descope_auth_access_policy resources manage the rules that allow or block an authentication based on the request's IP address or country, or the user's email, login ID, or phone. The policy also sets the order in which Descope evaluates the rules.

Full auth access rule schema reference · Full auth access policy schema reference

Attributes

Custom user and tenant attributes are separate resources. The id attribute is the machine name of the attribute, and changing it replaces the resource.

resource "descope_user_attribute" "department" {
  project_id = descope_project.myproject.id
  id         = "department"
  name       = "Department"
  type       = "string"
}

resource "descope_tenant_attribute" "plan" {
  project_id     = descope_project.myproject.id
  id             = "plan"
  name           = "Plan"
  type           = "multiselect"
  select_options = ["A", "B"]
}

Access key attributes use descope_access_key_attribute.

Full user attribute schema reference · Full tenant attribute schema reference

Connectors

Each connector type has its own resource named after the type, such as descope_http_connector or descope_aws_s3_connector. The example below shows bearer token auth on an HTTP connector, role-based auth on an S3 audit connector, and a reCAPTCHA Enterprise connector:

# Bearer token authentication
resource "descope_http_connector" "eligibility" {
  project_id     = descope_project.myproject.id
  name           = "Test HTTP"
  description    = "A Description"
  base_url       = var.http_connector_base_url
  use_static_ips = false
  authentication = {
    bearer_token = var.http_connector_secret
  }
}

# Role-based authentication
resource "descope_aws_s3_connector" "audit" {
  project_id    = descope_project.myproject.id
  name          = "S3 Audit Connector"
  description   = "A Description"
  auth_type     = "assumeRole"
  role_arn      = "arn:aws:iam::YOUR_ACCOUNT_ID:role/your-connector-role"
  external_id   = "YOUR_EXTERNAL_ID"
  region        = "us-east-1"
  bucket        = "your-audit-logs-bucket"
  audit_enabled = true
}

# reCAPTCHA Enterprise
resource "descope_recaptcha_enterprise_connector" "bot_protection" {
  project_id        = descope_project.myproject.id
  name              = "reCAPTCHA Enterprise"
  description       = "Bot protection"
  config_project_id = var.recaptcha_project_id
  site_key          = var.recaptcha_site_key
  api_key           = var.recaptcha_api_key
  action            = "login"
}

In the reCAPTCHA Enterprise connector, config_project_id is the Google Cloud project ID, and project_id is the Descope project.

Note

For AWS connector role setup requirements, including trust policy configuration, refer to the specific connector documentation (S3, SES, etc.).

Full HTTP connector schema reference · Full AWS S3 connector schema reference · Full reCAPTCHA Enterprise connector schema reference

JWT Templates

The descope_jwt_template resource configures a custom JWT claim template. You can include a description, control which standard claims are included, and add security features like JTI. The type attribute selects user session JWTs (user) or access key JWTs (key).

resource "descope_jwt_template" "app_claims" {
  project_id  = descope_project.myproject.id
  name        = "app-claims"
  description = "Adds subscription tier and org context to user JWTs"
  type        = "user"
  template    = jsonencode({
    tier   = "@user.customAttributes.subscriptionTier"
    org_id = "@user.tenants[0].tenantId"
  })

  # Exclude the permissions claim to keep tokens lean
  exclude_permission_claim = true

  # Add a unique JWT ID for replay attack prevention
  add_jti_claim = true

  # Move the user ID to a new dsub claim, allowing sub to be customized
  override_subject_claim = true
}

A template takes effect when the session settings reference it by ID. You would set user_jwt_template = descope_jwt_template.app_claims.id on descope_session_settings, or access_key_jwt_template for a key template.

Full JWT template schema reference

SSO Settings and Admin Portal

Terraform manages project-level SSO with descope_sso_settings, which also holds the SSO Setup Suite options in sso_suite_settings. The hosted Admin Portal has its own descope_admin_portal resource.

The table below shows where each SSO concern lives:

In TerraformNot in Terraform (use Setup Suite, Console, or Management API)
Project SSO auth method settings (descope_sso_settings)Per-tenant SAML/OIDC connections (IdP metadata, certs, maps)
SSO Setup Suite UI options (sso_suite_settings)Per-tenant SCIM tokens and base URLs
Default post-auth redirect URL (redirect_url)Individual tenants and users
SSO invite email template (descope_email_template with method = "sso")Day-to-day customer IdP changes
Admin Portal enablement, style, and widget list (descope_admin_portal)

You can automate per-tenant connections with the Management SSO SDKs or tenants SSO API, or hand customers the SSO Setup Suite.

Project SSO Settings

These attributes map to Project-level SSO settings in the Console.

resource "descope_sso_settings" "main" {
  project_id = descope_project.myproject.id

  # Disable direct SSO API/SDK starts (Flows that use SSO still work)
  disabled = false

  # Merge SSO logins into existing users with the same email
  merge_users = true

  # Default post-auth redirect (tenant, SDK, or API can override)
  redirect_url = "https://app.example.com/auth/callback"

  # Same SSO domain allowed on more than one tenant
  allow_duplicate_domains = false

  # SSO-mapped roles replace the user's existing roles
  allow_override_roles = true

  # Prefer group-based role maps when priorities conflict
  groups_priority = true

  # Tenant SSO configs must include an SSO domain
  require_sso_domains = true

  # Tenant SSO configs must set a groups attribute name
  require_groups_attribute_name = true

  # Reject login if the assertion email domain is not a configured SSO domain
  block_if_email_domain_mismatch = true

  # Leave email unverified after SSO
  mark_email_as_unverified = false

  # Allow converting a user into an SSO connection even if they belong to other tenants too
  allow_merge_users_with_multiple_tenants = false

  # Only allow mapping attributes listed below
  limit_mapping_to_mandatory_attributes = false

  # Attributes that must be present and mappable from the IdP
  # (id = Console "Machine Name", set custom = true for custom attributes)
  mandatory_user_attributes = [
    { id = "email" },
    { id = "name" },
    { id = "department", custom = true },
  ]

  # SSO Setup Suite, see the next subsection
  sso_suite_settings = {
    style_id                  = "customer-facing-style"
    hide_sso                  = false
    hide_scim                 = false
    hide_saml                 = false
    hide_oidc                 = false
    hide_role_mapping         = false
    hide_fga_mapping          = false
    hide_domains              = false
    hide_jit_guide            = false
    force_domain_verification = false
    show_help_contact         = true
    support_email             = "it-support@example.com"
  }

  # Optional: custom template for SSO invite emails
  email_template_id = descope_email_template.sso_invite.id
}

resource "descope_email_template" "sso_invite" {
  project_id = descope_project.myproject.id
  method     = "sso"
  name       = "SSO Invite"
  subject    = "You've been invited to sign in"
  html_body  = "<p>Click <a href='{{link}}'>here</a> to accept.</p>"
}

The following table describes each attribute:

AttributePurpose
disabledBlocks direct SSO via API/SDK. It does not disable SSO inside Flows.
merge_usersMerge SSO users into existing accounts with the same email (merging guide)
redirect_urlProject default post-auth URL (settings). Required for IdP-initiated unless set per tenant.
allow_duplicate_domainsAllow the same SSO domain on multiple tenants
allow_override_rolesLet SSO group to role maps override existing roles (details)
groups_priorityEnable groups priority when resolving role maps
require_sso_domainsForce an SSO domain on each tenant SSO config (settings)
require_groups_attribute_nameForce a groups attribute name on each tenant SSO config
block_if_email_domain_mismatchFail login when the email domain doesn't match the SSO domains
mark_email_as_unverifiedDo not mark email verified after SSO
allow_merge_users_with_multiple_tenantsAllow converting a user into an SSO connection even if they belong to other tenants too (details)
mandatory_user_attributesRequired Descope attributes from SSO (id, optional custom)
limit_mapping_to_mandatory_attributesOnly allow maps into those mandatory attributes
sso_suite_settingsHosted Setup Suite UI (below)
email_connector_idID of the email connector for SSO invite emails. An empty value selects the built-in Descope delivery service.
email_template_idID of a descope_email_template with method = "sso" for SSO invite emails

Full SSO settings schema reference

SSO Setup Suite Settings

The sso_suite_settings attribute of descope_sso_settings controls what customer IT admins see in the SSO Setup Suite. It mirrors the SSO Suite settings in the Console.

AttributePurpose
style_idStyle applied to the suite (must exist in the project)
hide_ssoHide the whole SSO configuration, leaving a SCIM-only suite
hide_saml / hide_oidcHide SAML or OIDC setup
hide_scimHide SCIM in the suite
hide_groups_mappingHide the groups mapping UI
hide_role_mappingHide only the role mapping UI
hide_fga_mappingHide only the FGA mapping UI
hide_domainsHide SSO domains UI
hide_jit_guideHide the JIT provisioning guide in the hosted UI
force_domain_verificationAllow only verified domains
oidc_login_id_attributeThe IdP claim the suite suggests as the Login ID for OIDC configurations (defaults to sub)
show_xaaShow the Cross-App Access (XAA) section, which stays hidden unless this is set
show_help_contactShow a help/support contact in the suite
support_emailEmail shown as that support contact

Admin Portal

The descope_admin_portal resource configures the hosted Admin Portal, including which widgets appear and which style to use. Each widget_id usually references a widget managed with descope_widget (see Widgets).

resource "descope_admin_portal" "main" {
  project_id = descope_project.myproject.id
  enabled    = true
  style_id   = "admin-portal-style"

  widgets = [
    {
      type      = "user-management-widget"
      widget_id = descope_widget.user_management.widget_id
    },
  ]
}

The following table describes each attribute:

AttributePurpose
enabledTurn the hosted Admin Portal on or off. At least one widget is required when enabled = true.
style_idStyle for the portal. An empty value uses the default style.
widgetsOrdered list of widgets. Each entry needs type and widget_id.

type is the type of the widget, which is one of user-management-widget, role-management-widget, access-key-management-widget, user-access-key-management-widget, audit-management-widget, user-profile-widget, applications-portal-widget, tenant-profile-widget, or outbound-applications-widget. widget_id is the ID of the widget instance you want to host. You can create those widgets under Widgets and Admin Widgets, then manage them with descope_widget.

Full admin portal schema reference

If Descope is the IdP for other apps (outbound SAML/OIDC), you configure those with descope_oidc_app and descope_saml_app. See Applications and Federated Apps. Federated apps are separate from tenant SSO, where customers' IdPs sign users into your app.

Flows and Styles

If you've designed custom flows in the Descope console, you can export them and load them with Terraform:

  1. In the Descope console, go to Authentication Flows.
  2. Open the flow you want to manage, click the export button, and save the JSON file (for example, flows/sign-up-or-in.json).
  3. Optionally, export your flow styles from the same screen and save them as flows/styles.json.
  4. Reference the files in your configuration.

Each flow is a descope_flow resource keyed by its flow_id, and the project theme is a single descope_styles resource:

resource "descope_flow" "sign_up_or_in" {
  project_id = descope_project.myproject.id
  flow_id    = "sign-up-or-in"
  data       = file("${path.module}/flows/sign-up-or-in.json")
}

resource "descope_styles" "main" {
  project_id = descope_project.myproject.id
  data       = file("${path.module}/flows/styles.json")
}

Full flow schema reference · Full styles schema reference

Widgets

You can build widgets, such as a user management widget or a role management widget, under Widgets in the Descope console. You can then export a widget and manage it as code the same way as flows:

  1. In the Descope console, go to Widgets and open the widget you want to manage.
  2. Export its JSON and save it (for example, widgets/user-management.json).
  3. Reference the file in a descope_widget resource with a machine-readable widget_id.
resource "descope_widget" "user_management" {
  project_id = descope_project.myproject.id
  widget_id  = "user-management-widget"
  data       = file("${path.module}/widgets/user-management.json")
}

Full widget schema reference

Full Terraform Plan Example

The configuration below creates a project and manages its session settings, password policy, SSO settings, a role, an HTTP connector, a flow, and an inbound app together. Every resource points at the same project through project_id, and the session settings reference the JWT template by ID.

terraform {
  required_providers {
    descope = {
      source  = "descope/descope"
      version = "~> 1.0"
    }
  }
}

provider "descope" {}

variable "http_connector_base_url" {
  type = string
}

variable "http_connector_secret" {
  type      = string
  sensitive = true
}

resource "descope_project" "my_project" {
  name        = "my-project"
  environment = "production"
  tags        = ["production", "team-auth"]
}

resource "descope_project_settings" "main" {
  project_id       = descope_project.my_project.id
  app_url          = "https://app.example.com"
  approved_domains = ["example.com", "app.example.com"]
}

resource "descope_jwt_template" "app_claims" {
  project_id = descope_project.my_project.id
  name       = "app-claims"
  type       = "user"
  template   = jsonencode({
    tier = "@user.customAttributes.subscriptionTier"
  })
}

resource "descope_session_settings" "main" {
  project_id               = descope_project.my_project.id
  refresh_token_expiration = "3 weeks"
  enable_inactivity        = true
  inactivity_time          = "2 hours"
  user_jwt_template        = descope_jwt_template.app_claims.id
}

resource "descope_password_settings" "main" {
  project_id            = descope_project.my_project.id
  lock                  = true
  lock_attempts         = 3
  min_length            = 8
  disallow_email_match  = true
  disallowed_characters = "'\""
}

resource "descope_sso_settings" "main" {
  project_id          = descope_project.my_project.id
  merge_users         = true
  redirect_url        = "https://example.com/auth/callback"
  require_sso_domains = true
  mandatory_user_attributes = [
    { id = "email" },
    { id = "name" },
  ]
  sso_suite_settings = {
    hide_scim         = false
    show_help_contact = true
    support_email     = "it-support@example.com"
  }
}

resource "descope_permission" "read_data" {
  project_id  = descope_project.my_project.id
  name        = "read:data"
  description = "Read access to application data"
}

resource "descope_role" "viewer" {
  project_id  = descope_project.my_project.id
  name        = "viewer"
  description = "Can read data"
  permissions = [descope_permission.read_data.name]
}

resource "descope_http_connector" "eligibility" {
  project_id  = descope_project.my_project.id
  name        = "User Eligibility Check"
  description = "Checks if a new user is allowed to register"
  base_url    = var.http_connector_base_url
  authentication = {
    bearer_token = var.http_connector_secret
  }
}

resource "descope_flow" "sign_up_or_in" {
  project_id = descope_project.my_project.id
  flow_id    = "sign-up-or-in"
  data       = file("${path.module}/flows/sign-up-or-in.json")
}

resource "descope_inbound_app" "partner" {
  project_id             = descope_project.my_project.id
  name                   = "Partner Integration"
  description            = "OAuth client for a partner integration"
  client_type            = "confidential"
  approved_callback_urls = ["https://partner.example.com/callback"]
  permissions_scopes = [
    {
      name        = "read:data"
      description = "Read application data on the user's behalf"
      values      = [descope_permission.read_data.name]
    }
  ]
}

Additional Resources

Users and tenants are generally not managed via Terraform, but some dynamic resources have dedicated resource types. Defining them as code keeps access control auditable and consistent across environments.

Management Keys

You can use descope_management_key to manage Descope Management Keys as code, alongside the rest of your project configuration.

Important

The raw key value (cleartext) is only available immediately after creation and cannot be retrieved later. Store it in a secrets manager (for example, AWS Secrets Manager or HashiCorp Vault) right after terraform apply.

Keys can be scoped to restrict access at the company level, per project, or by project tag. The first example creates a company-level key and outputs its value as a sensitive output:

# Company-level key (access to all projects)
resource "descope_management_key" "pipeline_key" {
  name        = "CI/CD Pipeline Key"
  description = "Used by the deployment pipeline to manage users"

  rebac = {
    company_roles = ["<role-machine-name>"]
  }
}

output "pipeline_key_value" {
  value     = descope_management_key.pipeline_key.cleartext
  sensitive = true
}

Important

Role names must use the machine name format (kebab-case), not the human-readable format from the Descope Console. For example, use company-asset-mgmt-read-write instead of "Asset Management - Read & Write". See the Management Key Roles documentation for the complete list of machine names.

The second example scopes a key to a single project:

# Project-scoped key
resource "descope_management_key" "staging_key" {
  name = "Staging Key"

  rebac = {
    project_roles = [
      {
        project_ids = ["__ProjectID__"]
        roles       = ["<role-machine-name>"]
      }
    ]
  }
}

Full management key schema reference

Access Keys

You can use descope_access_key to manage project-level Access Keys as code, for machine-to-machine authentication.

Important

The cleartext value (the plaintext access key) is only available immediately after creation and cannot be retrieved later. Store it in a secrets manager (for example, AWS Secrets Manager or HashiCorp Vault) right after terraform apply.

The example below creates an access key limited to an IP range, with an expiration time:

resource "descope_access_key" "ci_pipeline" {
  project_id    = descope_project.myproject.id
  name          = "CI/CD Pipeline Key"
  description   = "Used by the deployment pipeline"
  roles         = ["Viewer"]
  permitted_ips = ["10.0.0.0/8"]
  expire_time   = 1924991999
}

output "ci_pipeline_key" {
  value     = descope_access_key.ci_pipeline.cleartext
  sensitive = true
}

Full access key schema reference

Descopers (Console Users)

You can use descope_descoper to manage Descopers as code. Roles can be scoped to the entire company, to specific projects, or to all projects with a given tag. The available roles are admin, developer, support, and auditor.

The examples below create a company admin, a developer scoped to two projects, and a support user for every production-tagged project:

# Company admin
resource "descope_descoper" "admin" {
  email = "admin@example.com"
  name  = "Alice Admin"

  rbac = {
    is_company_admin = true
  }
}

# Developer scoped to specific projects
resource "descope_descoper" "developer" {
  email = "dev@example.com"
  name  = "Bob Dev"

  rbac = {
    project_roles = [
      {
        role        = "developer"
        project_ids = ["P123abc", "P456def"]
      }
    ]
  }
}

# Support access for all production-tagged projects
resource "descope_descoper" "support" {
  email = "support@example.com"

  rbac = {
    tag_roles = [
      {
        role = "support"
        tags = ["production"]
      }
    ]
  }
}

Full descoper schema reference

Applications

Like users and tenants, applications are dynamic resources that vary per environment. The Terraform provider supports Federated Apps, where Descope is the IdP for an OIDC, SAML, or WS-Fed application, and Inbound Apps, which are third-party OAuth clients with scopes and consent. Each app type is a standalone resource.

Federated Apps

You can use descope_oidc_app, descope_saml_app, and descope_wsfed_app to configure applications for outbound SSO integrations. App-scoped roles and permissions use descope_app_role and descope_app_permission.

resource "descope_oidc_app" "web" {
  project_id             = descope_project.myproject.id
  name                   = "My Web App"
  description            = "Primary OIDC application"
  force_authentication   = false
  claims                 = ["sub", "exp", "email"]
  backchannel_logout_url = "https://example.com/backchannel-logout"
}

resource "descope_saml_app" "enterprise" {
  project_id                  = descope_project.myproject.id
  name                        = "My SAML App"
  description                 = "Enterprise SAML integration"
  force_authentication        = false
  default_signature_algorithm = "sha256"
  manual_configuration = {
    acs_url   = "https://example.com/saml/acs"
    entity_id = "https://example.com"
  }
}

Federated apps have deletion_protection enabled by default, because destroying or replacing one breaks the service provider's integration.

Full OIDC app schema reference · Full SAML app schema reference · Full WS-Fed app schema reference

Inbound Apps

You can use descope_inbound_app to manage Inbound Apps, which are third-party applications that authenticate users with Descope as an OAuth 2.0 identity provider. OAuth clients, MCP server configurations, and partner integrations all benefit from being version-controlled alongside your project.

resource "descope_inbound_app" "my_oauth_client" {
  project_id     = descope_project.myproject.id
  name           = "My OAuth Client"
  description    = "OAuth client application"
  logo_url       = "https://example.com/logo.png"
  login_page_url = "__BaseURL__"
  client_type    = "confidential"
  approved_callback_urls = [
    "https://myapp.com/callback",
    "http://localhost:3000/callback"
  ]
  permissions_scopes = [
    {
      name        = "read:profile"
      description = "Read the user's profile information"
      values      = ["read:data"]
    },
    {
      name        = "write:profile"
      description = "Modify the user's profile information"
      optional    = true
      values      = ["write:data"]
    }
  ]
  attributes_scopes = [
    {
      name        = "email"
      description = "The user's email address"
      values      = ["email"]
    }
  ]
}

Scopes (permissions_scopes, attributes_scopes, connections_scopes) each take the same shape:

FieldRequiredDescription
nameYesUnique identifier for the scope
descriptionYesDescription shown during the consent flow
valuesNoIdentifiers of the underlying permissions, attributes, or connections this scope grants
optionalNoWhen true, the user may decline to grant this scope during authorization

The resource also supports force_dpop, force_pkce, default_audience, audience_whitelist, allowed_tenants, scope_claim_mapping, and a session_settings block that overrides the project's token lifetimes for this app.

To control how the app gets tokens, you can turn off individual grant types with the *_disabled attributes. You can also set client_auth_method to authenticate the app with client_assertions (private key JWT) or workload_client_assertions (workload identity) instead of a client secret.

Full inbound app schema reference

Inbound App Client Type Changes

Moving an app from the legacy empty client_type to confidential updates it in place and keeps its client ID. Any change to or from public, and any move back to the legacy value, replaces the app. The replacement gets a new client ID and client secret, so policies and clients that reference the old client ID stop matching.

The deletion_protection attribute defaults to true on inbound apps and blocks a replacement until you set it to false. If you leave client_type out, Terraform keeps the app's current type, and a new app gets your account's default client type.

Descope Engine

You can use descope_engine to register a Descope Engine as code. The Engine lets Connectors reach resources inside your private network, but it runs as a container you deploy yourself. The Terraform resource only manages the Engine's registration with Descope (its id and secret), not the container.

resource "descope_engine" "private_network" {
  project_id = descope_project.myproject.id
  name       = "Private Network Engine"
}

Full engine schema reference

Using Terraform Within Your Environment

Terraform tracks your Descope project in a state file. You can store it somewhere your team can access, and remote backends like S3 or Terraform Cloud work well.

  • Run terraform plan to preview changes before applying.
  • Run terraform apply to apply them.

For anything Terraform doesn't cover, you can use the Management API or the management SDKs. If you manage a project with provider v0.3, the upgrading from v0.3 guide walks through moving to the standalone resources.

Was this helpful?

On this page