Two homelab services currently do their own Google OAuth: tapir (YouTube, internal/auth/auth.go) and the planned email-triage-agent (Gmail). Each holds its own refresh tokens in SOPS and runs its own authorization code flow. As more Google-connected services are added, this pattern creates:
One OAuth consent per service per account (user has to authorize each service separately)
N separate SOPS secrets for N services × M accounts
No central revocation — revoking access requires finding and deleting all service-specific tokens
No user-facing view of "what has access to my Google account"
Proposed architecture
Use Authentik as the Google OAuth broker. One Google Social OAuth2 source in Authentik covers all services.
User → Authentik login (Google social source, one-time consent)
↓
Authentik holds Google refresh token per user
↓
tapir / email-triage-agent / future services
→ request token from Authentik for this user + required scopes
→ never hold their own Google credentials
Benefits:
One browser consent per user, ever (all scopes requested upfront)
Central revocation in Authentik UI
New services request tokens without new OAuth flows
Consistent with existing Authentik OIDC pattern (tapir-web, cobalt-dingo, claude-ai MCP)
Scope gate — do NOT implement now
This is an ADR, not an active work item. Build email-triage-agent with per-service OAuth first (same as tapir today) to ship value fast. Migrate both to Authentik-centralized tokens as a follow-up once:
email-triage-agent Phase 1 is live and proven
A third Google-connected service is being planned
The migration then covers tapir + email-triage-agent + the new service in one pass, paying the migration cost once.
What the migration would involve
Configure Authentik Google Social OAuth2 source with all required scopes:
youtube.readonly, youtube.force-ssl (tapir)
gmail.readonly, gmail.modify (email-triage-agent)
Any future scopes
Build a token-forwarding mechanism: services call an Authentik endpoint to get a short-lived access token for a given user. Authentik handles refresh internally.
Update tapir and email-triage-agent to fetch tokens from Authentik instead of SOPS.
Risk
Deferred — not yet assessed. Assess at migration time. Key risk: Authentik token forwarding requires a custom Property Mapping or a small proxy service — not out-of-the-box. Prototype before committing.
Decision
Deferred. Build per-service OAuth now. Migrate to Authentik-centralized when the third Google service materializes.
Related
homelab#3 — ntfy fix
homelab#4 — email-triage-agent
mathias/tapir internal/auth/auth.go — current per-service OAuth pattern
ADR-0001 — Authentik migration from Dex (precedent for IdP centralization)
## Context
Two homelab services currently do their own Google OAuth: tapir (YouTube, `internal/auth/auth.go`) and the planned email-triage-agent (Gmail). Each holds its own refresh tokens in SOPS and runs its own authorization code flow. As more Google-connected services are added, this pattern creates:
- One OAuth consent per service per account (user has to authorize each service separately)
- N separate SOPS secrets for N services × M accounts
- No central revocation — revoking access requires finding and deleting all service-specific tokens
- No user-facing view of "what has access to my Google account"
## Proposed architecture
Use Authentik as the Google OAuth broker. One Google Social OAuth2 source in Authentik covers all services.
```
User → Authentik login (Google social source, one-time consent)
↓
Authentik holds Google refresh token per user
↓
tapir / email-triage-agent / future services
→ request token from Authentik for this user + required scopes
→ never hold their own Google credentials
```
Benefits:
- One browser consent per user, ever (all scopes requested upfront)
- Central revocation in Authentik UI
- New services request tokens without new OAuth flows
- Consistent with existing Authentik OIDC pattern (tapir-web, cobalt-dingo, claude-ai MCP)
## Scope gate — do NOT implement now
This is an ADR, not an active work item. Build email-triage-agent with per-service OAuth first (same as tapir today) to ship value fast. Migrate both to Authentik-centralized tokens as a follow-up once:
1. email-triage-agent Phase 1 is live and proven
2. A third Google-connected service is being planned
The migration then covers tapir + email-triage-agent + the new service in one pass, paying the migration cost once.
## What the migration would involve
1. Configure Authentik Google Social OAuth2 source with all required scopes:
- `youtube.readonly`, `youtube.force-ssl` (tapir)
- `gmail.readonly`, `gmail.modify` (email-triage-agent)
- Any future scopes
2. Build a token-forwarding mechanism: services call an Authentik endpoint to get a short-lived access token for a given user. Authentik handles refresh internally.
3. Remove per-service SOPS secrets and `tapir auth` / `email-triage-agent auth` CLI subcommands.
4. Update tapir and email-triage-agent to fetch tokens from Authentik instead of SOPS.
## Risk
**Deferred — not yet assessed.** Assess at migration time. Key risk: Authentik token forwarding requires a custom Property Mapping or a small proxy service — not out-of-the-box. Prototype before committing.
## Decision
**Deferred.** Build per-service OAuth now. Migrate to Authentik-centralized when the third Google service materializes.
## Related
- homelab#3 — ntfy fix
- homelab#4 — email-triage-agent
- `mathias/tapir internal/auth/auth.go` — current per-service OAuth pattern
- ADR-0001 — Authentik migration from Dex (precedent for IdP centralization)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Context
Two homelab services currently do their own Google OAuth: tapir (YouTube,
internal/auth/auth.go) and the planned email-triage-agent (Gmail). Each holds its own refresh tokens in SOPS and runs its own authorization code flow. As more Google-connected services are added, this pattern creates:Proposed architecture
Use Authentik as the Google OAuth broker. One Google Social OAuth2 source in Authentik covers all services.
Benefits:
Scope gate — do NOT implement now
This is an ADR, not an active work item. Build email-triage-agent with per-service OAuth first (same as tapir today) to ship value fast. Migrate both to Authentik-centralized tokens as a follow-up once:
The migration then covers tapir + email-triage-agent + the new service in one pass, paying the migration cost once.
What the migration would involve
Configure Authentik Google Social OAuth2 source with all required scopes:
youtube.readonly,youtube.force-ssl(tapir)gmail.readonly,gmail.modify(email-triage-agent)Build a token-forwarding mechanism: services call an Authentik endpoint to get a short-lived access token for a given user. Authentik handles refresh internally.
Remove per-service SOPS secrets and
tapir auth/email-triage-agent authCLI subcommands.Update tapir and email-triage-agent to fetch tokens from Authentik instead of SOPS.
Risk
Deferred — not yet assessed. Assess at migration time. Key risk: Authentik token forwarding requires a custom Property Mapping or a small proxy service — not out-of-the-box. Prototype before committing.
Decision
Deferred. Build per-service OAuth now. Migrate to Authentik-centralized when the third Google service materializes.
Related
mathias/tapir internal/auth/auth.go— current per-service OAuth pattern