From b8b090c7f2430ce45ece8a2fc14d20c592e37c1b Mon Sep 17 00:00:00 2001 From: Madison Ehlers Date: Wed, 30 Sep 2026 16:21:20 -0500 Subject: [PATCH] [VPI-331] Document per-account token exchange OIDC configuration Co-authored-by: Cursor --- docs/guides/authentication.mdx | 2 + .../token-exchange-oidc-configuration.mdx | 244 ++++++++++++++++++ sidebars.js | 4 + 3 files changed, 250 insertions(+) create mode 100644 docs/guides/token-exchange-oidc-configuration.mdx diff --git a/docs/guides/authentication.mdx b/docs/guides/authentication.mdx index d549505a..3e8e4059 100644 --- a/docs/guides/authentication.mdx +++ b/docs/guides/authentication.mdx @@ -128,6 +128,8 @@ for a Vertex Platform token. To enable token exchange, your deployment must be configured with the `OidcUserinfoEndpoint` Cloudformation parameter pointing to your [IdP’s userinfo endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo). +Root administrators can also set a userinfo endpoint per account. See [Token +exchange OIDC configuration](./token-exchange-oidc-configuration). + + +```shell +curl --request POST 'https://platform.vertexvis.com/accounts' \ + --header 'Authorization: Bearer [ROOT_ACCESS_TOKEN]' \ + --header 'Content-Type: application/vnd.api+json' \ + --data '{ + "data": { + "type": "account", + "attributes": { + "name": "Example account", + "oidc_userinfo_endpoint": "https://idp.example.com/oauth2/userinfo" + } + } + }' +``` + + + + +```json +{ + "data": { + "type": "account", + "id": "[YOUR_ACCOUNT_ID]", + "attributes": { + "name": "Example account", + "status": "active", + "created": "2026-09-30T18:00:00Z", + // highlight-next-line + "oidc_userinfo_endpoint": "https://idp.example.com/oauth2/userinfo" + } + } +} +``` + + + + +### Update an account's endpoint + +Send only the attributes you want to change: + +- **Set or change the endpoint:** send the new URL. +- **Keep the current endpoint:** omit `oidc_userinfo_endpoint`. +- **Clear the endpoint and use the deployment fallback:** send `null`. + +```shell +curl --request PATCH 'https://platform.vertexvis.com/accounts/[YOUR_ACCOUNT_ID]' \ + --header 'Authorization: Bearer [ROOT_ACCESS_TOKEN]' \ + --header 'Content-Type: application/vnd.api+json' \ + --data '{ + "data": { + "type": "account", + "attributes": { + "oidc_userinfo_endpoint": "https://idp.example.com/oauth2/userinfo" + } + } + }' +``` + +To clear the endpoint: + +```shell +curl --request PATCH 'https://platform.vertexvis.com/accounts/[YOUR_ACCOUNT_ID]' \ + --header 'Authorization: Bearer [ROOT_ACCESS_TOKEN]' \ + --header 'Content-Type: application/vnd.api+json' \ + --data '{ + "data": { + "type": "account", + "attributes": { + "oidc_userinfo_endpoint": null + } + } + }' +``` + +### Check the configured endpoint + +`GET /accounts/{id}` returns the value in +`data.attributes.oidc_userinfo_endpoint`. The attribute is absent or `null` when +the account uses the deployment fallback. + +```shell +curl --request GET 'https://platform.vertexvis.com/accounts/[YOUR_ACCOUNT_ID]' \ + --header 'Authorization: Bearer [ROOT_ACCESS_TOKEN]' \ + | jq .data.attributes.oidc_userinfo_endpoint +``` + +### Use the Admin Dashboard + +Root administrators can also set the endpoint in the Vertex Admin Dashboard: + +- **New account:** the create form, including first-run setup, has an optional + **OIDC userinfo endpoint** field. Leave it empty to use the deployment + fallback. +- **Existing account:** the account's detail page shows the configured endpoint + or **Not configured**. Edit the account, then enter a URL or empty the field + to clear it, and save. + +The dashboard displays the API's error if the URL is rejected. + +## Link users before exchanging tokens + +Configuring the endpoint doesn't create users. An exchange succeeds only when +the account that owns the application has a user whose `idpId` exactly matches +the `sub` returned by the userinfo endpoint. Create these users with `POST /users` before users exchange tokens. See [User management](./user-management) +for details. + +## Troubleshoot token exchange + +A successful exchange returns `200 OK` with a user access token that expires +after about one hour (`expires_in` is `3600` or slightly less). + +| Response | Cause | +| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `400` `invalid_request` | No user in the account has an `idpId` that matches the IdP's `sub`. | +| `400` `invalid_request` | Neither the account nor the deployment has a valid userinfo endpoint. Set `oidc_userinfo_endpoint` on the account or `OIDC_USERINFO_ENDPOINT` on the deployment. | +| `401` `invalid_credentials` | The userinfo endpoint rejected the subject token with `401` or `403`, or the client credentials are wrong. | +| `500` `server_error` | The Platform couldn't reach the userinfo endpoint or got another error from it, such as a redirect or a TLS failure against an untrusted CA. | + +The missing-configuration case is a failed precondition in the Platform. The +token endpoint reports it as `invalid_request`, the same as an unlinked user, so +check the account's endpoint and your users' `idpId` values when you see this +error. The failure affects only exchanges that need the missing configuration; +it doesn't stop the Platform from starting or block other accounts. + +## Trust a private certificate authority + +If your IdP's userinfo endpoint uses a certificate from a private CA, the +Platform must trust that CA. Store the CA certificates in S3 and set these +environment variables on the account management service: + +| Variable | Description | +| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `OIDC_CERT_S3_BUCKET` | The S3 bucket that holds the CA certificates. For deployments installed with CloudFormation, this is the `ConfigBucket` parameter. | +| `OIDC_CERT_S3_PREFIX` | The key prefix to load certificates from. Defaults to `certificates/`. | + +The service loads certificates when it starts: + +- It reads every object under the prefix whose key ends in `.pem`, and adds every + certificate in those files to the default JVM trust roots. +- If the bucket or prefix can't be listed, or a certificate can't be downloaded + or parsed, the service doesn't start. +- If the prefix has no `.pem` files, the service uses the default JVM trust + roots. + +The service must be able to read the bucket (`s3:ListBucket` and +`s3:GetObject`). Certificate changes take effect only after the account +management service restarts. diff --git a/sidebars.js b/sidebars.js index 7faf51ee..5a8dd943 100644 --- a/sidebars.js +++ b/sidebars.js @@ -68,6 +68,10 @@ module.exports = { type: 'doc', id: 'guides/collaboration-apis', }, + { + type: 'doc', + id: 'guides/token-exchange-oidc-configuration', + }, { type: 'doc', id: 'guides/user-management',