Skip to content
Merged
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.33.0] - 2026-07-20

### Added
- **Sign in through the identity provider.** The UI login screen can now send the user to the identity provider's own login page, where the federated providers configured on the realm (CILogon, EarthScope, ORCID) are offered. This matters because users arriving with institutional credentials have no password in the realm, so the username/password form never worked for them — their only way in was to sign in on nationaldataplatform.org, find their access token and paste it by hand.
- Off by default behind `OIDC_ENABLED`, and configured with `OIDC_ISSUER` and `OIDC_CLIENT_ID`. The Authorization Code flow with PKCE is used against a **public** client, so no client secret has to be shipped with the Endpoint. `OIDC_SCOPE` is optional and defaults to `openid profile email`.
- Nothing about a particular identity provider is baked into the build: the endpoints are read from the realm's OpenID discovery document, and the button wording comes from `OIDC_BUTTON_LABEL` / `OIDC_HELP_TEXT`, whose defaults are provider-neutral. Moving from a local Keycloak to production is a change of `OIDC_ISSUER`.
- The callback is handled at `<ROOT_PATH>/ui/auth/callback`, which must be registered as a valid redirect URI on the client. The client must also issue the `sub` claim — `AUTH_API_URL` looks the user up by it. `OIDC_ISSUER` and `AUTH_API_URL` must point at the same identity provider. See `example.env` and [docs/configuration.md](docs/configuration.md) for the full requirements.

### Changed
- When the identity provider authenticates the user but the Endpoint cannot validate the resulting token, the UI now says so explicitly instead of reporting "Invalid token", which read as mistyped credentials even though the sign-in had succeeded.

### Backwards compatibility
- The access token and username/password sign-in methods are unchanged and remain available.
- `OIDC_ENABLED` defaults to `False`, so existing deployments behave exactly as before until they opt in. A deployment that switches it on without both `OIDC_ISSUER` and `OIDC_CLIENT_ID` shows no button rather than one that fails on click.
- PKCE needs `crypto.subtle`, which browsers expose only in secure contexts. On a plain-http or bare-IP deployment the button is shown disabled with an explanation rather than silently downgrading to the weaker `plain` challenge method; the other two sign-in methods still work there.

## [0.32.4] - 2026-06-05

### Added
Expand Down
2 changes: 1 addition & 1 deletion api/config/swagger_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class Settings(BaseSettings):

swagger_title: str = "API Documentation"
swagger_description: str = "This is the API documentation."
swagger_version: str = "0.32.4"
swagger_version: str = "0.33.0"
root_path: str = "" # API root path prefix (e.g., "/test" or "")
is_public: bool = True
metrics_endpoint: str = "https://federation.ndp.utah.edu/metrics/"
Expand Down
80 changes: 80 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,86 @@ AAI administrator.

---

## Identity provider sign-in (optional, off by default)

By default the UI login screen offers two ways in: paste an access token, or
type a username and password. Both assume the user holds a credential that
lives in the realm itself.

Users who arrive through a **federated provider** configured on the realm —
CILogon (institutional credentials), EarthScope, ORCID — do not. They have no
password in the realm, so the username/password form cannot work for them, and
their only route in is to sign in on the platform's own site, find their access
token and paste it by hand.

Turning this on adds a **third** button that sends the user to the identity
provider's own login page, where those providers appear. It uses the OAuth
Authorization Code flow with PKCE against a **public** client, so no client
secret is shipped with the Endpoint.

The other two methods are unchanged and remain available either way.

#### `OIDC_ENABLED`
*Optional · default: `False`.*
Master switch. While `False`, the login screen behaves exactly as before and
none of the values below are read. **Where:** you decide.

#### `OIDC_ISSUER`
*Required when `OIDC_ENABLED=True`.*
The realm URL, e.g. `https://idp.nationaldataplatform.org/realms/NDP`. The
authorization, token and userinfo endpoints are read from this realm's OpenID
discovery document, so no provider-specific URL is hardcoded — moving from a
local Keycloak to production is just a change of this value.
**Where:** your AAI/Keycloak administrator.

#### `OIDC_CLIENT_ID`
*Required when `OIDC_ENABLED=True`.*
The client registered for **this** Endpoint (see the requirements below).
**Where:** your AAI/Keycloak administrator.

#### `OIDC_SCOPE`
*Optional · default: `openid profile email`.*

#### `OIDC_BUTTON_LABEL` / `OIDC_HELP_TEXT`
*Optional · defaults: `Sign in with your identity provider` / empty.*
Wording for the button and the line under it. The defaults are
provider-neutral on purpose: only the deployment knows which providers its
realm offers. Naming providers the realm does **not** have is worse than
saying nothing, so `OIDC_HELP_TEXT` shows no second line while empty.

### What your identity provider administrator must register

1. **A public client with PKCE (S256).** Public rather than confidential so
that no client secret has to be distributed with the Endpoint, which is
self-hosted by many institutions.
2. **This deployment's callback URL** as a valid redirect URI on that client:
`<scheme>://<host>[:<port>]<ROOT_PATH>/ui/auth/callback` — for example
`https://my-endpoint.example.org/ep-api/ui/auth/callback`. The Endpoint
derives this from the browser's address and `ROOT_PATH`, so you never
configure it, but it must match what is registered.
3. **The `sub` claim in access tokens.** `AUTH_API_URL` looks the user up by
`sub`. Since Keycloak 24 that claim comes from the built-in `basic` client
scope, and a realm imported from an older export may not have `basic` among
its default scopes — in which case a freshly created client silently mints
tokens without `sub`. Assign the scope to the client explicitly.

### Two things that commonly go wrong

**`OIDC_ISSUER` and `AUTH_API_URL` must point at the same identity provider.**
The token is minted by one and validated by the other. If they disagree, the
user's `sub` will not exist on the validating side and sign-in fails at the
last step — after a successful login, which makes it look like a credentials
problem when it is not.

**The UI must be served over https.** Browsers only expose the crypto API that
PKCE needs in secure contexts. On a plain-http or bare-IP deployment the button
is shown **disabled** with an explanation rather than silently falling back to
the weaker `plain` challenge method; the other two sign-in methods still work
there. `localhost` counts as a secure context, so an SSH tunnel is enough for
testing.

---

## Local catalog (where this EP stores its data)

#### `LOCAL_CATALOG_BACKEND`
Expand Down
12 changes: 11 additions & 1 deletion entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@
# Generate runtime config for the React UI. AFFINITIES_EP_UUID lets the
# UI highlight the user's group that ties them to this endpoint without
# having to look the value up against the deployment env manually.
# OIDC_ENABLED gates the "sign in through the identity provider" button; it
# defaults to False so a deployment that says nothing keeps the login screen
# it had before. The realm URL, client and wording all come from the
# environment so nothing about a particular identity provider is baked in.
cat > /app/ui/build/config.js <<EOF
window.__EP_CONFIG__ = {
rootPath: "${ROOT_PATH}",
affinitiesEpUuid: "${AFFINITIES_EP_UUID}"
affinitiesEpUuid: "${AFFINITIES_EP_UUID}",
oidcEnabled: "${OIDC_ENABLED:-False}",
oidcIssuer: "${OIDC_ISSUER}",
oidcClientId: "${OIDC_CLIENT_ID}",
oidcScope: "${OIDC_SCOPE}",
oidcButtonLabel: "${OIDC_BUTTON_LABEL}",
oidcHelpText: "${OIDC_HELP_TEXT}"
};
EOF

Expand Down
67 changes: 67 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,73 @@ TEST_TOKEN=testing_token
# This endpoint is used to validate tokens and fetch user details
AUTH_API_URL=https://idp.nationaldataplatform.org/temp/information

# ----------------------------------------------
# Identity provider sign-in (Optional · OFF by default)
# ----------------------------------------------
# Adds a button to the UI login screen that sends the user to the identity
# provider's own login page. This is what makes the federated providers
# configured on the realm (e.g. CILogon, EarthScope, ORCID) usable: those
# users have no password in the realm, so the username/password form cannot
# work for them and pasting an access token by hand is their only alternative.
#
# This is an ADDITIONAL way in. The access token and username/password forms
# stay exactly as they are, whether this is on or off.

# Master switch. False (the default) means the login screen behaves exactly as
# it did before and none of the values below are read.
OIDC_ENABLED=False

# The realm URL. Everything else (authorization, token and userinfo endpoints)
# is read from its OpenID discovery document, so no provider-specific URL is
# ever hardcoded and pointing this at a different Keycloak is all that a move
# from a local instance to production requires.
# Required when OIDC_ENABLED=True.
# OIDC_ISSUER=https://idp.nationaldataplatform.org/realms/NDP

# The client id registered for THIS Endpoint. Required when OIDC_ENABLED=True.
# OIDC_CLIENT_ID=ndp-ep-ui

# Optional. Defaults to "openid profile email" when empty.
# OIDC_SCOPE=

# Optional wording. The defaults are provider-neutral because only the
# deployment knows which providers its realm actually offers. Leave
# OIDC_HELP_TEXT empty to show no second line.
# Default: "Sign in with your identity provider"
# OIDC_BUTTON_LABEL=Sign in with National Data Platform
# OIDC_HELP_TEXT=Use your institutional credentials, EarthScope or ORCID.

# ----------------------------------------------
# What the identity provider administrator must register
# ----------------------------------------------
# 1. A PUBLIC client with PKCE (S256) enabled. Public rather than confidential
# so that no client secret has to be shipped with the Endpoint, which is
# self-hosted by many institutions.
#
# 2. This deployment's callback URL as a valid redirect URI on that client:
# <scheme>://<host>[:<port>]<ROOT_PATH>/ui/auth/callback
# e.g. https://my-endpoint.example.org/ep-api/ui/auth/callback
# The Endpoint derives this from the browser's address and ROOT_PATH, so it
# is never configured here — but it must match what is registered.
#
# 3. The client MUST issue the `sub` claim in its access tokens. AUTH_API_URL
# looks the user up by `sub`; a token without it fails validation and the
# UI reports that the Endpoint could not validate the token, even though
# the sign-in itself succeeded.
# Since Keycloak 24 that claim comes from the built-in `basic` client
# scope. A realm imported from an older export may not have `basic` among
# its default scopes, in which case a newly created client silently mints
# tokens with no `sub` — assign the scope to the client explicitly.
#
# 4. OIDC_ISSUER and AUTH_API_URL must point at the SAME identity provider.
# The token is minted by one and validated by the other; if they disagree,
# the user's `sub` will not exist on the validating side and sign-in fails.
#
# 5. Serve the UI over https. Browsers only expose the crypto API that PKCE
# needs in secure contexts, so on a plain-http/bare-IP deployment the
# button is shown disabled with an explanation (`localhost` counts as
# secure, so an SSH tunnel works for testing).

# ==============================================
# External Service Integrations
# ==============================================
Expand Down
Loading
Loading