Skip to main content

OpenID Connect (OIDC)

Use Windmill's OIDC provider to authenticate from scripts to cloud providers and other APIs.

OIDC is an EE feature only.

Windmill OpenID Connect (OIDC) allows your scripts to authenticate to other APIs (such as AWS, GCP, or your own API) without having to store sensitive, long-lived credentials. Your Windmill scripts can generate short-lived ID tokens which are passed to a target API to authenticate and "prove" their identity. ID tokens contain subject and claim information specific to the script, flow or worker, allowing for fine-grained access control (e.g. allowing specific scripts to access AWS buckets). This page documents how to generate Windmill tokens and the token format. For guides on how to integrate OIDC with cloud providers like AWS or your own API, see:

Generating OIDC/JWT tokens

OIDC tokens are generated at runtime and are scoped to the script that generated them. Tokens can be generated with Windmill's SDKs or from the REST API directly. Your token must be associated with an audience which identifies the intended recipient of the token. The audience is provided as a parameter when generating the token. If the audience is incorrect, the consumer will reject the token. (For AWS, this audience is sts.amazonaws.com. For your own APIs, you can specify an audience such as auth.yourcompany.com.) If you are using a TypeScript or Python scripts, you can use the Windmill SDK to generate tokens. For other like REST or shell, you should use the REST api directly:

curl -s -X POST -H "Authorization: Bearer $WM_TOKEN" "$BASE_INTERNAL_URL/api/w/$WM_WORKSPACE/oidc/token/MY_AUDIENCE"

Generate the token

Tokens can be generated at runtime with Windmill's SDKs. For example, to generate an OIDC token with audience sts.amazonaws.com to assume an AWS role:

import { STSClient } from 'npm:@aws-sdk/client-sts';
import { AssumeRoleWithWebIdentityCommand } from 'npm:@aws-sdk/client-sts';
import * as wmill from 'npm:windmill-client';

export async function main() {
const token = await wmill.getIdToken('sts.amazonaws.com');

const command = new AssumeRoleWithWebIdentityCommand({
RoleArn: 'arn:aws:iam::000000000000:role/my_aws_role',
WebIdentityToken: token,
RoleSessionName: 'my_session'
});

const client = new STSClient({ region: 'us-east-1' });
console.log(await client.send(command));
}

Token payload reference

OIDC tokens generated by Windmill's OIDC provider are signed JSON web tokens (JWTs). The ID token is encoded by using RS256 and signed with a dedicated private key. The expiry time for the token is set for 48 hours. The token includes standard claims:

  • aud: Intended audience for the token. This field is set by the user when fetching the ID token and is the only customizable field.
  • iss: The issuer of the token, your base_url set in in your instance settings
  • sub: The subject claim is validated by your cloud provider. The subject is formatted as {email}::{script_path}::{flow_path}::{workspace}.
  • exp: The expiration time of the token.
  • iat: The time the token was issued.
  • alg: The algorithm used to sign the token.
  • kid: The key ID used to sign the token.

The token also includes custom claims provided by Windmill. To see the full list of claims supported by Windmill's OIDC provider, see the claims_supported entries at <base_url>/api/oidc/.well-known/openid-configuration. Note that not all cloud providers support all claims.

  • job_id: Job id
  • path: Job's path (the script path for a script or a virtual path for a step part of a flow)
  • flow_path: If job is part of a flow, contain the path of the flow
  • groups: the groups the requester is part of
  • username: the username the requester has in the given workspace
  • email: the email of the requester
  • workspace: workspace_id the job is executed in

The following example token was requested with the audience sts.amazonaws.com from the script u/admin/ambitious_script in the foobar workspace:

eyJhbGciOiJSUzI1NiIsImtpZCI6IndpbmRtaWxsIn0.eyJpc3MiOiJodHRwczovL215Y29tcGFueS5jb20iLCJhdWQiOlsic3RzLmFtYXpvbi5jb20iXSwiZXhwIjoxNzA1NjYwOTU1LCJpYXQiOjE3MDU0ODgxNTUsInN1YiI6ImFkbWluQHdpbmRtaWxsLmRldjo6dS9hZG1pbi9hbWJpdGlvdXNfc2NyaXB0Ojpub19mbG93Ojpmb29iYXIiLCJlbWFpbCI6ImFkbWluQHdpbmRtaWxsLmRldiIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJqb2JfaWQiOiIwMThkMTcwNC0wMGMxLTA1YmItNDE4My1lNTZhOGVkZWI4MjMiLCJwYXRoIjoidS9hZG1pbi9hbWJpdGlvdXNfc2NyaXB0IiwiZmxvd19wYXRoIjpudWxsLCJncm91cHMiOlsiYWxsIl0sInVzZXJuYW1lIjoiYWRtaW4iLCJlbWFpbCI6ImFkbWluQHdpbmRtaWxsLmRldiIsIndvcmtzcGFjZSI6ImZvb2JhciJ9.FLOAMsT7wTuCMM7hdZBGU5uMna3v-3FVXbouQ5CXJ61Vp7Ew82w9K2IFdb5rE-x-YDra-MdT8F3v1CNcvB1vQ4jSsgPLFm_fDu0aA2fI_B69Kno2KBpd5afXVhMUwSF4zyRiruZdIS1hL8PjeNsMOIfy-c_FnF_BchbarXswpo6_UjjsJg353l2C5wOqny8OT4fNGn4yyvTM5PDPaisxOrBuSQDK0GhuB1zSZ4OAGChnd5-KwopqlYmm0NlNiUUTYa5tORtNfbvDFM-hrQ1GOKCKn18nmSV5QKjzn8Au8lP8qNmoZPXdXhxr4yPx8mxWHge7ckABZfB3xGpufG436g

{
"iss": "https://mycompany.com/api/oidc/",
"aud": [
"sts.amazonaws.com"
],
"exp": 1705660955,
"iat": 1705488155,
"sub": "admin@windmill.dev::u/admin/ambitious_script::no_flow::foobar",
"email": "admin@windmill.dev",
"email_verified": true,
"job_id": "018d1704-00c1-05bb-4183-e56a8edeb823",
"path": "u/admin/ambitious_script",
"flow_path": null,
"groups": [
"all"
],
"username": "admin",
"workspace": "foobar"
}

Settings discovery

Windmill's OIDC settings are available at the following discovery URL:

<base_url>/api/oidc/.well-known/openid-configuration

Jwks are available directly at:

<base_url>/api/oidc/jwks

Use OIDC with AWS

Create an identity provider with AWS for OIDC

  1. Search for the Identity Providers tab in the console search or IAM.
  2. Add provider
  3. Select OpenID Connect
  4. Provider Url: <base_url>/api/oidc/, audience: sts.amazonaws.com

Create a role

  1. IAM -> Roles -> Create Role
  2. Web Identity
  3. Pick provider created above, audience: sts.amazonaws.com
  4. Pick the permission policy to attach to this role. You can create as many roles as needed so it's best to be specific.
  5. fill the Trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRoleWithWebIdentity",
"Principal": {
"Federated": "arn:aws:iam::976079455550:oidc-provider/<base_url>/api/oidc/"
},
"Condition": {
"StringEquals": {
"<base_url>/api/oidc/:aud": "sts.amazonaws.com",
"<base_url>/api/oidc/:email": "example@example.com",
...
}
}
}
]
}

Note that AWS only supports conditions on the aud, sub, and email claims. You can use StringLike on the sub claim to limit by job, flow, or workspace:

"StringLike": {
"<base_url>/api/oidc/:sub": "*::<workspace>",
"<base_url>/api/oidc/:sub": "*::<script_path>::*::*",
"<base_url>/api/oidc/:sub": "*::*::<flow_path>::*"
}

Get AWS credentials

import { STSClient } from 'npm:@aws-sdk/client-sts';
import { AssumeRoleWithWebIdentityCommand } from 'npm:@aws-sdk/client-sts';
import * as wmill from 'npm:windmill-client';

export async function main() {
const token = await wmill.getIdToken('sts.amazonaws.com');

const command = new AssumeRoleWithWebIdentityCommand({
RoleArn: 'arn:aws:iam::000000000000:role/my_aws_role',
WebIdentityToken: token,
RoleSessionName: 'my_session'
});

const client = new STSClient({ region: 'us-east-1' });
console.log(await client.send(command));
}

You can now get the ephemeral access key, secret key, and session token from it to use with any AWS API.

credentials = credentials["Credentials"]

aws_access_key_id=credentials["AccessKeyId"]
aws_secret_access_key=credentials["SecretAccessKey"]
aws_session_token=credentials["SessionToken"]

Use OIDC with Hashicorp Vault

vault auth enable jwt

vault write auth/jwt/config \
bound_issuer="<base_url>/api/oidc/" \
oidc_discovery_url="<base_url>/api/oidc/"

vault write auth/jwt/role/myproject-production -<<EOF
{
"role_type": "jwt",
"bound_audiences": ["MY_AUDIENCE"],
"bound_claims": { "email": "admin@windmill.dev"},
"user_claim": "sub",
"policies": ["myproject-production"],
"ttl": "10m"
}
EOF

vault policy write myproject-production - <<EOF
# Read-only permission on 'secret/data/production/*' path

path "secret/data/production/*" {
capabilities = [ "read" ]
}
EOF

Test it

Write a secret at production/foo:

vault kv put -mount=secret production/foo foo=world
export VAULT_ADDR='http://127.0.0.1:8200'

export VAULT_JWT=$(curl -s -X POST -H "Authorization: Bearer $WM_TOKEN" "$BASE_INTERNAL_URL/api/w/$WM_WORKSPACE/oidc/token/MY_AUDIENCE")
export VAULT_TOKEN=$(vault write -field=token auth/jwt/login role=myproject-production jwt=$VAULT_JWT)

vault kv get -mount=secret production/foo