Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/@okta/vuepress-site/docs/guides/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ guides:
- key-management
- manage-orgs-okta-aerial
- mfa
- migrate-customizations
- migrate-to-okta-prerequisites
- migrate-to-okta-bulk
- migrate-to-okta-password-hooks
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: Migrate your brand customizations with automation
excerpt: Migrate your brand customizations from a test to production org using Terrafrom, PowerShell, or Go automation.
layout: Guides
sections:
- main
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
```bash
# Create or update UserActivation email template customization
# Note: EMAIL_BODY should contain your HTML email template
EMAIL_BODY='<html><body style="font-family: Arial, sans-serif;"><p>Hello ${user.profile.firstName},</p><p>Your new account has been created at ${org.name}.</p><p>Click <a href="${activationLink}">Activate My Account</a> to get started.</p><p>Thank you!</p></body></html>'

# Create a new customization (if it doesn't exist)
curl -X POST "${OKTA_ORG_URL}/api/v1/brands/${BRAND_ID}/templates/email/UserActivation/customizations" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d "{
\"language\": \"en\",
\"isDefault\": true,
\"subject\": \"Welcome to Acme Co - Activate Your Account\",
\"body\": $(echo "$EMAIL_BODY" | jq -Rs .)
}"

# Or update an existing customization
# First get the customization ID
# CUSTOMIZATION_ID=$(curl -s -X GET "${OKTA_ORG_URL}/api/v1/brands/${BRAND_ID}/templates/email/UserActivation/customizations" \
# -H "Authorization: Bearer ${ACCESS_TOKEN}" \
# -H "Accept: application/json" | jq -r '.[0].id')
#
# curl -X PUT "${OKTA_ORG_URL}/api/v1/brands/${BRAND_ID}/templates/email/UserActivation/customizations/${CUSTOMIZATION_ID}" \
# -H "Authorization: Bearer ${ACCESS_TOKEN}" \
# -H "Content-Type: application/json" \
# -H "Accept: application/json" \
# -d "{
# \"language\": \"en\",
# \"isDefault\": true,
# \"subject\": \"Welcome to Acme Co - Activate Your Account\",
# \"body\": $(echo "$EMAIL_BODY" | jq -Rs .)
# }"

echo "Email template customization applied"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
```bash
# Note: Error page HTML customization is only supported when multibrand isn't enabled.
# This API call may return 403 if multibrand customization is enabled in your org.

ERROR_PAGE_HTML='<div style="text-align: center; padding: 50px; font-family: sans-serif;"><h1 style="color: #0047AB;">Oops! Something Went Wrong.</h1><p style="font-size: 1.2em;">We encountered an unexpected error.</p><p>Please check the URL or try again later.</p><a href="/" style="display: inline-block; margin-top: 20px; padding: 10px 20px; background-color: #0047AB; color: white; text-decoration: none; border-radius: 5px;">Go Home</a></div>'

curl -X PUT "${OKTA_ORG_URL}/api/v1/brands/${BRAND_ID}/pages/sign-in/customized" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d "{
\"pageContent\": $(echo "$ERROR_PAGE_HTML" | jq -Rs .)
}"

# If you receive a 403 error, multibrand customization is enabled
# and error page HTML customization isn't available.
echo "Error page customization applied (if multibrand is not enabled)"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
```bash
# Get theme ID
THEME_ID=$(curl -s -X GET "${OKTA_ORG_URL}/api/v1/brands/${BRAND_ID}/themes" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Accept: application/json" | jq -r '.[0].id')

echo "Updating Theme ID: $THEME_ID"

# Update theme colors and variants
curl -X PUT "${OKTA_ORG_URL}/api/v1/brands/${BRAND_ID}/themes/${THEME_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"primaryColorHex": "#0047AB",
"secondaryColorHex": "#f0f4f8",
"primaryColorContrastHex": "#FFFFFF",
"secondaryColorContrastHex": "#000000",
"signInPageTouchPointVariant": "BACKGROUND_SECONDARY_COLOR",
"endUserDashboardTouchPointVariant": "OKTA_DEFAULT",
"errorPageTouchPointVariant": "OKTA_DEFAULT",
"emailTemplateTouchPointVariant": "OKTA_DEFAULT"
}'

echo "Theme updated successfully"

# Optional: Upload logo (multipart/form-data)
# curl -X POST "${OKTA_ORG_URL}/api/v1/brands/${BRAND_ID}/themes/${THEME_ID}/logo" \
# -H "Authorization: Bearer ${ACCESS_TOKEN}" \
# -F "file=@./assets/logo.png"

# Optional: Upload background image
# curl -X POST "${OKTA_ORG_URL}/api/v1/brands/${BRAND_ID}/themes/${THEME_ID}/background-image" \
# -H "Authorization: Bearer ${ACCESS_TOKEN}" \
# -F "file=@./assets/background.jpg"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
```bash
# List existing brands
curl -X GET "${OKTA_ORG_URL}/api/v1/brands" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Accept: application/json"

# Most commonly, you work with your existing default brand (first in the list)
BRAND_ID=$(curl -s -X GET "${OKTA_ORG_URL}/api/v1/brands" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Accept: application/json" | jq -r '.[0].id')

echo "Working with Brand ID: $BRAND_ID"

# Update the brand properties
curl -X PUT "${OKTA_ORG_URL}/api/v1/brands/${BRAND_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "Acme Co. User Portal Brand",
"agreeToCustomPrivacyPolicy": true,
"customPrivacyPolicyUrl": "https://www.acme.com/privacy",
"removePoweredByOkta": true,
"locale": "en"
}'

# To create a NEW custom brand (requires the multibrand feature):
# curl -X POST "${OKTA_ORG_URL}/api/v1/brands" \
# -H "Authorization: Bearer ${ACCESS_TOKEN}" \
# -H "Content-Type: application/json" \
# -H "Accept: application/json" \
# -d '{
# "name": "Acme Co. User Portal Brand",
# "agreeToCustomPrivacyPolicy": true,
# "customPrivacyPolicyUrl": "https://www.acme.com/privacy",
# "removePoweredByOkta": true,
# "locale": "en"
# }'
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
You can obtain the OAuth 2.0 access token using the client credentials flow with your service app credentials. Set environment variables to switch between test and production environments.

```bash
# Set variables for test environment
export OKTA_ORG_URL="https://dev-test.com"
export OKTA_CLIENT_ID="0oa1234567890abcdef"
export OKTA_PRIVATE_KEY_PATH="./keys/test-private-key.pem"

# To target production, change these variables:
# export OKTA_ORG_URL="https://acme-prod.okta.com"
# export OKTA_CLIENT_ID="0oa9876543210fedcba"
# export OKTA_PRIVATE_KEY_PATH="./keys/prod-private-key.pem"
```

To obtain an access token, you need to create a JWT and exchange it for an OAuth 2.0 access token. Terraform and PowerShell handle this process automatically. For direct API calls, you can use a helper script or library to generate the JWT.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
title: Migrate your brand customizations with automation
excerpt: Migrate your brand customizations from a test to production org using Terraform, PowerShell, or Go automation.
layout: Guides
---

This guide details how to automate copying custom brand settings from an Okta test environment to a live production environment. This migration includes CSS, email content, and error page HTML for Terraform, Powershell CLI, and the Okta APIs. Switch between these three tools using the dropdown list on the right.

---

#### Learning outcomes

Configure and run a brand customization synchronization from a test or preview environment to a production environment using automated tools.

#### What you need

* A branded Okta test or preview org and a production Okta org

* Automated tooling (Terraform provider, PowerShell module, or Okta CLI client)
* Okta Terraform provider 4.9.1 or later (for Terraform users)
* PowerShell 7.0+ and Okta.PowerShell module v2.0.2+ (for PowerShell users)
* Go-based okta-cli-client (for CLI users - currently in Beta, requires source compilation)

---

## Prerequisites

This guide assumes that you have already registered and verified separate custom domains for your test and production environments (for example, `test.example.com` and `portal.example.com`). Okta also assumes that your Terraform or CLI tools are fully authenticated and configured.

> **Important**: You can create brands programmatically, but themes are automatically generated when a brand is created. Import existing themes before you manage them with automation tools. This guide demonstrates the proper import workflow.

**New to these tools?** If you haven’t configured authentication or installed the necessary SDKs (Terraform provider, PowerShell module, Okta Go CLI), refer to the introductory guides on the respective GitHub repositories before proceeding.

### Limitations

* The Okta Management API doesn't provide endpoints to customize the generic, system-generated SMS messages (such as those used for MFA codes). This functionality remains limited to the Admin Console interface.
* You can't create or delete themes programmatically, each brand automatically receives exactly one theme that can only be read and updated.
* You can't modify custom error page HTML content when multibrand customization is enabled in your org.

## Tool setup

Setting up secure automation starts with choosing the right authentication method, whether you're using Terraform for infrastructure, PowerShell for scripting, or making direct API calls. This section provides the steps to configure OAuth 2.0 service apps and mobile integrations to ensure that your tools have the precise permissions they need without compromising security. By the end of this section, you'll know how to manage environment-specific variables and establish a secure connection to your Okta org.

<StackSnippet snippet="toolsetup" />

## Define reusable branding content

All customization logic is centralized into reusable blocks (content strings) that are applied identically across both environments.

<StackSnippet snippet="reusable" />

## Synchronize branding metadata

This step ensures that the core brand object exists and is correctly linked to the custom domain of the target environment (test or production).

<StackSnippet snippet="synchronize" />

## Apply Sign-In Widget customization

**Important:** Themes are automatically created when a brand is created. Import existing themes before managing them.

<StackSnippet snippet="siwcustom" />

## Apply email template customization

Synchronizes the email content defined in the [Define reusable branding content](#define-resuable-branding-content) section for key templates.

<StackSnippet snippet="emailcustom" />

## Apply error page customization

**Important:** You can't modify custom error page HTML if multibrand customization is enabled in your org. This section only applies to orgs without multibrand.

<StackSnippet snippet="errorcustom" />

## Final synchronization step

After the content is defined in the script or configuration file:

1. Run against the test environment: Execute your chosen tool (Terraform `apply`, PowerShell script, or cURL commands) using the test environment variables or context.
1. Verify: Manually test the customization on your test custom domain (`https://test.example.com/login/default`).
1. Run against production: Change your environment variables (or tool configuration) to target production and re-run the exact same script or configuration. The code is designed to be idempotent and creates the brand in production using the production domain ID.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
```powershell
# Create or update User Activation email template
$emailCustomization = @{
subject = "Welcome to Acme Co - Activate Your Account"
language = "en"
isDefault = $true
body = $UserActivationBody
}

$emailPayload = $emailCustomization | ConvertTo-Json -Depth 10

try {
# Try to create the customization
$result = Invoke-OktaApi -Uri "api/v1/brands/$BrandId/templates/email/UserActivation/customizations" `
-Method POST `
-Body $emailPayload
Write-Host "Created UserActivation email template"
} catch {
if ($_.Exception.Response.StatusCode -eq 409) {
# Already exists, update it instead
Write-Host "Email template exists, updating..."

# Get existing customization ID
$existing = Invoke-OktaApi -Uri "api/v1/brands/$BrandId/templates/email/UserActivation/customizations"
$customizationId = $existing[0].id

$result = Invoke-OktaApi -Uri "api/v1/brands/$BrandId/templates/email/UserActivation/customizations/$customizationId" `
-Method PUT `
-Body $emailPayload
Write-Host "Updated UserActivation email template"
} else {
Write-Error "Failed to customize email template: $_"
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
```powershell
# Note: This only works if multibrand customization isn't enabled
try {
$pagePayload = @{
pageContent = $CustomErrorPageHTML
} | ConvertTo-Json -Depth 10

Invoke-OktaApi -Uri "api/v1/brands/$BrandId/pages/sign-in/customized" `
-Method PUT `
-Body $pagePayload

Write-Host "Error page customization applied"
} catch {
if ($_.Exception.Message -like "*403*") {
Write-Warning "Error page customization is disabled (multibrand may be enabled)"
} else {
Write-Error "Failed to update error page: $_"
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
### PowerShell (string variables)

Create a configuration script with reusable variables:

```powershell
# brands-config.ps1
$BrandName = "Acme Co. User Portal Brand"

# Theme colors
$ThemeColors = @{
primaryColorHex = "#0047AB"
primaryColorContrastHex = "#FFFFFF"
secondaryColorHex = "#f0f4f8"
secondaryColorContrastHex = "#000000"
signInPageTouchPointVariant = "BACKGROUND_SECONDARY_COLOR"
endUserDashboardTouchPointVariant = "OKTA_DEFAULT"
errorPageTouchPointVariant = "OKTA_DEFAULT"
emailTemplateTouchPointVariant = "OKTA_DEFAULT"
}

# Email templates
$UserActivationBody = @'
<html>
<body style="font-family: Arial, sans-serif;">
<p>Hello ${user.profile.firstName},</p>
<p>Your new account has been created at ${org.name}.</p>
<p>Click <a href="${activationLink}">Activate My Account</a> to get started.</p>
<p>Thank you!</p>
</body>
</html>
'@

# Error page (only if multibrand isn't enabled)
$CustomErrorPageHTML = @'
<div style="text-align: center; padding: 50px; font-family: sans-serif;">
<h1 style="color: #0047AB;">Oops! Something Went Wrong.</h1>
<p style="font-size: 1.2em;">We encountered an unexpected error.</p>
<p>Please check the URL or try again later.</p>
<a href="/" style="display: inline-block; margin-top: 20px; padding: 10px 20px;
background-color: #0047AB; color: white; text-decoration: none;
border-radius: 5px;">Go Home</a>
</div>
'@
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
```powershell
# Get the theme for the brand
$themes = Invoke-OktaListBrandThemes -BrandId $BrandId
$themeId = $themes[0].id

Write-Host "Updating theme: $themeId"

# Update the theme with custom settings
$themeUpdate = [PSCustomObject]$ThemeColors

try {
$updatedTheme = Update-OktaBrandTheme -BrandId $BrandId -ThemeId $themeId -Theme $themeUpdate
Write-Host "Successfully updated theme"

# Optional: Upload custom logo
# Invoke-OktaUploadBrandThemeLogo -BrandId $BrandId -ThemeId $themeId -File ".\assets\logo.png"

# Optional: Upload background image
# Invoke-OktaUploadBrandThemeBackgroundImage -BrandId $BrandId -ThemeId $themeId -File ".\assets\background.jpg"

} catch {
Write-Error "Failed to update theme: $_"
}
```
Loading