infra/.claude/skills/archived/authentik/SKILL.md
Viktor Barzin fd0f4a0365 fix: restore tree dropped by 6d224861; land stem95su gdrive-sync (10m) [ci skip]
6d224861 came from a --no-checkout worktree whose empty index made the
commit drop every file except two. This restores 05b50d2b's full tree and
correctly adds stacks/stem95su/gdrive-sync.tf + the service-catalog stem95su
entry. Forward-only (parent=6d224861, no force-push); [ci skip] since the
live infra was never applied from the broken commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 08:45:33 +00:00

10 KiB

name description author version date
authentik Manage the Authentik identity provider via its REST API. Use when: (1) User asks to create, update, or delete users in Authentik, (2) User asks to manage groups or group memberships, (3) User asks to create a new OAuth2/OIDC application or provider, (4) User asks to protect a service with forward auth (Authentik + Traefik), (5) User asks about SSO, single sign-on, authentication, or identity, (6) User asks to manage Authentik flows, stages, or policies, (7) User asks to configure social login (Google, GitHub, Facebook), (8) User asks about OIDC for Kubernetes or who has access to what, (9) User deploys a new service that needs authentication. Authentik v2025.10.3 running in Kubernetes, managed via REST API. Claude Code 1.0.0 2026-02-17

Authentik Identity Provider Management

Overview

  • URL: https://authentik.viktorbarzin.me
  • Admin UI: https://authentik.viktorbarzin.me/if/admin/
  • API Base: https://authentik.viktorbarzin.me/api/v3/
  • API Docs: https://authentik.viktorbarzin.me/api/v3/docs/
  • Helm Chart: authentik v2025.10.3
  • Namespace: authentik

API Access

Getting the Token

The API token is stored in terraform.tfvars (git-crypt encrypted):

AUTHENTIK_TOKEN=$(grep authentik_api_token terraform.tfvars | cut -d'"' -f2)

Making API Calls

# Generic pattern
curl -s -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  "https://authentik.viktorbarzin.me/api/v3/<endpoint>/"

# With JSON body (POST/PATCH/PUT)
curl -s -X POST \
  -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  -H "Content-Type: application/json" \
  "https://authentik.viktorbarzin.me/api/v3/<endpoint>/" \
  -d '{"key": "value"}'

Verify Token Works

curl -s -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  "https://authentik.viktorbarzin.me/api/v3/core/users/me/" | python3 -m json.tool

Key API Endpoints

Endpoint Methods Purpose
core/users/ GET, POST List/create users
core/users/{id}/ GET, PATCH, DELETE Get/update/delete user
core/groups/ GET, POST List/create groups
core/groups/{pk}/ GET, PATCH, DELETE Get/update/delete group
core/applications/ GET, POST List/create applications
core/tokens/ GET, POST List/create tokens
core/tokens/{identifier}/view_key/ GET View token secret key
providers/all/ GET List all providers
providers/oauth2/ GET, POST OAuth2/OIDC providers
providers/proxy/ GET, POST Proxy providers (forward auth)
flows/instances/ GET List flows
stages/all/ GET List stages
sources/all/ GET List sources (social login)
outposts/instances/ GET List outposts
propertymappings/provider/scope/ GET, POST OIDC scope mappings
rbac/roles/ GET List roles

Common Operations

List All Users

curl -s -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  "https://authentik.viktorbarzin.me/api/v3/core/users/?page_size=50" | \
  python3 -c "
import json,sys
for u in json.load(sys.stdin)['results']:
    groups=[g['name'] for g in u.get('groups_obj',[])]
    print(f\"  {u['username']:<40} {u['name']:<30} groups={groups}\")
"

Create a New User

curl -s -X POST \
  -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  -H "Content-Type: application/json" \
  "https://authentik.viktorbarzin.me/api/v3/core/users/" \
  -d '{
    "username": "user@example.com",
    "name": "Full Name",
    "email": "user@example.com",
    "is_active": true,
    "type": "internal",
    "path": "users"
  }'

Add User to Group

# First get the group to find current users
GROUP_PK="<group-uuid>"
CURRENT_USERS=$(curl -s -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  "https://authentik.viktorbarzin.me/api/v3/core/groups/$GROUP_PK/" | \
  python3 -c "import json,sys; print(json.load(sys.stdin)['users'])")

# Then PATCH with the updated user list (add new user pk)
curl -s -X PATCH \
  -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  -H "Content-Type: application/json" \
  "https://authentik.viktorbarzin.me/api/v3/core/groups/$GROUP_PK/" \
  -d '{"users": [<existing_pks>, <new_pk>]}'

Create a New Group

curl -s -X POST \
  -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  -H "Content-Type: application/json" \
  "https://authentik.viktorbarzin.me/api/v3/core/groups/" \
  -d '{
    "name": "My New Group",
    "is_superuser": false,
    "parent": "<parent-group-pk-or-null>"
  }'

Create OAuth2/OIDC Application (Full Flow)

Step 1: Create the OAuth2 Provider

curl -s -X POST \
  -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  -H "Content-Type: application/json" \
  "https://authentik.viktorbarzin.me/api/v3/providers/oauth2/" \
  -d '{
    "name": "Provider for myapp",
    "authorization_flow": "<flow-pk>",
    "invalidation_flow": "<invalidation-flow-pk>",
    "client_type": "confidential",
    "client_id": "<generated-or-custom>",
    "client_secret": "<generated-or-custom>",
    "redirect_uris": "https://myapp.viktorbarzin.me/callback",
    "property_mappings": ["<scope-mapping-pks>"],
    "signing_key": "<signing-key-pk>"
  }'

Step 2: Create the Application

curl -s -X POST \
  -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  -H "Content-Type: application/json" \
  "https://authentik.viktorbarzin.me/api/v3/core/applications/" \
  -d '{
    "name": "My App",
    "slug": "myapp",
    "provider": <provider-pk-from-step-1>,
    "meta_launch_url": "https://myapp.viktorbarzin.me"
  }'

List Applications

curl -s -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  "https://authentik.viktorbarzin.me/api/v3/core/applications/?page_size=50" | \
  python3 -c "
import json,sys
for a in json.load(sys.stdin)['results']:
    ptype = a.get('provider_obj',{}).get('verbose_name','N/A')
    print(f\"  {a['name']:<30} slug={a['slug']:<25} provider={ptype}\")
"

Create a Non-Expiring API Token

# Create token
curl -s -X POST \
  -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  -H "Content-Type: application/json" \
  "https://authentik.viktorbarzin.me/api/v3/core/tokens/" \
  -d '{
    "identifier": "my-token-name",
    "intent": "api",
    "expiring": false,
    "description": "Description here"
  }'

# Retrieve the key
curl -s -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  "https://authentik.viktorbarzin.me/api/v3/core/tokens/my-token-name/view_key/"

Important Reference UUIDs

Authorization Flows

Flow Slug Use For
Authorize Application (explicit consent) default-provider-authorization-explicit-consent Apps that should show consent screen
Authorize Application (implicit consent) default-provider-authorization-implicit-consent Internal/trusted apps, auto-redirect
Logout default-invalidation-flow Invalidation/logout flow

Common Property Mappings (OIDC Scopes)

These are the standard scope mappings used by most providers:

  • 60e33a8c-66a2-414f-840c-b13012b4d4bd — openid
  • 1f51c659-f13b-4ad4-ba89-70458ef88e9c — email
  • 4c0bf430-7f74-4216-b9d7-23703ab544ba — profile

Login Sources

Source Slug Matching Mode
Google google identifier
GitHub github email_link
Facebook facebook email_link

Protecting a Service with Forward Auth

To protect a service via Authentik + Traefik forward auth:

  1. In the service's Terraform module, set protected = true in the ingress_factory call
  2. This adds the authentik-forward-auth Traefik middleware
  3. Unauthenticated users get redirected to the Authentik login page
  4. After login, these headers are forwarded to the service:
    • X-authentik-username
    • X-authentik-uid
    • X-authentik-email
    • X-authentik-name
    • X-authentik-groups

Invitation Management

Create Invitation

curl -s -X POST \
  -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  -H "Content-Type: application/json" \
  "https://authentik.viktorbarzin.me/api/v3/stages/invitation/invitations/" \
  -d '{
    "name": "invite-slug-name",
    "single_use": true,
    "fixed_data": {"group": "Target Group Name"},
    "flow": "<invitation-enrollment-flow-pk>"
  }'
# Returns PK which is the itoken
# Link: https://authentik.viktorbarzin.me/if/flow/invitation-enrollment/?itoken=<pk>

List Invitations

curl -s -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  "https://authentik.viktorbarzin.me/api/v3/stages/invitation/invitations/?page_size=50"

Delete Invitation

curl -s -X DELETE -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
  "https://authentik.viktorbarzin.me/api/v3/stages/invitation/invitations/<pk>/"

Helper Script

Use .claude/scripts/authentik-invite.sh for invitation management:

./authentik-invite.sh create "Group Name" [--days N]
./authentik-invite.sh assign <username> "Group Name"
./authentik-invite.sh list

Important Notes

  • OAuth source enrollment_flow is set to invitation-enrollment -- new social login users require invitation
  • Source updates require Django ORM (PATCH not supported on sources/oauth/<slug>/)
  • Invitation name field must be a slug (letters, numbers, hyphens, underscores)

Gotchas

  1. API pagination: All list endpoints return paginated results. Use ?page_size=50 or check pagination.next for more pages.
  2. Group user updates: PATCH to groups replaces the entire user list — always fetch current users first, then append.
  3. Provider property mappings: Must reference existing scope mapping UUIDs. Query propertymappings/provider/scope/ to find them.
  4. Signing key for OIDC: Must assign a signing key to OAuth2 providers or JWKS endpoint returns empty {}.
  5. Email verified claim: Default email scope mapping sets email_verified: False. For Kubernetes OIDC, create a custom mapping that returns True.
  6. Token identifier uniqueness: Token identifiers must be unique across the entire instance.

Notes

  • Authentik is classified as DEFCON Level 1 (Critical) — handle with care
  • Changes to Authentik configuration (Helm chart, PgBouncer, etc.) must go through Terraform
  • API-level changes (users, groups, applications) are fine to make directly via the API
  • The embedded outpost auto-discovers providers assigned to it
  • See also: ingress-factory-migration skill for protecting services