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 at developer.hashicorp.com/terraform/intro.
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.
Prerequisites
Note
The Terraform provider works with a paid Descope license (Pro +). For licensing questions, contact support@descope.com.
- Terraform CLI 1.0 or later installed.
- A Management Key from Company Settings. Set the scope to All Projects if you intend to create new projects via Terraform.
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
Authentication
Configure authentication methods:
authentication = {
magic_link = {
expiration_time = "1 hour"
}
password = {
lock = true
lock_attempts = 3
min_length = 8
}
sso = {
merge_users = true
redirect_url = var.descope_redirect_url
}
}Full authentication 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"
} ]
}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
Configure global settings for Single Sign-On across all tenants in your project:
authentication = {
sso = {
# Merge SSO users with existing accounts of the same email
merge_users = true
# Allow SSO roles to override a user's existing roles
allow_override_roles = true
# Prioritize group-based role mappings over direct role assignments
groups_priority = true
# Enforce that SSO domains are always specified
require_sso_domains = true
# Require a groups attribute name in SSO configuration
require_groups_attribute_name = true
# Block login if the user's email domain doesn't match the configured SSO domains
block_if_email_domain_mismatch = true
# Mark the user's email as unverified during SSO authentication
mark_email_as_unverified = false
# Define required Descope attributes when receiving SSO information
mandatory_user_attributes = [
{ id = "email" },
{ id = "name" },
{ id = "department", custom = true },
]
# Configure the SSO Suite portal appearance
sso_suite_settings = {
style_id = "my-brand-style"
hide_scim = false
hide_saml = false
hide_oidc = false
hide_groups_mapping = false
hide_domains = false
force_domain_verification = false
}
# Use a custom email connector for SSO Suite 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 your invitation.</p>"
active = true
}
]
}
}
}Note
When using email_service, the connector must be set to an existing HTTP connector defined in your connectors block. If any template has active = true, the connector cannot be "Descope" (the built-in default). Each template requires either html_body (default) or plain_text_body with use_plain_text_body = true. Template names must be unique and cannot be "System".
Full SSO settings schema reference
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
}
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
force_domain_verification = false
}
}
}
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-name>"]
}
}
output "pipeline_key_value" {
value = descope_management_key.pipeline_key.cleartext
sensitive = true
}# Project-scoped key
resource "descope_management_key" "staging_key" {
name = "Staging Key"
rebac = {
project_roles = [
{
project_ids = ["<project-id>"]
roles = ["<role-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.
