- Add Groups field to User struct with JSON storage - Include GetGroups() and SetGroups() helper methods - Extract groups from OIDC claims in FromClaim() - Add database migration 202509161200 for groups column - Update config-example.yaml with groups scope - Add comprehensive documentation and testing
7.6 KiB
API Reference: OIDC Groups
This document describes the API changes related to OIDC group storage and management introduced in Headscale v0.24.0.
Overview
Headscale now stores OIDC group membership information extracted from authentication claims. This enables external integrations to implement role-based access control based on a user's group membership.
User API Changes
User Object Schema
The User object now includes group information when users authenticate via OIDC:
{
"id": "1",
"name": "alice",
"createdAt": "2024-01-01T00:00:00Z",
"displayName": "Alice Smith",
"email": "alice@example.com",
"providerId": "https://provider.com/alice",
"provider": "oidc",
"profilePicUrl": "https://provider.com/avatar/alice.jpg",
"groups": ["admin", "developers", "security-team"]
}
Field Descriptions
| Field | Type | Description |
|---|---|---|
groups |
string[] |
Array of group names extracted from OIDC claims. Empty array for non-OIDC users. |
API Endpoints
List Users
Returns all users including their group membership.
Request:
GET /api/v1/user
Authorization: Bearer <api-key>
Response:
{
"users": [
{
"id": "1",
"name": "alice",
"email": "alice@example.com",
"provider": "oidc",
"groups": ["admin", "developers"]
},
{
"id": "2",
"name": "bob",
"email": "",
"provider": "cli",
"groups": []
}
]
}
Get User
Returns a specific user including group membership.
Request:
GET /api/v1/user/{name}
Authorization: Bearer <api-key>
Response:
{
"user": {
"id": "1",
"name": "alice",
"email": "alice@example.com",
"provider": "oidc",
"groups": ["admin", "developers", "security-team"]
}
}
Group Management
Automatic Group Updates
Groups are automatically updated when OIDC users authenticate:
- User logs in via OIDC
- Groups are extracted from ID token and/or UserInfo endpoint
- User's group membership is updated in the database
- API responses include updated group information
Group Sources
Groups can be extracted from multiple sources in OIDC claims:
- Standard
groupsclaim: Most common format rolesclaim: Alternative role-based claim- Provider-specific claims: e.g.,
cognito:groupsfor AWS Cognito - Nested claims: e.g.,
resource_access.client.rolesfor Keycloak
Group Validation
- Only string values are accepted as group names
- Empty or null groups are filtered out
- Group names are stored as-is (case-sensitive)
- Maximum reasonable limit of groups per user (no strict limit enforced)
Integration Examples
Role-Based Access Control
External applications can query user groups for access control:
# Get user groups via API
curl -H "Authorization: Bearer $API_KEY" \
https://headscale.example.com/api/v1/user/alice | \
jq '.user.groups[]'
# Check if user has admin group
curl -H "Authorization: Bearer $API_KEY" \
https://headscale.example.com/api/v1/user/alice | \
jq '.user.groups | contains(["admin"])'
Database Queries
For direct database access:
-- Get all users with their groups
SELECT name, email, groups FROM users WHERE provider = 'oidc';
-- Find users in specific group
SELECT name FROM users
WHERE provider = 'oidc'
AND JSON_EXTRACT(groups, '$') LIKE '%"admin"%';
-- Count users by group membership
SELECT
json_each.value as group_name,
COUNT(*) as user_count
FROM users, json_each(users.groups)
WHERE provider = 'oidc'
GROUP BY json_each.value;
WebUI Integration
Headplane Integration
When using Headplane as a web interface, the groups information enables:
- Automatic Role Assignment: Map OIDC groups to Headplane roles
- Dynamic Permissions: Update user capabilities based on current group membership
- Audit Trails: Track role assignments based on group changes
Example Headplane API usage:
// Fetch user with groups
const response = await fetch('/api/v1/user/alice', {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
const user = await response.json();
// Map groups to roles
const roles = mapGroupsToRoles(user.groups);
console.log(`User ${user.name} has roles:`, roles);
Backward Compatibility
CLI Users
- Users created via CLI (
headscale users create) have empty groups array - No changes to existing CLI user management
- Groups field is optional and defaults to empty array
API Compatibility
- All existing API endpoints continue to work unchanged
- New
groupsfield is additive (doesn't break existing clients) - Clients can safely ignore the groups field if not needed
Migration
- Existing OIDC users will have empty groups until next login
- No database migration required for basic functionality
- Groups are populated automatically on subsequent OIDC logins
Configuration
Headscale Configuration
Ensure groups scope is included for group extraction:
oidc:
issuer: "https://your-provider.com"
client_id: "headscale"
client_secret: "your-secret"
scope: ["openid", "profile", "email", "groups"]
Provider-Specific Configuration
=== "Keycloak"
yaml # Keycloak automatically includes groups in standard format oidc: scope: ["openid", "profile", "email", "groups"]
=== "Azure AD"
yaml # Azure AD requires group claims configuration oidc: scope: ["openid", "profile", "email"] # Groups included via claims configuration in Azure AD
=== "Okta"
yaml # Okta supports groups in tokens oidc: scope: ["openid", "profile", "email", "groups"]
Error Handling
Common Issues
Groups not appearing:
- Verify
groupsscope is included in OIDC configuration - Check identity provider group claim configuration
- Ensure user is member of groups in identity provider
Invalid group data:
- Non-string group values are automatically filtered out
- Empty arrays are valid (user has no groups)
- Malformed JSON is handled gracefully with empty array fallback
Error Responses
Standard HTTP error codes apply:
401 Unauthorized: Invalid or missing API key404 Not Found: User does not exist500 Internal Server Error: Server-side processing error
Security Considerations
Access Control
- Groups information is available to any client with valid API key
- Consider creating read-only API keys for external integrations
- Audit API key usage for compliance requirements
Data Privacy
- Group names may contain sensitive organizational information
- Consider data classification for group membership information
- Implement appropriate access controls for group data
Token Security
- Groups are extracted from verified OIDC tokens only
- Token validation ensures groups cannot be spoofed
- Groups are updated only during successful authentication
Monitoring and Observability
Metrics
Monitor group extraction and updates:
- Number of OIDC logins with groups extracted
- Distribution of group membership across users
- Failed group extractions or parsing errors
Logging
Key log events to monitor:
- Group extraction from OIDC claims
- Group updates during user authentication
- Group parsing errors or validation failures
Example log entries:
INFO Groups extracted from OIDC claims user=alice groups=["admin","developers"]
WARN Invalid group value filtered out user=bob value=123 type=number
ERROR Failed to parse groups from OIDC claims user=charlie error="invalid JSON"
This API enhancement provides the foundation for implementing sophisticated role-based access control systems while maintaining backward compatibility with existing deployments.