← Docs

Enterprise · Identity federation

Enterprise SSO

This page is for IT admins standing up Parleq’s LLM cleanup access behind one corporate OIDC sign-in instead of handing each user an API key. The user signs in once with their company identity provider; Parleq federates that sign-in into your cloud — AWS Bedrock and/or Google Cloud Vertex AI — and mints short-lived per-cloud credentials at call time. No long-lived cloud secret is stored on the device.

Setting this up at home without an IT department? The same engine works for a single user on a free identity provider (Cognito) or a personal Google account — with different gotchas. See the DIY: SSO & Sign-in with Google guide for the path-picker and the burn-an-hour gotchas.

What you get

One engine, several legs. The user runs a single OIDC sign-in (PKCE); Parleq trades the resulting token for short-lived cloud credentials on demand. The properties that matter for a security review:

How it works

Parleq runs an OIDC authorization-code flow with PKCE in a system web view (no embedded browser, no password ever touches Parleq). The flow yields a refresh token, which Parleq stores in the macOS Keychain and uses to mint short-lived ID tokens on demand. Each cloud leg then trades that ID token for its own credentials:

Two principles bound the data handling:

Refresh tokens are rotated where the IdP supports it: Parleq persists each newly-issued refresh token to the Keychain the instant it arrives, before doing anything else, so a rotated token is never lost. The access and ID tokens stay in process memory only. Offboarding (disabling the user at the IdP) takes effect at Parleq’s next token refresh; note that already-issued STS credentials are not revocable and live to their session maximum, so keep the AWS session duration short if you need tight offboarding (see the managed-configuration reference and security review).

Azure OpenAI note: the Azure OpenAI provider keeps its existing Microsoft Entra ID auth (via az login). OIDC federation for Azure is not part of this release — the two federated legs today are AWS Bedrock and Google Cloud Vertex AI; Azure OpenAI keeps its existing Entra ID and API-key auth modes.

Enabling it in Parleq

Set the shared OIDC section, then turn on whichever cloud leg(s) you need. All of these can be pinned fleet-wide via MDM — see the Enterprise OIDC federation keys. Hand-editing ~/.parleq/config.json directly:

{
  "oidc": {
    "issuer": "https://acme.okta.com",
    "client_id": "0oaEXAMPLEclientid",
    "scopes": ["openid", "profile", "email", "offline_access"],
    "ephemeral_browser": false
  },
  "aws": {
    "region": "us-east-1",
    "auth_mode": "oidc",
    "role_arn": "arn:aws:iam::123456789012:role/ParleqBedrock",
    "session_duration_seconds": 3600
  },
  "vertex": {
    "project": "my-gcp-project",
    "region": "us-central1",
    "auth_mode": "oidcFederation",
    "workforce_provider": "locations/global/workforcePools/parleq-pool/providers/oidc"
  }
}

Then open Settings → Company Account and sign in. That section shows the signed-in identity and a connection doctor that runs token-free discovery plus a silent refresh and reports each cloud leg’s last success/failure — the place to confirm a new IdP or cloud setup end-to-end.

Identity-provider playbooks

Okta

Register Parleq as a native / public application (authorization-code + PKCE, no client secret):

Refresh rotation: Okta rotates refresh tokens and runs replay detection — reusing an old refresh token after rotation revokes the session. Parleq handles this by persisting the rotated token on receipt, but it means a lost rotated token forces a fresh sign-in. Because Okta is a public issuer with a publicly-fetchable JWKS, it works for both the AWS and GCP legs.

Keycloak

Create a public client in your realm with the standard (authorization-code) flow enabled and PKCE (S256) required:

A ready-made dev rig — docker-compose Keycloak with this exact client and a test user — lives in the repo at scripts/dev/keycloak/. One caveat for end-to-end cloud testing: AWS STS fetches the issuer’s JWKS from its own side, so a localhost issuer can’t pass AssumeRoleWithWebIdentity. GCP accepts an inline-uploaded JWKS (no fetch), but requires the issuer URI itself to use HTTPS — so a plain-http localhost issuer works for neither cloud leg. For live cloud tests, use a hosted free issuer (Cognito or an Okta Integrator org) or front Keycloak with a stable-hostname HTTPS tunnel.

Generic OIDC

Any spec-compliant OIDC provider works if it offers:

AWS Bedrock setup

In IAM, register the IdP as an OpenID Connect identity provider (the issuer URL plus the client ID as an audience), then create a role whose trust policy admits tokens from that provider. AssumeRoleWithWebIdentity is the gate: the call itself is unauthenticated, so the role’s trust policy is what authorizes access — constrain it with an aud condition.

Example role trust policy (placeholder account ID and client ID):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/acme.okta.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "acme.okta.com:aud": "0oaEXAMPLEclientid"
        }
      }
    }
  ]
}

Attach a permissions policy granting Bedrock model invocation:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
      "Resource": "*"
    }
  ]
}

"Resource": "*" keeps the example short — scope it to the specific model or inference-profile ARNs you have approved for least privilege.

Put the role’s ARN in aws.role_arn and set aws.auth_mode to oidc. The requested STS session duration is aws.session_duration_seconds (default 3600, clamped to 900–43200). Bedrock model access is per-region, so make sure the chosen region has the model enabled. The role’s session name carries the signed-in user’s email for CloudTrail attribution.

Google Cloud Vertex AI setup

Create a Workforce Identity Pool and add an OIDC provider for your IdP (issuer URL + the client ID as the allowed audience). For a publicly-reachable issuer GCP fetches the JWKS automatically; for a non-public issuer you can upload the JWKS inline (jwks_json) — but note the issuer URI must use HTTPS either way. Workforce Identity Pools are an Organization-level resource — creating one requires a GCP Organization (Cloud Identity) and iam.workforcePools.create, not just project access.

Workforce-federated calls must specify a billing/quota project: Parleq sends your configured vertex.project as the x-goog-user-project header on every Vertex request, so that project needs the Vertex AI API enabled and the federated principal needs serviceusage.services.use on it.

Google account → Vertex AI directly

The third leg skips federation entirely. GCP refuses to accept Google itself as a workforce IdP, so the workforce-pool path can’t do “sign in with Google, dictate with Gemini.” Instead, Parleq’s own Google sign-in requests the cloud-platform scope, and the resulting OAuth access token is a valid Vertex bearer directly — exactly what gcloud Application Default Credentials produces, done in-app, with no broker and no gcloud install. This is the googleOAuth Vertex mode.

For org-owned Google identities this works the same way — with no “unverified app” warning and no refresh-token cap if the OAuth client lives in your Google Cloud organization. The personal-@gmail variant, with its consent-screen trade-offs and the granular-consent gotcha, is covered in the DIY guide.

Pin it fleet-wide

Every value above can be pushed via MDM so users sign in but can’t re-point the app at a personal tenant. The nine enterprise OIDC keys — oidcIssuer, oidcClientID, oidcScopes, oidcRedirectURI, oidcExtraAuthParams, oidcEphemeralBrowserSession, awsRoleArn, awsSessionDurationSeconds, and vertexWorkforceProvider — sit alongside the existing managed-config surface. Enable a leg by also pinning awsAuthMode: oidc and/or vertexAuthMode: oidcFederation (for Workforce federation) or vertexAuthMode: googleOAuth (for the Sign-in-with-Google direct leg). See the Managed Configuration reference → Enterprise OIDC federation for the full table, and the Admin Guide for the deployment workflow.

Troubleshooting

Start at Settings → Company Account → Test connection. The connection doctor reports which hop failed (IdP discovery / token exchange / cloud provider) with the server’s reason, which usually points straight at the fix below.

Symptom Cause / fix
tokenCacheNotFound / SSO token error on AWS If your IAM OIDC provider URL or the IdP issuer ends in /#, drop the trailing # — the INI parser strips it mid-value. Confirm the issuer in the doctor matches the IAM OIDC provider exactly.
AssumeRoleWithWebIdentity rejected (audience / not authorized) The role’s trust policy aud condition must equal the OIDC client ID, and the Federated principal must name the same issuer host you registered. The STS call is unauthenticated — the trust policy is the only gate.
AWS leg fails with a JWKS / issuer error on a self-hosted IdP STS fetches the issuer’s JWKS server-side, so a localhost issuer cannot pass. Front the IdP with a stable-hostname HTTPS URL (or use a hosted issuer such as Cognito).
GCP workforce provider create fails: “issuer must be HTTPS” GCP can take an inline-uploaded JWKS (no fetch), but the issuer URI itself must still be HTTPS — a plain-http localhost issuer works for neither cloud leg.
Sign-in window flashes open and closes (invalid_scope) The IdP rejected a requested scope the client doesn’t have enabled. On a Cognito app client this is usually profile — request only ["openid", "email"] or enable Profile in the app client. Cognito also rejects offline_access.
Signed out shortly after signing in (refresh rejected) A rotated refresh token was lost, or the IdP enforces replay detection. Sign in again; for IdPs that rotate on every refresh, ensure nothing else is reusing the token.