Skip to main content

Jenkins OIDC, Gitea OIDC and Grafana SSO with Casdoor: One Login for a Self-Hosted DevOps Stack

· 10 min read
Yang Luo
Casdoor Maintainer

Every tool in a typical self-hosted DevOps stack can act as an OpenID Connect client: Gitea and Argo CD speak OIDC natively, Jenkins does through the oic-auth plugin, Grafana and Portainer through generic OAuth 2.0, and MinIO, Harbor and GitLab through their OIDC settings. Point all of them at one Casdoor instance, give each tool its own Casdoor application, and put your people in Casdoor groups. Each tool then reads the groups claim and decides who is an administrator. This post lists the callback URL, the config keys and the group mapping for each tool, then covers the parts they share.

The stack at a glance​

All paths below are relative to the tool's own public URL. Register them in Redirect URLs of the Casdoor application for that tool.

AppProtocolCallback URLGroup/role to admin mappingCasdoor doc
GiteaOIDC (OAuth2 auth source)/user/oauth2/<source-name>/callbackYes: admin group, restricted group, team mapGitea
JenkinsOIDC (oic-auth plugin)/securityRealm/finishLogin, logout /OicLogoutYes: groups claim plus Matrix-based securityJenkins (OIDC)
GrafanaGeneric OAuth 2.0/login/generic_oauthYes: role_attribute_pathGrafana
Argo CDOIDC/auth/callback, CLI http://localhost:8085/auth/callbackYes: argocd-rbac-cmArgo CD
MinIOOIDCConsole /oauth_callbackPolicies from one claim (claim_name)MinIO
PortainerOAuth 2.0 (Custom)The Portainer base URLBusiness Edition onlyPortainer
HarborOIDC/c/oidc/callbackYes: OIDC Admin Groupnone yet
GitLabOIDC (OmniAuth)/users/auth/openid_connect/callbackPremium and Ultimate: admin_groupsGitLab

The discovery document every OIDC client needs is https://<casdoor-host>/.well-known/openid-configuration. If you have not connected an OIDC client to Casdoor before, Connect a standard OIDC client explains the endpoints.

Prepare Casdoor once​

Before touching the tools, set up three things in Casdoor.

Groups. Create groups such as devops-admins and devops-users in the organization tree and add users to them. See Groups.

Token group format. By default the groups claim names each group as <organization>/<group>. Most tools compare the claim with a plain string you type, so set Token group format on the OIDC/OAuth tab of each application to Name (group). The claim then carries devops-admins. The options are described under Group format.

Token format. Keep Token format at JWT, the default. In that format the ID token carries groups, roles and tag. JWT-Empty and JWT-Standard leave all three out of the token, and JWT-Custom carries them only if you select them in Token fields. The /api/userinfo response returns groups and the role names when the profile scope is granted, so tools that read userinfo still see them.

Roles work too. In the JWT format the roles claim is an array of role objects, so match on roles[*].name. In /api/userinfo it is a plain array of names. See User roles.

Gitea​

In Site Administration, add an authentication source of type OAuth2 with provider OpenID Connect, the client ID and secret, and the Casdoor discovery URL. The name you give the source is part of the callback URL: a source named casdoor uses https://gitea.example.com/user/oauth2/casdoor/callback.

For group mapping, fill in Claim name providing group names with groups, then Group Claim value for administrator users with devops-admins. Gitea also has Group Claim value for restricted users and Map claimed groups to Organization teams. The same source from the CLI:

gitea admin auth add-oauth \
--name casdoor \
--provider openidConnect \
--key <client-id> \
--secret <client-secret> \
--auto-discover-url https://door.example.com/.well-known/openid-configuration \
--group-claim-name groups \
--admin-group devops-admins

Jenkins​

Jenkins has no OIDC support of its own. Install the OpenId Connect Authentication plugin, choose it as the security realm, and use the well-known configuration. The login callback is /securityRealm/finishLogin and the logout callback is /OicLogout; add both to Casdoor. In JCasC:

jenkins:
securityRealm:
oic:
serverConfiguration:
wellKnown:
wellKnownOpenIDConfigurationUrl: https://door.example.com/.well-known/openid-configuration
clientId: <client-id>
clientSecret: <client-secret>
userNameField: name
fullNameFieldName: displayName
emailFieldName: email
groupsFieldName: groups
logoutFromOpenidProvider: true
properties:
- "pkce"

The plugin only passes the groups to Jenkins. Rights come from the authorization strategy: with Matrix-based security, each row of the table is a user or a group, so add a row for devops-admins and give it Overall/Administer. Choosing the plugin as the security realm turns off other login methods, so configure the plugin's escapeHatch property (a fallback username and secret) before you save. Casdoor also has its own Jenkins plugin, described in Jenkins plugin, but the generic OIDC plugin is the one this post assumes.

Grafana​

Grafana uses generic OAuth. Its callback is /login/generic_oauth. Grafana's default scopes is user:email, so set the OIDC scopes explicitly:

[auth.generic_oauth]
enabled = true
name = Casdoor
client_id = <client-id>
client_secret = <client-secret>
scopes = openid profile email
auth_url = https://door.example.com/login/oauth/authorize
token_url = https://door.example.com/api/login/oauth/access_token
api_url = https://door.example.com/api/userinfo
use_pkce = true
groups_attribute_path = groups
role_attribute_path = contains(groups[*], 'devops-admins') && 'Admin' || contains(groups[*], 'devops-users') && 'Editor' || 'Viewer'
role_attribute_strict = true

Grafana evaluates role_attribute_path against the ID token first, then the userinfo response, then the access token. Valid results are None, Viewer, Editor, Admin and GrafanaAdmin; the last one needs allow_assign_grafana_admin = true. allowed_groups limits login to listed groups and requires groups_attribute_path. Team sync is a Grafana Enterprise and Grafana Cloud feature.

Argo CD​

Argo CD talks to Casdoor directly, without its bundled Dex. The browser callback is /auth/callback. argocd login --sso starts a local server on port 8085 by default and uses http://localhost:8085/auth/callback, so register that as well.

Set url and oidc.config (with name, issuer, clientID, clientSecret, requestedScopes) in argocd-cm. Argo CD's default requestedScopes adds a groups scope. Casdoor does not need it, because the JWT ID token always carries groups, so our guide requests ["openid", "profile", "email"]. Groups are mapped in argocd-rbac-cm, where scopes defaults to [groups]:

data:
policy.default: role:readonly
policy.csv: |
g, devops-admins, role:admin

The full walkthrough, including the client secret in argocd-secret, is in the Argo CD guide.

MinIO​

MinIO reads policies from a single claim, set with claim_name (default policy). It does not map groups. MinIO's own Casdoor quickstart and our guide use Casdoor's tag claim: put a MinIO policy name such as readwrite in the user's Tag, then:

mc admin config set myminio identity_openid \
config_url="https://door.example.com/.well-known/openid-configuration" \
client_id=<client-id> client_secret=<client-secret> claim_name="tag"

role_policy is the alternative: every user who signs in through this provider gets the listed policies, and it cannot be combined with claim_name. The Console callback is <console-url>/oauth_callback. Behind a proxy, set MINIO_BROWSER_REDIRECT_URL; the old redirect_uri key is deprecated. Note that the minio/minio GitHub repository has been archived and its README says it is no longer maintained, so check which MinIO build you run before relying on these settings.

Portainer​

Portainer uses a Custom OAuth provider. Fill in Authorization URL (/login/oauth/authorize), Access token URL (/api/login/oauth/access_token) and Resource URL (/api/userinfo) on your Casdoor host, and set Redirect URL to the Portainer base URL. There is also a Logout URL field. In Portainer CE, Automatic team membership is marked as a Business Edition feature, and the setting that assigns admin rights to groups is only in Business Edition. On CE, users created by automatic provisioning get the standard user role and you promote admins by hand.

Harbor and GitLab​

Harbor shows its redirect URI at the bottom of the OIDC settings page: <harbor-url>/c/oidc/callback. Set OIDC Provider Endpoint to the Casdoor URL, Group Claim Name to groups, and OIDC Admin Group to devops-admins. Harbor reads the group claim from the ID token, which the JWT format provides. You can only switch Harbor from database to OIDC authentication before any local users exist.

GitLab's OIDC docs have a Casdoor section and require Casdoor to be served over HTTPS. The callback is /users/auth/openid_connect/callback. Group-based roles (groups_attribute, admin_groups, required_groups, external_groups) need GitLab Premium or Ultimate.

Tools without OIDC​

For internal dashboards with no login of their own, put them behind Apache APISIX's openid-connect plugin (discovery, client_id, client_secret, redirect_uri, bearer_only), as in the APISIX guide, or behind nginx as described in Nginx SSO authentication.

One application per tool or a shared one​

A single Casdoor application with every tool's callback in its Redirect URLs works, but one application per tool is easier to run:

  • Each tool gets its own client secret, so a leaked Jenkins secret does not expose Grafana.
  • Token group format, Token format and token expiry are per application, so you can give Argo CD Name (group) without changing another tool's claims.
  • Casdoor checks post_logout_redirect_uri against the Redirect URLs of the application, so each list stays short and specific.

Users still sign in once: after the first login, Casdoor's session cookie lets the next tool's redirect complete without a password prompt.

Logout​

Casdoor's end-session endpoint is /api/logout, listed as end_session_endpoint in the discovery document. It accepts id_token_hint, post_logout_redirect_uri, client_id and state, and only redirects to a URL listed in the application's Redirect URLs. Signing out of Casdoor ends the Casdoor session and the tokens of that session, but each tool also keeps its own session cookie. Wire up logout in each tool:

  • Jenkins: logoutFromOpenidProvider, with /OicLogout registered.
  • Grafana: signout_redirect_url.
  • Argo CD: logoutURL in oidc.config, for example https://door.example.com/api/logout?id_token_hint={{token}}&post_logout_redirect_uri={{logoutRedirectURL}}.
  • Harbor: OIDC Session Logout.
  • Portainer: Logout URL.

The details of the endpoint are in RP-initiated logout.

References​