weebo-forgejo

OIDC groups and teams

Let your identity provider's groups decide who is in which Forgejo team, with a naming convention set up once.

Rather than listing members in each ForgejoTeam, you can let your identity provider decide: whoever is in the group forgejo/weebo/devs is in the team devs of the organization weebo. Three parties share the work, and none does the others' job:

WhoDoes what
The identity provider (Authentik here)Holds the groups and who is in them.
Forgejo's authentication sourceAt each sign-in, reads the user's groups and adds or removes them from the matching teams.
The operatorCreates the organization and the team, sets their rights and repositories. Never touches the members (membersPolicy: External).
How an OIDC group fills a Forgejo teamAlice is in the Authentik group forgejo/weebo/devs. When she signs in to Forgejo, the ID token carries that group in its groups claim. The authentication source matches it against the pattern forgejo/org/team and adds her to the team devs of the organization weebo. In Kubernetes, weebo-authentik declares the group and weebo-forgejo declares the team with membersPolicy External; neither writes the membership.PROVIDERSIGN-INAUTH SOURCETEAMAuthentik groupforgejo/weebo/devsmembersaliceID token of aliceclaim groupscontainsforgejo/weebo/devsdynGroupMapsforgejo/{org}/{team}matchesorg weebo, team devsForgejo teamweebo / devsmembersaliceKUBERNETESweebo-authentikAuthentikGroup+ AuthentikUser.groupRefsweebo-forgejoForgejoTeam devsmembersPolicy: Externalcreates the team, its rights and repositories;never adds or removes its members
At each sign-in, Forgejo matches the groups claim against the pattern and adds or removes the user. The operators declare the group and the team, never the membership.

The mapping is a naming convention, configured once on the authentication source: forgejo/{org}/{team}. A new team needs a ForgejoTeam and a group at the provider, never a change to Forgejo's configuration or a restart.

Before you begin

  • Forgejo deployed with the Forgejo Helm chart, and an OAuth2/OIDC provider for it at your identity provider. Its redirect URI is https://<forgejo>/user/oauth2/<source name>/callback.
  • The operator connected to that Forgejo (see Connect an instance).
  • kubectl exec access to the Forgejo pod, once, for step 2.

1. Configure the authentication source

In the Forgejo chart's values:

gitea:
  oauth:
    - name: authentik                    # the source name, part of the redirect URI
      provider: openidConnect
      existingSecret: forgejo-oidc       # keys: key (client id), secret (client secret)
      autoDiscoverUrl: https://auth.example.com/application/o/forgejo/.well-known/openid-configuration
      scopes: openid,email,profile       # comma-separated
      groupClaimName: groups
      dynGroupMaps: '["forgejo/{org}/{team}"]'
  • scopes must include profile. Without scopes, Forgejo only asks for openid, and Authentik puts the groups claim in the profile scope. Separate scopes with commas: openid email profile would be stored as a single scope.
  • dynGroupMaps is a JSON list of patterns. {org} and {team} capture letters, digits, -, _ and ., and matching ignores case: Forgejo/Weebo/Site-Admins maps to the team site-admins of weebo. Pick a separator that can't appear in a name, like /.

The chart applies these values at every start of the pod, and restarts it when they change.

Don't put dynGroupMapsRemoval in the chart values

The chart passes it as --dyn-group-maps-removal "true", which Forgejo's CLI rejects (unexpected arguments: true) without creating the source. The chart's init container then fails, and Forgejo doesn't start. Turn it on once by hand instead (next step).

2. Turn on removal (once)

Without removal, a user who leaves a group stays in the team forever. Turn it on directly on the source:

# The source id, in the first column
kubectl -n forgejo exec deploy/forgejo -c forgejo -- forgejo admin auth list

kubectl -n forgejo exec deploy/forgejo -c forgejo -- \
  forgejo admin auth update-oauth --id 1 --dyn-group-maps-removal

The setting is stored in Forgejo's database. The chart only updates the options it passes, so its next runs leave it on. It takes effect at the next sign-in, with no restart.

Removal applies to every team

With removal on, each sign-in takes the user out of every team that no group maps them to, in every organization. That includes the Owners teams and teams with membersPolicy: Managed. See the rules.

3. Declare the team

apiVersion: forgejo.weebo.io/v1alpha1
kind: ForgejoTeam
metadata:
  name: weebo-devs
spec:
  organizationRef: { name: weebo }
  name: devs                     # the {team} part of the group name
  permission: write
  includesAllRepositories: true  # or list the team in each repository's teamRefs
  membersPolicy: External        # members come from the groups

memberRefs must stay empty: with External the CR reports InvalidSpec otherwise. Rights and repositories work as for any team (see Team access levels).

The organization and the team must exist before anyone signs in with the group. Forgejo never creates them; it skips a group whose team is missing and logs group sync: Could not find team.

4. Create the group at the provider

The group name is the pattern with the Forgejo names filled in: forgejo/weebo/devs. With weebo-authentik:

apiVersion: authentik.weebo.io/v1alpha1
kind: AuthentikGroup
metadata:
  name: forgejo-weebo-devs
spec:
  name: forgejo/weebo/devs
---
apiVersion: authentik.weebo.io/v1alpha1
kind: AuthentikUser
metadata:
  name: alice
spec:
  username: alice
  name: Alice
  email: alice@example.com
  groupRefs:
    - forgejo/weebo/devs         # the Authentik group name, not the CR name

Or create the group in Authentik's admin interface.

5. Check it

Have the user sign in to Forgejo through the provider, then look at the team's members (Organization → Teams → devs in Forgejo). They're added at this sign-in, and the operator leaves them alone afterwards.

When membership changes

Forgejo syncs the groups only when the user signs in through the provider on the web. Nothing happens in between:

  • Added to a group: they join the team at their next sign-in.
  • Removed from a group: they leave the team at their next sign-in. Until then they keep the team's access, including through their API tokens and SSH keys, which never go through the provider. To cut access right away, also remove them from the team in Forgejo (the operator won't put them back) or block the account.

Rules for mixing with other teams

Because removal sweeps every team not mapped by a group, follow these rules:

TeamIts members
Any team an OIDC user should be inmembersPolicy: External, filled by a forgejo/{org}/{team} group.
An organization's OwnersA group too: forgejo/weebo/owners. Otherwise owners lose the team at their next sign-in.
membersPolicy: ManagedOnly accounts that never sign in through the provider: bots, local accounts.

Bots and local accounts are safe in any team: the sync only runs for the user who is signing in through the provider.

Alternative: one entry per group

If you can't rename your groups, map them explicitly instead:

gitea:
  oauth:
    - name: authentik
      # ...as above, without dynGroupMaps
      groupClaimName: groups
      groupTeamMap: '{"developers": {"weebo": ["devs"]}, "readers": {"weebo": ["ro"]}}'
      groupTeamMapRemoval: true

Here the chart handles groupTeamMapRemoval correctly, and removal is narrower: it only takes users out of the teams listed in the map, so Managed teams and Owners are left alone. The cost is that every new team means editing the chart values, and the chart restarts Forgejo.

Troubleshooting

SymptomCause
Nobody is ever addedThe token has no groups claim: check that scopes includes profile. Scopes are read when Forgejo starts, so restart it after changing them by hand.
One group is ignoredIts team or organization doesn't exist yet (group sync: Could not find team in Forgejo's log), or the name has a character outside letters, digits, -, _, ..
Users lose a team the operator gave themRemoval is on and the team is Managed or Owners: see the rules.
Someone removed from the group still has accessExpected until their next sign-in: see When membership changes.
Forgejo's pod doesn't start after a chart changedynGroupMapsRemoval is in the values: remove it and use step 2.
The ForgejoTeam reports InvalidSpecmemberRefs is set together with membersPolicy: External.

On this page