Migration guide: Entra ID → Indigo IAM
ska-login-page 1.2.0 adds support for Indigo IAM as the SKAO
authentication provider. Microsoft Entra ID is still supported during the
transition but is now considered the legacy path.
This guide explains the minimum changes a consumer application needs to point
at the SKAO staging Indigo IAM instance at
https://iam-1.staging.devx.skao.int/.
TL;DR
Update to the latest version of
ska-login-pageAuthProvideraccepts a new generic OIDC prop set:authority,clientId,redirectUri,scope,postLogoutRedirectUri,audience.The existing
MSENTRA_*props keep working (deprecated). Apps that are not yet ready to switch can stay on Entra with no code changes.getUserInfonow returns id_token claims rather than MS Graph/me. A compatibility shim preserves the most common Graph field names.
1. Register a client with Indigo IAM
Before changing any code, the application needs a client registration on the target Indigo IAM instance. For the SKAO staging instance, follow the SKAO client-onboarding process and request:
A client_id for your application.
One or more redirect URIs (local development plus deployed environments).
The scopes
openid profile email(request additional ones if your app needs them, e.g.groupsfor group claims).
You will be given a client_id for your app, this replaces any Entra ID client id you were using previously.
2. Update your .env
The library does not read environment variables itself — config flows in
as props on AuthProvider. Add the following to your application’s .env (or
deployment configmap / vault) and wire them through:
# Indigo IAM — SKAO staging
REACT_APP_AUTH_AUTHORITY=https://iam-1.staging.devx.skao.int/
REACT_APP_AUTH_CLIENT_ID=<your-client-id-from-iam>
REACT_APP_AUTH_REDIRECT_URI=http://localhost:8100/
# Optional — defaults to "openid profile email"
REACT_APP_AUTH_SCOPE=openid profile email
For production, swap REACT_APP_AUTH_REDIRECT_URI for your deployed URL
(matching what you registered with IAM).
The REACT_APP_MSENTRA_* variables can be removed once you’ve fully migrated;
they remain readable while the deprecated shim is in place.
3. Update your AuthProvider
Replace the legacy MSENTRA_* props:
import { AuthProvider } from '@ska-telescope/ska-login-page';
<AuthProvider
authority={process.env.REACT_APP_AUTH_AUTHORITY}
clientId={process.env.REACT_APP_AUTH_CLIENT_ID}
redirectUri={process.env.REACT_APP_AUTH_REDIRECT_URI}
scope={process.env.REACT_APP_AUTH_SCOPE}
>
...code that requires authentication...
</AuthProvider>
When authority is a non-Microsoft host (e.g. iam-1.staging.devx.skao.int),
the library automatically:
switches MSAL into
protocolMode: 'OIDC',adds the authority host to
knownAuthorities,uses your
scopevalue as MSAL’sOIDCOptions.defaultScopes.
There is nothing else to configure.
4. Update your Helm chart
Replace the REACT_APP_MSENTRA_* env entries in your deployment with the
generic auth env vars:
env:
- name: REACT_APP_AUTH_REDIRECT_URI
value: "{{ .Values.ingress.hostname }}{{ include "your-chart.ui.ingress.path" . }}/"
- name: REACT_APP_AUTH_AUTHORITY
value: "{{ .Values.ui.auth.authority }}"
- name: REACT_APP_AUTH_CLIENT_ID
value: "{{ .Values.ui.vault.client_id }}"
values.yaml:
ui:
auth:
authority: https://iam-1.staging.devx.skao.int/
The vault annotations and client_id injection pattern stay the same — only
the variable names change.
5. Behaviour changes to be aware of
getUserInfo now decodes id_token claims
Previously called Microsoft Graph /me and returned Graph-shaped fields. It
now decodes the OIDC id_token directly. The returned object contains both the
raw OIDC claims and a backwards-compatibility shim with Graph-shaped
aliases:
| Old (Graph) field | Sourced from (OIDC claim) |
| ——————- | ————————- |
| displayName | name |
| givenName | given_name |
| surname | family_name |
| mail | email |
| userPrincipalName | preferred_username |
| id | oid ?? sub |
| mobilePhone | phone_number |
| preferredLanguage | locale |
Graph fields with no OIDC equivalent — jobTitle, officeLocation,
businessPhones — are no longer populated. If your app reads these you’ll
need to source the data from elsewhere.
Callers should now pass the id_token (e.g. accounts[0]?.idToken) where
they previously passed the access_token. If you accidentally keep passing an
access_token, getUserInfo will detect this (via scp/scope claim or
typ: at+jwt JWT header), emit a one-shot console.warn, and decode the
token anyway — so the migration is visible in yarn test output without
breaking your app.
Deprecated Graph helpers
getUser, getPhoto and getGroupMembers continue to function but now emit
a one-shot console.warn per process on first call. If you are using these, you
should prepare for future changes to get that information via your backend service.
Token source
useGetToken() continues to work unchanged — it returns an access token
acquired from whichever authority you’ve configured. No call-site changes.
6. Staying on Entra ID for now
If your app isn’t ready to migrate, the existing MSENTRA_* props remain
supported (with deprecation warnings). No code changes are required.
You can also use the new generic props with an Entra authority, in which case
the library detects *.microsoftonline.com and keeps MSAL in standard AAD
mode:
<AuthProvider
authority="https://login.microsoftonline.com/<tenant-id>/v2.0"
clientId={process.env.REACT_APP_AUTH_CLIENT_ID}
redirectUri={process.env.REACT_APP_AUTH_REDIRECT_URI}
>
This is the recommended end-state shape — same prop set for both providers, authority alone selects which one.
7. Nondisruptive rollout: runtime feature flag
For a phased cutover, keep both sets of config in your application and pick between them at runtime with a single flag. This lets ops flip an environment between Entra and Indigo (and back, if needed) by changing one deploy-time variable — no rebuild, no library change.
Why runtime, not build-time?
A VITE_USE_INDIGO=true / REACT_APP_USE_INDIGO=true flag baked into the
bundle would tree-shake the unused config branch out (saving ~5–20 KB), but
locks the switch behind a rebuild and full redeploy. The runtime approach:
flips per deploy environment (dev → staging → prod) with a config change,
gives ops a one-step kill-switch if Indigo misbehaves in production,
supports per-user overrides for smoke-testing,
matches the existing
window.envconfig pattern used elsewhere in SKAO React apps.
The small bundle-size cost is worth the operational flexibility during the transition.
Wiring
Expose the flag through whatever runtime-config mechanism your app already
uses. The two common SKAO patterns are a window.env object populated by a
config.js served alongside index.html, or a /config endpoint fetched
at boot. Either way the consumer-side code is the same:
import { AuthProvider } from '@ska-telescope/ska-login-page';
const indigo = {
authority: window.env.INDIGO_AUTHORITY, // e.g. https://iam-1.staging.devx.skao.int/
clientId: window.env.INDIGO_CLIENT_ID,
redirectUri: window.env.INDIGO_REDIRECT_URI,
scope: 'openid profile email',
audience: window.env.INDIGO_AUDIENCE, // required by Indigo for `aud` claim on access tokens
};
const entra = {
authority: `https://login.microsoftonline.com/${window.env.MSENTRA_TENANT_ID}`,
clientId: window.env.MSENTRA_CLIENT_ID,
redirectUri: window.env.MSENTRA_REDIRECT_URI,
scope: 'User.Read openid profile email',
};
const config = window.env.USE_INDIGO === 'true' ? indigo : entra;
export function App() {
return (
<AuthProvider {...config}>
{/* your app */}
</AuthProvider>
);
}
The library doesn’t need to know which provider is active — AuthProvider
already detects *.microsoftonline.com and switches modes accordingly.
Helm / deploy config
Ship both sets of values; gate the active one on USE_INDIGO:
ui:
auth:
useIndigo: false # flip to true to switch this environment
indigo:
authority: https://iam-1.staging.devx.skao.int/
clientId: <indigo-client-id>
audience: <api-audience>
entra:
tenantId: <entra-tenant-id>
clientId: <entra-client-id>
The deployment template writes these into the runtime config.js:
env:
- name: USE_INDIGO
value: "{{ .Values.ui.auth.useIndigo }}"
- name: INDIGO_AUTHORITY
value: "{{ .Values.ui.auth.indigo.authority }}"
- name: INDIGO_CLIENT_ID
value: "{{ .Values.ui.auth.indigo.clientId }}"
# …and so on for the rest