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. Read more here.
Descope publishes a Terraform provider for managing projects and their configuration.
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.
Only resources explicitly declared in your configuration will be affected when you run terraform apply. Omitting a block (e.g. connectors) means Terraform will not touch it, while adding a block (even if empty) will cause Terraform to manage it and remove anything not declared.
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.
- 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
Declare the Descope provider in your .tf file:
terraform {
required_providers {
descope = {
source = "descope/descope"
version = "~> 0.3"
}
}
}Warning
Never hardcode your management key in Terraform configuration files—this risks exposing it in version control. Use environment variables or a secrets manager instead.
| Variable | Description |
|---|---|
DESCOPE_MANAGEMENT_KEY | A valid management key for your Descope company |
DESCOPE_BASE_URL | Override the Descope API base URL (optional, for testing) |
export DESCOPE_MANAGEMENT_KEY="K2..."With those set, the provider block needs no additional configuration:
provider "descope" {}Run terraform init to download the provider:
terraform initIf you need to configure credentials explicitly (e.g. in a module):
variable "descope_management_key" {
type = string
sensitive = true
}
provider "descope" {
management_key = var.descope_management_key
}Creating a Project
Add a project resource to your .tf file:
resource "descope_project" "myproject" {
name = "project-name"
environment = "production"
tags = ["foo", "bar"]
}Attributes like tags support dynamically computed values:
variable "additional_project_tags" {
type = list(string)
nullable = false
}
resource "descope_project" "myproject" {
name = "project-name"
tags = [
"foo",
...var.additional_project_tags
]
}Examples
Each example below is an attribute inside the descope_project resource.
Project Settings
Configure project-level settings:
project_settings = {
refresh_token_expiration = "3 weeks"
enable_inactivity = true
inactivity_time = "1 hour"
}Full project settings schema reference
Invite Settings
Configure user invitation behavior:
invite_settings = {
require_invitation = true
invite_url = "https://example.com/invite"
add_magiclink_token = true
expire_invited_users = true
invite_expiration = "2 weeks"
}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". Use it alongside expire_invited_users and/or add_magiclink_token.
Full invite settings schema reference
Authorization
Configure permissions and roles:
authorization = {
permissions = [
{
name = "test-permission"
description = "this is a test"
}
]
roles = [
{
name = "test-role"
description = "this is a test"
permissions = ["test-permission"]
}
]
}Full authorization schema reference
FGA Schema
Use the fga attribute to manage your project's Fine-Grained Authorization schema as code. In the Descope console, open the FGA tab and use the Code view to copy the schema text. You can paste it inline or load it from a file:
authorization = {
# Inline
fga = "model AuthZ\n..."
# Or from a file
fga = file("${path.module}/fga-schema.txt")
}Note
The fga value must start with "model AuthZ". Use the code view in the FGA tab — not the visual editor — to copy the schema text.
Authentication
Configure authentication methods:
authentication = {
magic_link = {
expiration_time = "1 hour"
}
password = {
lock = true
lock_attempts = 3
min_length = 8
disallow_email_match = true
disallowed_characters = "'\""
enforce_strength = "very_strong"
}
sso = {
merge_users = true
redirect_url = var.descope_redirect_url
}
}Full authentication schema reference
Passkeys
Use the passkeys block to configure 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):
authentication = {
passkeys = {
top_level_domain = "example.com"
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"
]
}
}Full passkeys schema reference
Attributes
Configure custom attributes for users and tenants:
attributes = {
user = [
{
name = "test attribute user"
type = "string"
}
]
tenant = [
{
name = "test attribute tenant"
type = "multiselect"
select_options = ["A", "B"]
}
]
}Full attributes schema reference
Connectors
Connectors support bearer token auth and role-based auth:
connectors = {
# Bearer Token Authentication Example
http = [ {
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 Example
aws_s3 = [ {
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"
} ]
# reCAPTCHA Enterprise Example
recaptcha_enterprise = [ {
name = "reCAPTCHA Enterprise"
description = "Bot protection"
project_id = var.recaptcha_project_id
site_key = var.recaptcha_site_key
api_key = var.recaptcha_api_key
action = "login"
} ]
}Full connectors schema reference
JWT Templates
Use jwt_templates to configure custom JWT claim templates. You can include a description, control which standard claims are included, and add security features like JTI:
jwt_templates = {
user_templates = [
{
name = "app-claims"
description = "Adds subscription tier and org context to user JWTs"
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
}
]
}Full JWT templates schema reference
SSO Settings and Admin Portal
Use Terraform to manage project-level SSO (authentication.sso), SSO Setup Suite options (sso_suite_settings), invite emails, and the hosted Admin Portal (admin_portal).
Provider schema source of truth: authentication/sso and adminportal under the project docs tree.
In Terraform (descope_project) | Not in Terraform (use Setup Suite / Console / Management API) |
|---|---|
Project SSO auth method settings (authentication.sso) | 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 | Individual tenants and users |
| SSO invite email templates | Day-to-day customer IdP changes |
| Admin Portal enablement, style, and widget list | — |
Automate per-tenant connections with the Management SSO SDKs or tenants SSO API, or hand customers the SSO Setup Suite.
Project SSO Settings
These map to Project-level SSO settings in the Console.
authentication = {
sso = {
# 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 / 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 assertion email domain ≠ configured SSO domains
block_if_email_domain_mismatch = true
# Leave email unverified after SSO
mark_email_as_unverified = false
# Only allow mapping attributes listed below
limit_mapping_to_mandatory_attributes = false
# Attributes that must be present / mappable from the IdP
# (id = Console "Machine Name"; set custom = true for custom attrs)
mandatory_user_attributes = [
{ id = "email" },
{ id = "name" },
{ id = "department", custom = true },
]
# SSO Setup Suite — see next subsection
sso_suite_settings = {
style_id = "customer-facing-style"
hide_scim = false
hide_saml = false
hide_oidc = false
hide_groups_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 connector for SSO invite emails
email_service = {
connector = "My Email Connector"
templates = [
{
name = "SSO Invite"
subject = "You've been invited to sign in"
html_body = "<p>Click <a href='{{link}}'>here</a> to accept.</p>"
active = true
}
]
}
}
}| Attribute | Purpose |
|---|---|
disabled | Blocks direct SSO via API/SDK; does not disable SSO inside Flows |
merge_users | Merge SSO users into existing accounts with the same email (merging guide) |
redirect_url | Project default post-auth URL (settings); required for IdP-initiated unless set per tenant |
allow_duplicate_domains | Allow the same SSO domain on multiple tenants |
allow_override_roles | Let SSO group → role maps override existing roles |
groups_priority | Enable groups priority when resolving role maps |
require_sso_domains | Force an SSO domain on each tenant SSO config |
require_groups_attribute_name | Force a groups attribute name on each tenant SSO config |
block_if_email_domain_mismatch | Fail login when email domain ≠ SSO domains |
mark_email_as_unverified | Do not mark email verified after SSO |
mandatory_user_attributes | Required Descope attributes from SSO (id, optional custom) |
limit_mapping_to_mandatory_attributes | Only allow maps into those mandatory attributes |
sso_suite_settings | Hosted Setup Suite UI (below) |
email_service | Connector + templates for SSO invite emails |
Registry: authentication.sso nested schema.
Note
For email_service, the connector must be an existing HTTP connector in your connectors block. If any template has active = true, the connector cannot be "Descope". Each template needs html_body or plain_text_body with use_plain_text_body = true. Template names must be unique and cannot be "System".
SSO Setup Suite Settings
Controls what customer IT admins see in the SSO Setup Suite (mirrors SSO Suite settings in the Console).
| Attribute | Purpose |
|---|---|
style_id | Style applied to the suite (must exist in the project) |
hide_saml / hide_oidc | Hide SAML or OIDC setup (cannot both be true) |
hide_scim | Hide SCIM in the suite |
hide_groups_mapping | Hide group mapping UI |
hide_domains | Hide SSO domains UI |
hide_jit_guide | Hide the JIT provisioning guide in the hosted UI |
force_domain_verification | Require DNS domain verification (incompatible with hide_domains = true) |
show_help_contact | Show a help/support contact in the suite |
support_email | Email shown as that support contact |
Admin Portal
Configures the hosted Admin Portal — which widgets appear and which style to use.
admin_portal = {
enabled = true
style_id = "admin-portal-style"
# type + widget_id must match widgets that already exist in the project
# (create/export them under Widgets in the Console, or via the widgets block)
widgets = [
{
type = "users"
widget_id = "user-management-widget"
},
{
type = "roles"
widget_id = "role-management-widget"
},
]
}| Attribute | Purpose |
|---|---|
enabled | Turn the hosted Admin Portal on or off |
style_id | Style for the portal |
widgets | Ordered list of widgets (type + widget_id, both required). At least one widget is required when enabled = true |
type is the widget type string from the Console (common examples from the provider: users, roles). widget_id is the ID of the widget instance you want to host. Create those widgets under Widgets / Admin Widgets (you can also manage widget JSON via the project's widgets map — see the Registry widgets schema).
Registry: admin_portal nested schema.
If Descope is the IdP for other apps (outbound SAML/OIDC), configure those under applications — see Applications and Federated Apps. That is separate from tenant SSO (customers' IdPs signing into your app).
Flows and Styles
If you've designed custom flows in the Descope console, you can export and load them via Terraform:
- In the Descope console, go to Authentication Flows
- Open the flow you want to manage, click the export button, and save the JSON file (e.g.,
flows/sign-up-or-in.json) - Optionally export your flow styles from the same screen and save as
flows/styles.json - Reference the files in your configuration:
flows = {
"sign-up-or-in" = {
data = file("${path.module}/flows/sign-up-or-in.json")
}
}
styles = {
data = file("${path.module}/flows/styles.json")
}Full flows schema reference · Full styles schema reference
Full Terraform Plan Example
variable "http_connector_base_url" {
type = string
}
variable "http_connector_secret" {
type = string
sensitive = true
}
variable "s3_role_arn" {
type = string
}
variable "s3_external_id" {
type = string
}
terraform {
required_providers {
descope = {
source = "descope/descope"
version = "~> 0.3"
}
}
}
provider "descope" {}
resource "descope_project" "my-project" {
name = "my-project"
environment = "production"
tags = ["production", "team-auth"]
project_settings = {
refresh_token_expiration = "3 weeks"
enable_inactivity = true
inactivity_time = "2 hours"
}
invite_settings = {
require_invitation = true
invite_url = "https://example.com/invite"
add_magiclink_token = true
expire_invited_users = true
invite_expiration = "2 weeks"
}
authentication = {
magic_link = {
expiration_time = "1 hour"
}
password = {
lock = true
lock_attempts = 3
min_length = 8
disallow_email_match = true
disallowed_characters = "'\""
}
sso = {
merge_users = true
redirect_url = "https://example.com/auth/callback"
allow_override_roles = true
groups_priority = true
require_sso_domains = true
require_groups_attribute_name = true
block_if_email_domain_mismatch = true
mark_email_as_unverified = false
mandatory_user_attributes = [
{ id = "email" },
{ id = "name" },
{ id = "department", custom = true },
]
sso_suite_settings = {
style_id = "my-brand-style"
hide_scim = false
hide_saml = false
hide_oidc = false
hide_groups_mapping = false
hide_domains = false
hide_jit_guide = false
force_domain_verification = false
show_help_contact = true
support_email = "it-support@example.com"
}
}
}
admin_portal = {
enabled = true
style_id = "admin-portal-style"
widgets = [
{ type = "users", widget_id = "user-management-widget" },
{ type = "roles", widget_id = "role-management-widget" },
]
}
attributes = {
user = [
{
name = "subscriptionTier"
type = "string"
}
]
tenant = [
{
name = "plan"
type = "multiselect"
select_options = ["free", "pro", "enterprise"]
}
]
}
authorization = {
permissions = [
{
name = "read:data"
description = "Read access to project data"
},
{
name = "write:data"
description = "Write access to project data"
}
]
roles = [
{
name = "viewer"
description = "Read-only access"
permissions = ["read:data"]
},
{
name = "editor"
description = "Read and write access"
permissions = ["read:data", "write:data"]
}
]
}
applications = {
oidc_applications = [
{
name = "My Web App"
description = "Primary OIDC application"
force_authentication = false
claims = ["sub", "exp", "email"]
}
]
saml_applications = [
{
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"
}
}
]
}
jwt_templates = {
user_templates = [
{
name = "app-claims"
description = "Adds subscription tier and org context to user JWTs"
template = jsonencode({
tier = "@user.customAttributes.subscriptionTier"
org_id = "@user.tenants[0].tenantId"
})
exclude_permission_claim = true
add_jti_claim = true
override_subject_claim = true
}
]
}
connectors = {
http = [
{
name = "Internal API"
description = "Backend service connector"
base_url = var.http_connector_base_url
use_static_ips = false
authentication = {
bearer_token = var.http_connector_secret
}
}
]
aws_s3 = [
{
name = "S3 Audit Logs"
description = "Audit log storage"
auth_type = "assumeRole"
role_arn = var.s3_role_arn
external_id = var.s3_external_id
region = "us-east-1"
bucket = "my-audit-logs-bucket"
}
]
}
flows = {
"sign-up-or-in" = {
data = file("${path.module}/flows/sign-up-or-in.json")
}
}
styles = {
data = file("${path.module}/flows/styles.json")
}
}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
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 (e.g., AWS Secrets Manager, HashiCorp Vault) right after terraform apply.
Keys can be scoped to restrict access at the company level, per project, or by project tag:
# 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.
# Project-scoped key
resource "descope_management_key" "staging_key" {
name = "Staging Key"
rebac = {
project_roles = [
{
project_ids = ["<project-id>"]
roles = ["<role-machine-name>"]
}
]
}
}Full management key schema reference
Descopers (Console Users)
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.
Available roles: admin, developer, support, auditor.
# 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 two types: Federated Apps (First Party Applications, configured inside descope_project) and Inbound Apps (Third Party Applications with Scopes and Consent, managed as a standalone descope_inbound_app resource).
Federated Apps
Use the applications block inside descope_project to configure OIDC and SAML applications for outbound SSO integrations.
applications = {
oidc_applications = [
{
name = "My Web App"
description = "Primary OIDC application"
force_authentication = false
claims = ["sub", "exp", "email"]
}
]
saml_applications = [
{
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"
}
}
]
}Full applications schema reference
Inbound Apps
Use descope_inbound_app to manage third-party applications that authenticate users via 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 = "https://api.descope.com"
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:
| Field | Required | Description |
|---|---|---|
name | Yes | Unique identifier for the scope |
description | Yes | Description shown during the consent flow |
values | No | Identifiers of the underlying permissions, attributes, or connections this scope grants |
optional | No | When true, the user may decline to grant this scope during authorization |
Full inbound app schema reference
Using Terraform Within Your Environment
Terraform tracks your Descope project in a state file. Store it somewhere your team can access — remote backends like S3 or Terraform Cloud work well.
- Run
terraform planto preview changes before applying. - Run
terraform applyto apply them.
