# Authentik Access Groups Manage group-based application access via the Authentik API. No web UI interaction required. **Authentik instance:** https://auth.echo6.co (Contabo, 100.64.0.1) **Key behavior:** Users in `authentik Admins` (is_superuser=true) bypass ALL policy checks automatically. Group bindings only restrict non-superuser access. --- ## How It Works By default, any authenticated Authentik user can access any application. Adding a **policy binding** that ties a **group** to an **application** restricts that app to group members only (plus superusers). - One binding per group-application pair - An app can have multiple group bindings (policy_engine_mode=`any` means membership in ANY bound group grants access) - Apps with zero bindings remain open to all authenticated users - Superusers always have access regardless of bindings --- ## Setup ```bash AK_TOKEN="$(grep 'AUTHENTIK_API_TOKEN=' /home/zvx/projects/.ref/credentials | tail -1 | sed 's/.*=//' | tr -d '"')" AK_API="https://auth.echo6.co/api/v3" ``` Verify: ```bash curl -s -H "Authorization: Bearer $AK_TOKEN" "$AK_API/core/groups/?page_size=1" | jq '.pagination.count' ``` Must return a number. If `403`, the token is invalid or expired. --- ## Procedure A: Create a New Access Group ### Inputs ``` GROUP_NAME= # lowercase, hyphenated (e.g., "finance-users", "dev-users") ``` Convention: `-users` (e.g., `media-users`, `cloud-users`, `security-users`). ### Create the group ```bash curl -s -X POST "$AK_API/core/groups/" \ -H "Authorization: Bearer $AK_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"name\": \"$GROUP_NAME\"}" | jq '{name: .name, pk: .pk}' ``` Store the returned `pk` as `GROUP_PK`. ### Gate Response must include a valid UUID `pk`. If it returns an error, the group name likely already exists. --- ## Procedure B: Bind a Group to an Application This restricts the application so only members of the bound group (and superusers) can access it. ### Inputs ``` APP_SLUG= # Application slug (e.g., "jellyfin", "nextcloud") GROUP_PK= # Group UUID from Procedure A or the reference table below ``` ### Look up the application PK ```bash APP_PK=$(curl -s -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/core/applications/?slug=$APP_SLUG&superuser_full_list=true" \ | jq -r '.results[0].pk') echo "App PK: $APP_PK" ``` Must return a UUID. Use `superuser_full_list=true` because apps that already have bindings won't appear without it. ### Check for existing bindings ```bash curl -s -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/policies/bindings/?target=$APP_PK" \ | jq '.results[] | {pk: .pk, group: .group_obj.name}' ``` Review output. If the desired group is already bound, skip creation. ### Create the binding ```bash curl -s -X POST "$AK_API/policies/bindings/" \ -H "Authorization: Bearer $AK_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"group\": \"$GROUP_PK\", \"target\": \"$APP_PK\", \"order\": 0, \"enabled\": true, \"negate\": false, \"timeout\": 30 }" | jq '{pk: .pk, group: .group_obj.name, target: .target}' ``` ### Gate Response must include a valid UUID `pk`. If it fails: - **"target" invalid** — the application PK is wrong - **"group" invalid** — the group PK is wrong --- ## Procedure C: Add a User to a Group ### Inputs ``` USERNAME= # Authentik username (e.g., "jodie") GROUP_PK= # Group UUID ``` ### Look up the user PK ```bash USER_PK=$(curl -s -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/core/users/?search=$USERNAME" \ | jq -r '.results[0].pk') echo "User PK: $USER_PK" ``` ### Get current group members ```bash CURRENT_USERS=$(curl -s -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/core/groups/$GROUP_PK/" \ | jq -r '[.users[]] | join(",")') echo "Current user PKs: $CURRENT_USERS" ``` ### Add user to group ```bash curl -s -X PATCH "$AK_API/core/groups/$GROUP_PK/" \ -H "Authorization: Bearer $AK_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"users\": [$CURRENT_USERS, $USER_PK]}" \ | jq '{name: .name, users: [.users_obj[].username]}' ``` ### Gate Response must list the user in `users`. The `users` field is a **replace** operation — always include existing user PKs to avoid removing them. --- ## Procedure D: Remove a User from a Group Same as Procedure C, but omit the user PK from the `users` array: ```bash # Get current members, filter out the target user NEW_USERS=$(curl -s -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/core/groups/$GROUP_PK/" \ | jq -r "[.users[] | select(. != $USER_PK)] | join(\",\")") curl -s -X PATCH "$AK_API/core/groups/$GROUP_PK/" \ -H "Authorization: Bearer $AK_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"users\": [$NEW_USERS]}" \ | jq '{name: .name, users: [.users_obj[].username]}' ``` --- ## Procedure E: Remove a Group Binding from an Application This re-opens the application to all authenticated users (if it was the only binding). ### Find the binding PK ```bash curl -s -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/policies/bindings/?target=$APP_PK" \ | jq '.results[] | {binding_pk: .pk, group: .group_obj.name}' ``` ### Delete the binding ```bash BINDING_PK= # From the output above curl -s -X DELETE -w "%{http_code}" \ -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/policies/bindings/$BINDING_PK/" ``` Must return `204`. --- ## Procedure F: Rename a Group ```bash OLD_GROUP_PK= # UUID of the group to rename NEW_NAME= # New name (e.g., "media-users") curl -s -X PATCH "$AK_API/core/groups/$OLD_GROUP_PK/" \ -H "Authorization: Bearer $AK_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"name\": \"$NEW_NAME\"}" \ | jq '{name: .name, pk: .pk}' ``` Renaming propagates to all existing bindings automatically — no need to recreate bindings. --- ## Verification ### List all groups and members ```bash curl -s -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/core/groups/?page_size=50" \ | jq '.results[] | {name: .name, pk: .pk, superuser: .is_superuser, users: [.users_obj[].username]}' ``` ### List all application bindings ```bash curl -s -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/core/applications/?superuser_full_list=true&page_size=50" \ | jq -r '.results[] | .slug' | while read slug; do echo "--- $slug ---" APP_PK=$(curl -s -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/core/applications/?slug=$slug&superuser_full_list=true" \ | jq -r '.results[0].pk') curl -s -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/policies/bindings/?target=$APP_PK" \ | jq -r 'if .results | length == 0 then " (open to all)" else .results[] | " \(.group_obj.name)" end' done ``` ### Check what a specific user can see ```bash # This shows apps visible to the API token owner without superuser bypass curl -s -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/core/applications/?superuser_full_list=false" \ | jq '[.results[].name]' ``` For a non-superuser, this returns only apps they have group access to plus unbound apps. --- ## Troubleshooting ### User gets "access denied" after binding was added 1. Verify the user is in the correct group: ```bash curl -s -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/core/groups/$GROUP_PK/" \ | jq '[.users_obj[].username]' ``` 2. Verify the binding exists and is enabled: ```bash curl -s -H "Authorization: Bearer $AK_TOKEN" \ "$AK_API/policies/bindings/?target=$APP_PK" \ | jq '.results[] | {group: .group_obj.name, enabled: .enabled, negate: .negate}' ``` 3. Check that `negate` is `false` — if `true`, the binding denies access instead of granting it. ### Superuser can't see all apps in the UI The Authentik user library page uses `superuser_full_list=false` by default. Superusers always have SSO access to all apps, but the library page only shows apps the user is explicitly authorized for. This is cosmetic — direct URL access still works. ### App disappeared from user's library after adding first binding Expected behavior. Before any bindings exist, the app is open to everyone. The moment you add the first group binding, only that group's members (and superusers) see it. Make sure all intended users are in the group before binding. --- ## Quick Reference: Current State ### Groups | Group | PK | Members | |-------|----|---------| | authentik Admins | `9944e153-f860-4443-81d1-ae544f611806` | akadmin, matt (superuser) | | media-users | `0820b2b8-6c54-4c20-9a0a-872820e6d9ea` | jodie | | communication-users | `31bce176-cd86-4aea-8db3-a57e03d5c2d1` | — | | security-users | `f345a043-c2a4-4906-a43b-9860eae86ee1` | — | | productivity-users | `698d80c7-7c29-43cd-b5d4-9eb24c85a6cc` | — | | cloud-users | `db3cbf5d-8057-4e33-8e8d-95bfdb35fbac` | — | | proxmox_admins | `d85a868d-7d1e-4585-92a8-b8bb86771b53` | akadmin, matt | | proxmox_users | `cf26703a-a824-47dd-9550-30b848a8ce5f` | — | ### Application Bindings | Application | Slug | Group | Binding PK | |-------------|------|-------|------------| | Jellyfin | jellyfin | media-users | `31515ffc-f937-442f-9813-263e68247687` | | Jellyseer | jellyseer | media-users | *(existing)* | | PeerTube | peertube | media-users | `c0f79fd3-9270-49f6-8457-42affc96c50a` | | Mailcow | mailcow | communication-users | `5a8f92de-81d5-4cf6-9273-7093de0f568d` | | Vaultwarden | vaultwarden | security-users | `a39e9d0c-237d-4a62-976e-b6c74ee31629` | | Forgejo | forgejo | productivity-users | `49953de3-af0b-4b1b-954d-70684d127445` | | Nextcloud | nextcloud | cloud-users | `6ac8ccfc-7ca3-4288-a281-b78a1c675e57` | | Immich | immich | cloud-users | `4fdf2887-e94f-4c24-81d2-d8cd4587ef38` | ### Unbound Applications (open to all authenticated users) | Application | Slug | Reason | |-------------|------|--------| | Headplane | headplane | Admin tool — superuser access only needed | | Headscale VPN | headscale | Admin tool — superuser access only needed | | Proxmox VE | proxmox | Admin tool — superuser access only needed | | WATCHTOWER | watchtower | Admin tool — superuser access only needed | --- *Last updated: 2026-02-14 — Initial creation with 5 access groups and 8 application bindings*