GitLab
Let GitLab authenticate your users, then require membership in a selected group
to reach your application. This guide works with GitLab.com or a GitLab instance
you administer. It uses the named gitlab driver and its UserInfo group filter.
The example targets caddy-security v1.3.0, which bundles go-authcrunch v1.3.8. Configuration and local provider fixtures verify the released driver's behavior; your GitLab registration and consent flow still need an end-to-end check in your environment.
Before you start
Complete Install and verify. Choose a public hostname
for AuthCrunch, with ports 80 and 443 available for automatic HTTPS. The example
uses auth.example.com; replace both occurrences in the Caddyfile and the
callback below.
Have permission to register a GitLab OAuth application and select a group whose
members should reach the protected app. This guide uses the full group path
example-team/app-members. Use an existing path or create a group for your app.
Plan a member and a nonmember account for testing.
Register the application
In GitLab, open your profile's Access → Applications → Add new application,
or visit https://gitlab.com/-/profile/applications. For Self-Managed, use your
GitLab hostname instead. Group-owned applications are another option; see
GitLab's application registration instructions.
| Setting | Value for this walkthrough |
|---|---|
| Name | A recognizable name, such as Example team portal |
| Redirect URI | https://auth.example.com/auth/oauth2/gitlab/authorization-code-callback |
| Confidential | Enabled; the portal keeps the secret on the server |
| Scopes | openid, email, profile |
Save the Application ID and Secret for the server environment. This
login uses an OAuth application secret, not a personal access token. Repository
write access and the broad api scope are unnecessary for this walkthrough.


Configure AuthCrunch
Save this complete configuration as Caddyfile. It is embedded from the
canonical GitLab example.
# Set the public portal hostname and GITLAB_DOMAIN before use.
{
admin off
persist_config off
security {
oauth identity provider gitlab {
driver gitlab
realm gitlab
client_id {env.GITLAB_CLIENT_ID}
client_secret {env.GITLAB_CLIENT_SECRET}
domain_name {$GITLAB_DOMAIN}
scopes openid email profile
user_group_filters ^example-team/app-members$
login icon text "GitLab"
}
authentication portal myportal {
enable identity provider gitlab
crypto default token lifetime 900
crypto key sign-verify {env.JWT_SHARED_KEY}
cookie path /
ui {
links {
"Example app" /app icon "las la-external-link-alt"
"My identity" /auth/whoami icon "las la-user"
}
}
transform user {
match realm gitlab
action add role authp/user
}
transform user {
match realm gitlab
match role {$GITLAB_DOMAIN}/example-team/app-members
action add role app/member
}
}
authorization policy apppolicy {
set auth url https://auth.example.com/auth/
crypto key verify {env.JWT_SHARED_KEY}
allow roles app/member
}
}
}
auth.example.com {
route {
redir /auth /auth/ 308
authenticate /auth/* with myportal
@app path /app /app/*
route @app {
authorize with apppolicy
respond "You reached the GitLab-protected app." 200
}
respond "AuthCrunch is running. Open /app to begin." 200
}
}
The first transform grants ordinary portal access. The second grants
app/member only when the filtered GitLab group is present. The policy requires
that application role for /app and every path underneath it. Replace the
protected respond with reverse_proxy when connecting your own application.
Set the environment
export GITLAB_DOMAIN='gitlab.com'
export GITLAB_CLIENT_ID='YOUR_APPLICATION_ID'
export GITLAB_CLIENT_SECRET='YOUR_APPLICATION_SECRET'
export JWT_SHARED_KEY="$(openssl rand -hex 32)"
For Self-Managed, set GITLAB_DOMAIN to your GitLab hostname, such as
gitlab.example.com, without https:// or a trailing slash. The provider
constructs its HTTPS discovery URL from that hostname. The AuthCrunch server
must reach it and trust its TLS certificate.
{$GITLAB_DOMAIN} expands when Caddy parses the configuration, including the
group-role prefix. The credentials and shared signing key use runtime
{env.VARIABLE} placeholders. Keep those secrets in the protected environment
of the process or service, and reuse the signing key across restarts.
Replace example-team/app-members in both the group filter and role matcher
with your group's full path. Escape regex metacharacters in the filter if your
path contains them. Retain the ^ and $ anchors for an exact path match.
Check and run
Using your installed binary:
./bin/authcrunch adapt --adapter caddyfile --config Caddyfile >/dev/null
./bin/authcrunch run --config Caddyfile
Adaptation checks syntax, not your credentials or membership. The foreground process starts the portal and provisions HTTPS. This example disables the admin endpoint; stop it with Ctrl+C and restart after configuration changes.
Understand the group filter
The gitlab driver exchanges the authorization code, then calls the discovered
UserInfo endpoint with the access token. It takes group paths from that
response's groups array. GitLab documents this array as including direct
membership and membership through an ancestor group. It differs from the
ID token's groups_direct claim. See
GitLab's OIDC claims.
The path passes through two checks:
| Stage | Example |
|---|---|
| GitLab UserInfo group | example-team/app-members |
| Filter applied to the unprefixed path | ^example-team/app-members$ |
| Role exposed to the portal | gitlab.com/example-team/app-members |
| Transform grants application access | app/member |
With no user_group_filters, the named driver omits GitLab groups. The filter
selects claims to retain; it does not itself deny sign-in. The application policy
is the final access check. A subgroup such as example-team/app-members/child
does not match this example's anchored filter.
For multiple accepted paths, put the regexes on one directive:
user_group_filters ^example-team/app-members$ ^example-team/app-operators$
Then add the corresponding role-granting transform for each path you intend to
authorize. Multiple role-granting transforms are additive. Avoid broad filters
such as .* unless every returned group is intended to become a portal role.
Identity fields
The named driver uses the UserInfo profile URL as the AuthCrunch sub,
not GitLab's numeric OIDC subject. A renamed GitLab username can change that
profile URL. This walkthrough authorizes by the selected group, not username
or email.
GitLab may omit email depending on the requested scope and the user's public
email setting. The named GitLab path can still sign in without it; the group
policy continues to apply. The generic driver's default ID-token email check
is a different behavior. An ID-token groups_direct claim, or a group present
only in the ID token, does not supply this named driver's group roles.
Verify login and denial
- Open
https://auth.example.com/appin a fresh browser session. - Choose GitLab, sign in, and authorize the registered application.
- Open Example app from the portal. A group member should see
You reached the GitLab-protected app. - Open My identity at
/auth/whoami. Checkrealm: gitlab, the prefixed group role, andapp/member. - In a separate session, sign in as a nonmember. Portal login can succeed, but
/appmust return 403 without the protected response.
Screenshots: GitLab consent and the earlier portal
These captures retain the earlier portal branding and example identities. In the current walkthrough, use Example app and My identity, and expect the selected group plus app/member rather than the historical administrator role.



Use a fresh login after changing group membership, filters, or transforms. Existing AuthCrunch tokens retain their recorded roles until they expire; this example uses a 900-second lifetime. Portal logout clears the portal session, while an existing GitLab browser session can still make the next login seamless.
Troubleshoot
| Symptom | Check |
|---|---|
| Provider fails to initialize | Hostname, discovery response, network access, and TLS trust for the GitLab instance |
| Callback rejected | Exact HTTPS host and /auth/oauth2/gitlab/authorization-code-callback path in the OAuth application |
Login succeeds but /app is 403 | Full group path, anchored filter, hostname prefix, and the user's actual UserInfo membership |
| Group appears in an ID token but not My identity | This driver consumes UserInfo groups, not ID-token groups_direct |
| Login fails while fetching claims | UserInfo must succeed and contain a profile field; a failed response is not a successful login |
| Access persists after removing membership | Test a new portal session; membership changes do not rewrite already-issued tokens |
For explicit issuer/discovery configuration and ID-token claim handling, see the generic OIDC reference. Switching drivers changes the subject and group source; recheck your transforms before migrating.