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-page

  • AuthProvider accepts 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.

  • getUserInfo now 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. groups for 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 scope value as MSAL’s OIDCOptions.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.env config 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