diff --git a/packages/@okta/vuepress-site/docs/guides/index.md b/packages/@okta/vuepress-site/docs/guides/index.md index 9e83f0a5160..0f24365b325 100644 --- a/packages/@okta/vuepress-site/docs/guides/index.md +++ b/packages/@okta/vuepress-site/docs/guides/index.md @@ -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 diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/index.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/index.md new file mode 100644 index 00000000000..c10264da21b --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/index.md @@ -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 +--- diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/emailcustom.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/emailcustom.md new file mode 100644 index 00000000000..cf7e8189a86 --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/emailcustom.md @@ -0,0 +1,36 @@ +```bash +# Create or update UserActivation email template customization +# Note: EMAIL_BODY should contain your HTML email template +EMAIL_BODY='

Hello ${user.profile.firstName},

Your new account has been created at ${org.name}.

Click Activate My Account to get started.

Thank you!

' + +# 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" +``` diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/errorcustom.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/errorcustom.md new file mode 100644 index 00000000000..f969e41b7b9 --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/errorcustom.md @@ -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='

Oops! Something Went Wrong.

We encountered an unexpected error.

Please check the URL or try again later.

Go Home
' + +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)" +``` diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/reusable.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/reusable.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/siwcustom.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/siwcustom.md new file mode 100644 index 00000000000..59e0a604440 --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/siwcustom.md @@ -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" +``` diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/synchronize.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/synchronize.md new file mode 100644 index 00000000000..04f18a0136a --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/synchronize.md @@ -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" +# }' +``` diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/toolsetup.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/toolsetup.md new file mode 100644 index 00000000000..dfd98a7b847 --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/api/toolsetup.md @@ -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. diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/index.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/index.md new file mode 100644 index 00000000000..0a1cfb3f8c7 --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/index.md @@ -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. + + + +## Define reusable branding content + +All customization logic is centralized into reusable blocks (content strings) that are applied identically across both environments. + + + +## 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). + + + +## Apply Sign-In Widget customization + +**Important:** Themes are automatically created when a brand is created. Import existing themes before managing them. + + + +## Apply email template customization + +Synchronizes the email content defined in the [Define reusable branding content](#define-resuable-branding-content) section for key templates. + + + +## 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. + + + +## 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. diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/emailcustom.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/emailcustom.md new file mode 100644 index 00000000000..10c8c41dc0c --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/emailcustom.md @@ -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: $_" + } +} +``` diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/errorcustom.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/errorcustom.md new file mode 100644 index 00000000000..cab1a6bf1ec --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/errorcustom.md @@ -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: $_" + } +} +``` diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/reusable.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/reusable.md new file mode 100644 index 00000000000..19e2b79b7ac --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/reusable.md @@ -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 = @' + + +

Hello ${user.profile.firstName},

+

Your new account has been created at ${org.name}.

+

Click Activate My Account to get started.

+

Thank you!

+ + +'@ + + # Error page (only if multibrand isn't enabled) + $CustomErrorPageHTML = @' +
+

Oops! Something Went Wrong.

+

We encountered an unexpected error.

+

Please check the URL or try again later.

+ Go Home +
+'@ +``` diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/siwcustom.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/siwcustom.md new file mode 100644 index 00000000000..d0434cc6951 --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/siwcustom.md @@ -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: $_" +} +``` diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/synchronize.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/synchronize.md new file mode 100644 index 00000000000..d51a3e832c2 --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/synchronize.md @@ -0,0 +1,43 @@ +### Working with brands + +PowerShell can work with both the default brand and custom brands. + +```powershell +# Source your configuration +. .\brands-config.ps1 + +# List existing brands to see what you have +$existingBrands = Invoke-OktaListBrands +Write-Host "Found $($existingBrands.Count) brand(s) in your org" + +# Option 1: Work with the default/first brand (most common) +$targetBrand = $existingBrands[0] +Write-Host "Working with brand: $($targetBrand.name) (ID: $($targetBrand.id))" + +# Update the brand properties +$updateParams = @{ + name = $BrandName + agreeToCustomPrivacyPolicy = $true + customPrivacyPolicyUrl = "https://www.acme.com/privacy" + removePoweredByOkta = $true + locale = "en" +} + +$targetBrand = Update-OktaBrand -BrandId $targetBrand.id -Brand $updateParams +$BrandId = $targetBrand.id + +# Option 2: Create a custom brand (requires the multibrand feature) +# Note: Brand creation through PowerShell requires using the API directly. +# $brandPayload = @{ +# name = $BrandName +# agreeToCustomPrivacyPolicy = $true +# customPrivacyPolicyUrl = "https://www.acme.com/privacy" +# removePoweredByOkta = $true +# locale = "en" +# } | ConvertTo-Json -Depth 10 +# +# $newBrand = Invoke-OktaApi -Uri "api/v1/brands" -Method POST -Body $brandPayload +# $BrandId = $newBrand.id + +Write-Host "Brand ID for operations: $BrandId" +``` diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/toolsetup.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/toolsetup.md new file mode 100644 index 00000000000..d5642a749c0 --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/powershell/toolsetup.md @@ -0,0 +1,39 @@ +The Okta.PowerShell module requires PowerShell 7.0+ and should use OAuth 2.0 device flow authentication as a security best practice. + +#### Configure an app for OAuth 2.0 Device flow authentication (recommended) + +Create an OIDC native app in your Admin Console: + +1. Go to **Applications** > **Applications** > **Create App Integration**. +2. Select **OIDC - OpenID Connect**. +3. Select **Native Application**. +4. Enter a name like **PowerShell Brand Management**. +5. Enable **Device Authorization** as the grant type and click **Save**. +6. On the **Okta API Scopes** tab, grant the following scopes: `okta.brands.manage`, `okta.brands.read`, `okta.templates.manage`. +7. On the **Assignments** tab, assign the appropriate users or groups who you want to run the scripts. +8. Note the **Client ID**. + +#### Configure PowerShell + +```powershell + # Install the module (one time) + Install-Module -Name Okta.PowerShell -Scope CurrentUser + + # Configure for your target environment + $Configuration = Get-OktaConfiguration + $Configuration.BaseUrl = 'https://your-org.okta.com' # Change per environment + $Configuration.ClientId = 'your_client_id' + $Configuration.Scope = "okta.brands.manage okta.brands.read okta.templates.manage" + + # This opens a browser for authentication + Invoke-OktaEstablishAccessToken + + # Verify connection + try { + $brands = Invoke-OktaListBrands + Write-Host "Successfully connected to $($Configuration.BaseUrl)" + Write-Host "Found $($brands.Count) brand(s)" + } catch { + Write-Error "Failed to connect: $_" + } +``` diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/emailcustom.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/emailcustom.md new file mode 100644 index 00000000000..6c76e5700fa --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/emailcustom.md @@ -0,0 +1,28 @@ +```hcl +resource "okta_email_customization" "user_activation" { + brand_id = okta_brand.custom_app_brand.id + template_name = "UserActivation" + language = "en" + is_default = true + subject = "Welcome to Acme Co - Activate Your Account" + body = local.user_activation_email +} + +# Additional templates +resource "okta_email_customization" "forgot_password" { + brand_id = okta_brand.custom_app_brand.id + template_name = "ForgotPassword" + language = "en" + is_default = true + subject = "Reset Your Acme Co Password" + body = < + +

Hello $${user.profile.firstName},

+

Click Reset Password to reset your password.

+

This link expires in $${tokenExpirationHours} hours.

+ + + EOT +} +``` diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/errorcustom.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/errorcustom.md new file mode 100644 index 00000000000..867a77fa7de --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/errorcustom.md @@ -0,0 +1,3 @@ +Error pages are managed through the [Custom Pages API](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/CustomPages/). Terraform support for custom error page content is limited. + +For most use cases, error page branding is controlled through the theme resource (colors, logos, and so on) rather than custom HTML. diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/reusable.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/reusable.md new file mode 100644 index 00000000000..9ab29523071 --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/reusable.md @@ -0,0 +1,41 @@ +### Terraform (HCL content blocks) + +Create a `locals.tf` file with reusable content strings: + +```hcl + locals { + # Brand metadata + brand_name = "Acme Co. User Portal Brand" + + # Theme colors + primary_color = "#0047AB" + secondary_color = "#f0f4f8" + primary_color_contrast = "#FFFFFF" + secondary_color_contrast = "#000000" + + # Shared HTML for User Activation Email Body (VTL/Velocity supported) + user_activation_email = < + +

Hello $${user.profile.firstName},

+

Your new account has been created at $${org.name}.

+

Click Activate My Account to get started.

+

Thank you!

+ + + EOT + + # Note: You can only modify custom error page HTML if multibrand + # customization isn't enabled in your org + custom_error_page_html = < +

Oops! Something Went Wrong.

+

We encountered an unexpected error.

+

Please check the URL or try again later.

+ Go Home + + EOT + } +``` diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/siwcustom.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/siwcustom.md new file mode 100644 index 00000000000..a6e55b7cb5a --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/siwcustom.md @@ -0,0 +1,35 @@ +```hcl +# Get the list of themes for the brand +data "okta_themes" "brand_themes" { + brand_id = okta_brand.custom_app_brand.id +} + +# Import the auto-generated theme (required step) +import { + to = okta_theme.custom_theme + id = "${okta_brand.custom_app_brand.id}/${tolist(data.okta_themes.brand_themes.themes)[0].id}" +} + +# Now manage the theme +resource "okta_theme" "custom_theme" { + brand_id = okta_brand.custom_app_brand.id + theme_id = tolist(data.okta_themes.brand_themes.themes)[0].id + + # Required color settings + primary_color_hex = local.primary_color + secondary_color_hex = local.secondary_color + primary_color_contrast_hex = local.primary_color_contrast + secondary_color_contrast_hex = local.secondary_color_contrast + + # Required touch point variants + sign_in_page_touch_point_variant = "BACKGROUND_SECONDARY_COLOR" + end_user_dashboard_touch_point_variant = "OKTA_DEFAULT" + error_page_touch_point_variant = "OKTA_DEFAULT" + email_template_touch_point_variant = "OKTA_DEFAULT" + + # Optional: Custom images (provide local file paths) + # logo = "${path.module}/assets/logo.png" + # favicon = "${path.module}/assets/favicon.ico" + # background_image = "${path.module}/assets/background.jpg" +} +``` diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/synchronize.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/synchronize.md new file mode 100644 index 00000000000..516aa548f95 --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/synchronize.md @@ -0,0 +1,56 @@ +There are two options when you want to synchronize branding metadata with Terraform: + +1. [Customize the existing default brand](#customize-the-existing-default-brand) +2. [Create a custom brand (requires multibrand)](#create-a-custom-brand-requires-multibrand) + +### Customize the existing default brand + +Most orgs want to customize their existing default brand: + +```hcl + # main.tf + + # First, discover the existing default brand + data "okta_brands" "test" {} + + # Import the default brand (first brand in the list) + import { + to = okta_brand.default_brand + id = tolist(data.okta_brands.test.brands)[0].id + } + + # Now manage the default brand + resource "okta_brand" "default_brand" { + name = "Acme Co. Portal" # You can rename it + agree_to_custom_privacy_policy = true + custom_privacy_policy_url = "https://www.acme.com/privacy" + remove_powered_by_okta = true + locale = "en" + } +``` + +### Create a custom brand (requires multibrand) + +If your org has multibrand enabled and you want to create a brand, use the following example: + +```hcl +# main.tf + +# Create a new custom brand (requires the multibrand feature) +resource "okta_brand" "custom_app_brand" { + name = local.brand_name + agree_to_custom_privacy_policy = true + custom_privacy_policy_url = "https://www.acme.com/privacy" + remove_powered_by_okta = true + locale = "en" +} + +# Associate with a custom domain (domain must exist first) +# resource "okta_domain" "custom" { +# name = var.target_domain_name +# brand_id = okta_brand.custom_app_brand.id +# certificate_source_type = "OKTA_MANAGED" # or "MANUAL" +# } +``` + +> **Note:** To create brands, you need the multibrand feature enabled in your Okta org. If you're unsure, start with customizing the default brand. diff --git a/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/toolsetup.md b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/toolsetup.md new file mode 100644 index 00000000000..553408f255c --- /dev/null +++ b/packages/@okta/vuepress-site/docs/guides/migrate-customizations/main/terraform/toolsetup.md @@ -0,0 +1,110 @@ +#### Provider configuration with OAuth 2.0 + +Okta recommends using OAuth 2.0 with service apps for automation. First, create an OAuth 2.0 service app in your Admin Console: + +1. Sign in to your Admin Console. +1. Go to **Applications** > **Applications**, and click **Create App Integration**. +1. Select **API Services** and click **Next**. +1. Enter an app name like **Terraform Automation**, and click **Save**. +1. Note the **Client ID**. +1. In the **Public keys** section, generate a public/private key pair for authentication. See [Generate the JWK using the Admin Console](/docs/guides/implement-oauth-for-okta-serviceapp/main/#generate-the-jwk-using-the-admin-console). +1. Ensure that the private key is in PKCS#1 format (begins with `-----BEGIN RSA PRIVATE KEY-----`). If your key begins with `-----BEGIN PRIVATE KEY-----` (PKCS#8 format), convert it: + +```bash + openssl rsa -in original-key.pem -out rsa-key.pem -traditional +``` + +8. Click the **Okta API Scopes** tab and grant: `okta.brands.manage`, `okta.brands.read`, `okta.templates.manage`, `okta.domains.manage` for your app. +9. Click the **Admin roles** tab, and then click **Edit assignments**. +10. Assign the appropriate admin role (for example, super admin for testing or a custom role with brand management permissions). Note that both scopes and admin role assignment are required for authentication to succeed. + +Create a Terraform configuration file with the Okta provider: + +```hcl + terraform { + required_providers { + okta = { + source = "okta/okta" + version = "~> 4.9.1" + } + } + + # Backend configuration for state management + backend "local" { + path = "terraform.tfstate" + } + } + + provider "okta" { + org_name = var.okta_org_name + base_url = var.okta_base_url + client_id = var.okta_client_id + scopes = ["okta.brands.manage", "okta.brands.read", "okta.templates.manage"] + private_key = file(var.okta_private_key_path) # Use file() function to read the key + } + + # Variables for environment switching + variable "okta_org_name" { + type = string + description = "Okta organization name (subdomain)" + } + + variable "okta_base_url" { + type = string + description = "Okta base URL" + default = "okta.com" + } + + variable "okta_client_id" { + type = string + description = "OAuth 2.0 service app client ID" + sensitive = true + } + + variable "okta_private_key_path" { + type = string + description = "Path to private key file (must be PKCS#1 RSA format)" + sensitive = true + } + + variable "target_domain_name" { + type = string + description = "Custom domain for this environment" + } +``` + +#### Environment-specific variable files + +Create separate `.tfvars` files for each environment: + +**test.tfvars:** + +```hcl + okta_org_name = "dev-test" + okta_base_url = "oktapreview.com" + okta_client_id = "0oa1234567890abcdef" + okta_private_key_path = "./keys/test-private-key.pem" + target_domain_name = "test.example.com" +``` + +**production.tfvars:** + +```hcl + okta_org_name = "acme-prod" + okta_base_url = "okta.com" + okta_client_id = "0oa9876543210fedcba" + okta_private_key_path = "./keys/prod-private-key.pem" + target_domain_name = "portal.example.com" +``` + +> **Note**: Never commit private keys to version control. Add `*.pem` and `keys/` to your `.gitignore` file. Consider using environment variables or a secrets management system like HashiCorp Vault for production. + +**Use:** + +```bash + # For test environment + terraform apply -var-file="test.tfvars" + + # For production environment + terraform apply -var-file="production.tfvars" +``` diff --git a/packages/@okta/vuepress-theme-prose/const/navbar.const.js b/packages/@okta/vuepress-theme-prose/const/navbar.const.js index ae1f059ee6d..00fd09c47ac 100644 --- a/packages/@okta/vuepress-theme-prose/const/navbar.const.js +++ b/packages/@okta/vuepress-theme-prose/const/navbar.const.js @@ -59,6 +59,10 @@ export const concepts = [ title: "Monitor Okta", path: "/docs/concepts/monitor/", }, + { + title: "Multibrand architecture", + path: "/docs/concepts/multibrand-architecture/", + }, { title: "Multi-tenant solutions", path: "/docs/concepts/multi-tenancy/", @@ -308,7 +312,7 @@ export const guides = [ guideName: "oie-embedded-sdk-use-case-sign-in-pwd-phone", }, { - title: "User sign out (local app)", + title: "User sign out flow (local app)", guideName: "oie-embedded-sdk-use-case-basic-sign-out", }, ], @@ -579,6 +583,10 @@ export const guides = [ title: "Configure SSO for native apps", guideName: "configure-native-sso", }, + { + title: "Manage credentials using the Okta Client SDK", + guideName: "manage-user-creds", + }, { title: "Request user consent", guideName: "request-user-consent" @@ -649,9 +657,13 @@ export const guides = [ guideName: "custom-url-domain", }, { - title: "Customize associated domains", + title: "Associated domains", guideName: "custom-well-known-uri", }, + { + title: "Passkeys and custom domains", + guideName: "custom-passkeys", + }, { title: "Sign-in page", guideName: "custom-widget", @@ -676,6 +688,10 @@ export const guides = [ title: "Email notifications", guideName: "custom-email", }, + { + title: "Migrate brand customizations", + guideName: "migrate-customizations", + }, ], }, { @@ -832,13 +848,11 @@ export const guides = [ ], }, { - title: "Manage orgs", - hidden: true, + title: "Multi-org management", subLinks: [ { title: "Manage orgs with Okta Aerial", guideName: "manage-orgs-okta-aerial", - hidden: true } ], }, @@ -937,6 +951,10 @@ export const guides = [ title: "Import existing resources", guideName: "terraform-import-existing-resources" }, + { + title: "Manage Okta Identity Governance resources", + guideName: "terraform-oig-resources", + }, ], }, { @@ -1078,6 +1096,9 @@ export const journeys = [ { title: "Sign users in through your web app", journeyName: "OCI-web-sign-in" }, + { title: "Apply your brand to the Okta user experience", + journeyName: "OCI-branding" + }, ] }, ], @@ -1091,6 +1112,7 @@ export const languagesSdk = [ subLinks: [ { title: "Recommended SDKs", path: "/code/" }, { title: "Alternate Sign-in SDKs", path: "/code/alternate-sign-in-sdks/" }, + { title: "Contributing to Python SDK 3.0", path: "/code/contribute-sdk/" }, { title: "Versioning", path: "/code/library-versions/" }, ], }, @@ -1128,38 +1150,37 @@ export const reference = [ path: "/docs/reference/rate-limits/", subLinks: [ { - title: "Rate limit dashboard", - path: "/docs/reference/rl-dashboard/", + title: "Token and OAuth 2.0 app rate limits", + path: "/docs/reference/rl2-token-oauth/", }, { - title: "Authentication and end-user rate limits", - path: "/docs/reference/rl-global-enduser/", + title: "Client-based rate limits", + path: "/docs/reference/rl2-client-based/", }, { - title: "Management rate limits", - path: "/docs/reference/rl-global-mgmt/", + title: "Burst rate limits", + path: "/docs/reference/rl2-burst/", }, - { - title: "Additional limits", - path: "/docs/reference/rl-additional-limits/", + title: "Concurrency limits", + path: "/docs/reference/rl2-concurrency/", }, { - title: "Rate limit best practices", - path: "/docs/reference/rl-best-practices/", + title: "Monitor and troubleshoot rate limits", + path: "/docs/reference/rl2-monitor/", }, { - title: "Client-based rate limits", - path: "/docs/reference/rl-clientbased/", + title: "System Log events for rate limits", + path: "/docs/reference/rl-system-log-events/", }, { - title: "DynamicScale", - path: "/docs/reference/rl-dynamic-scale/", + title: "Increase your rate limits", + path: "/docs/reference/rl2-increase/", }, { - title: "System Log events for rate limits", - path: "/docs/reference/rl-system-log-events/" - }, + title: "Additional Rate limits", + path: "/docs/reference/rl2-limits/", + } ] }, { title: "SSF Transmitter SET payload structures", path: "/docs/reference/ssf-transmitter-sets/" }, @@ -1198,6 +1219,10 @@ export const releaseNotes = [ title: "2025 - Access Gateway", path: "/docs/release-notes/2025-okta-access-gateway/", }, + { + title: "2025 - Aerial", + path: "/docs/release-notes/2025-okta-aerial/", + }, { title: "Archive", path: "/docs/release-notes/archive", diff --git a/packages/@okta/vuepress-theme-prose/util/frameworks.js b/packages/@okta/vuepress-theme-prose/util/frameworks.js index 4c966b227d1..9afad2bc1fd 100644 --- a/packages/@okta/vuepress-theme-prose/util/frameworks.js +++ b/packages/@okta/vuepress-theme-prose/util/frameworks.js @@ -100,6 +100,9 @@ const COMMON_NAME_TO_FANCY_NAME = { oktasaml: 'Okta SAML 2.0 IdP', thirdpartyoidc: 'Third-party OIDC IdP', thirdpartysaml: 'Third-party SAML 2.0 IdP', + terraform: 'Terraform', + powershell: 'Powershell CLI', + api: 'Okta APIs', }; const COMMON_NAME_TO_ICON_NAME = {