Skip to main content

External auth with JWT

Instead of using the built-in authentication and authorization mechanisms, you can generate your own JWT tokens with the desired permissions for your already authenticated users and pass them to Windmill. This way, you control what permissions your users have without having to create them in Windmill.

This feature is Enterprise Edition and Whitelabel only.

For JWT verification, the first option is to pass as an environment variable called JWT_EXT_PUBLIC_KEY the public key in PEM format (RS256). The second option is to pass as JWT_EXT_JWKS_URL the url of the JWKs endpoint to retrieve the public keys from which the JWTs will be matched on the kid field. In the latter case, the instance will refresh its cache of public keys every 15 minutes. The environment variable in both cases has to be passed to all servers and workers.

The JWT token should be prefixed with jwt_ext_ to let Windmill know that this is an external token and passed in the Authorization header as a bearer token. The JWT payload has to contain the following fields:

  • username: the username of the user (for logs)
  • email: the email of the user (for logs)
  • is_admin: a boolean indicating if the user is an admin
  • is_operator: a boolean indicating if the user is an operator (run-only)
  • workspace_id: the workspace id
  • folders: an array of arrays containing 3 values: the name of the folder, a boolean indicating whether the user can write to the folder and a boolean indicating whether the user is the owner of the folder (can manage folder permissions)
  • groups: an array of strings containing the groups to which the user belongs. When using JWT external authentication, group permissions on folders are not checked because the user's permissions depend directly on the folder permissions specified in the JWT.

While you don't need to create the user on Windmill, you should create the workspaces and folders.

Here's an example TypeScript code that generates the JWT token with the required payload and token prefix:

import { createSigner } from 'fast-jwt'

const YOUR_PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----`

async function generateJWT(
kid: string,
username: string,
email: string,
is_admin= false,
is_operator= false,
workspace_id: string,
folders: [string, boolean, boolean][] = [],
groups: string[] = [],
) {
const signer = createSigner({
kid,
algorithm: 'RS256',
key: YOUR_PRIVATE_KEY,
expiresIn: '1h'
})

const token = await signer({
username,
email,
is_admin,
is_operator,
folders,
groups,
workspace_id
})

return "jwt_ext_" + token
}

Embed public apps using your own authentification

On the Enterprise Edition, it is possible for one to embed public apps and reuse the embedding app auth and userbase. To do so, use an iframe as usual with the public app url as src.

For instance: https://your_instance.com/public/foo/a7f83d827f8bbb3a332c730659f4cf39, but in addition, append to the url the jwt token, separated by a '/'.

https://your_instance.com/public/foo/a7f83d827f8bbb3a332c730659f4cf39/eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IiJ9.ey1c2VybmFtZSI6ImZvbyIsImVtYWlsIjoiZm9vQHdpbmRtaWxsLmRldiIsImlzX2FkbWluIjp0cnVlLCJpc19vcGVyYXRvciI6ZmFsc2UsImZvbGRlcnMiOltdLCJncm91cHMiOltdLCJ3b3Jrc3BhY2VfaWQiOiJmb28iLCJpYXQiOjE3MjM0OTc0ODksImV4cCI6MTcyMzUwMTA4OX0.nnhAGjR_PbuHhLzVCDrTZmRRU9zQd_gpona8bSuvIWlK6Taaxgojn-8tQk1IDykw_WHdnLPgWrOt9uGPAPFD0SsLqK_P-aM1Q8KU-X-Ve3qMQ-Sru5IpE-BgCnb5n0_s2mR6-Ebl6exPYQWJFFNWQ_cj6lDF2fYS71hv2IeQqwssHU4YD1ujUl0rm1rCzRKGuK-iVr9mdFywB2K95iBnhJ0-XLnysjyM4UtutGqLqVZgIHabm2HhgFNNLfHtpfgNYtrk3l3lxCYsr9jmD5Z3lnLcWNhBDWxBlneyIp7yt73hLt7v5QKVoKzFgU_Ikf_FVx0TC7dmUvEKNhqfjfKNA

This will make the app act such that the user authentified is the one that corresponds to the jwt payload. Windmill uses just-in-time provisioning by default and the user doesn't need to have been pre-provisioned for it to works.