This is a quick tutorial on how to configure Okta authentication.
Quick setup with Igniter
The fastest way to add Okta authentication is with the Igniter generator:
mix ash_authentication.add_strategy okta
This creates the UserIdentity resource, register action, secrets wiring, and strategy DSL for you. Follow the printed instructions to register your Okta application and set the required environment variables. The rest of this tutorial covers manual setup.
Manual setup
First you'll need a registered application in your Okta Admin Console to get your OAuth 2.0 credentials.
- In the Admin Console, go to Applications > Applications
- Click Create App Integration
- Choose OIDC - OpenID Connect as the sign-in method, and Web Application as the application type, then click Next
- Give the app a name
- Under Sign-in redirect URIs, add your callback URL — e.g.
http://localhost:4000/auth/user/okta/callback - Choose which Okta users should have access (assignments) and click Save
- From the app's General tab, copy the Client ID and Client secret
You'll also need your Okta domain (e.g. mycompany.okta.com) — visible in the Admin Console URL — and an authorization server. Most installations should use the built-in default Custom Authorization Server: combined, your base_url is https://mycompany.okta.com/oauth2/default.
Org vs Custom Authorization Server
Okta exposes two kinds of authorization servers:
- Custom Authorization Server (recommended) — issuer is
https://YOUR_OKTA_DOMAIN/oauth2/{authServerId}. Every Okta org ships with one nameddefault. Configure claims, scopes, and policies under Security > API > Authorization Servers. - Org Authorization Server — issuer is
https://YOUR_OKTA_DOMAIN. Only suitable for a small number of Okta-internal use cases.
If you're not sure, use the default Custom Authorization Server.
Next we configure our resource to use Okta credentials:
defmodule MyApp.Accounts.User do
use Ash.Resource,
extensions: [AshAuthentication],
domain: MyApp.Accounts
attributes do
# ...
end
authentication do
strategies do
okta do
client_id MyApp.Secrets
client_secret MyApp.Secrets
redirect_uri MyApp.Secrets
base_url MyApp.Secrets
end
end
end
endPlease check the guide on how to properly configure your Secrets. The base_url should resolve to something like https://mycompany.okta.com/oauth2/default.
Then we need to define the action that will handle the OIDC flow. For Okta the action is :register_with_okta — it handles both registration of new users and sign-in for existing ones.
defmodule MyApp.Accounts.User do
require Ash.Resource.Change.Builtins
use Ash.Resource,
extensions: [AshAuthentication],
domain: MyApp.Accounts
# ...
actions do
create :register_with_okta do
argument :user_info, :map, allow_nil?: false
argument :oauth_tokens, :map, allow_nil?: false
upsert? true
upsert_identity :unique_email
change AshAuthentication.GenerateTokenChange
# Required if you have the `identity_resource` configuration enabled.
change AshAuthentication.Strategy.OAuth2.IdentityChange
change {AshAuthentication.Strategy.OAuth2.UserInfoToAttributes, fields: [:email]}
# Required if you're using the password & confirmation strategies
upsert_fields []
change set_attribute(:confirmed_at, &DateTime.utc_now/0)
end
end
# ...
endEnsure you set hashed_password to allow_nil?: true if you are also using the password strategy:
defmodule MyApp.Accounts.User do
# ...
attributes do
# ...
attribute :hashed_password, :string, allow_nil?: true, sensitive?: true
end
# ...
endThen generate and run migrations:
mix ash.codegen make_hashed_password_nullable
mix ash.migrate
Working with Okta groups
If you've configured your authorization server to include a groups claim (under Security > API > Authorization Servers > {server} > Claims), the claim will appear in the user_info argument passed to your register_with_okta action.
The shape of the value depends on the claim's configuration:
- When the claim's value type is Groups with a "Matches regex" / "Starts with" / "Equals" filter, Okta returns a JSON array of group names.
- When the claim's value type is Expression returning a single string, Okta returns a string.
Normalise both shapes before pattern-matching — e.g. wrap with List.wrap/1:
groups = user_info |> Map.get("groups", []) |> List.wrap()For full user/group sync (provisioning users from Okta and keeping group membership in step), prefer SCIM over driving everything off the OIDC groups claim — the claim is only populated when a user signs in, and won't catch group changes made while the user is already authenticated.
Step-up authentication / MFA
To force re-authentication, request specific factors, or pass other Okta-specific authorization parameters, use authorization_params. The acr_values to pass depend on which Okta engine your org runs:
okta do
# ...
authorization_params prompt: "login"
end# Okta Identity Engine (default for tenants created since 2022):
okta do
# ...
authorization_params acr_values: "urn:okta:loa:2fa:any:ifpossible"
end# Okta Classic Engine only:
okta do
# ...
authorization_params acr_values: "urn:okta:loa:2fa:any"
endSee Okta's step-up authentication guide for the current ACR value catalog and engine differences.