JWT/OAuth Authentication
Tom can validate JWT (JSON Web Token) tokens from OAuth providers, enabling SSO integration for interactive users. Tom validates tokens but doesn't issue them - clients obtain tokens from their OAuth provider and present them to Tom.
When to Use
- Interactive users (humans)
- SSO integration with existing identity provider
- User-level audit logging
- When you need granular access control by user/domain
Supported Providers
| Provider | Status | Notes |
|---|---|---|
| Duo Security | Tested | ID tokens and access tokens |
| Google OAuth | Tested | ID tokens only (access tokens are opaque) |
| Microsoft Entra ID | Tested | Full OIDC support |
Other OIDC-compliant providers should work with minimal configuration.
How It Works
- Client authenticates with OAuth provider and receives a JWT
- Client sends API request to Tom with
Authorization: Bearer <token> - Tom fetches the provider's public keys (JWKS) to validate the token signature
- Tom checks authorization rules (allowed users/domains)
- Request proceeds if valid
Configuration
Basic Setup
# tom_controller_config.yaml
auth_mode: jwt
jwt_providers:
- name: google # Unique identifier for this provider instance
type: google # Provider type: duo, google, or entra
enabled: true
client_id: "your-client-id.apps.googleusercontent.com"
discovery_url: "https://accounts.google.com/.well-known/openid-configuration"
Multiple Providers
jwt_providers:
- name: google
type: google
enabled: true
client_id: "google-client-id"
discovery_url: "https://accounts.google.com/.well-known/openid-configuration"
- name: entra
type: entra
enabled: true
client_id: "entra-client-id"
tenant_id: "your-tenant-id"
discovery_url: "https://login.microsoftonline.com/your-tenant-id/v2.0/.well-known/openid-configuration"
- name: duo
type: duo
enabled: false # Disabled
client_id: "duo-client-id"
discovery_url: "https://your-tenant.duosecurity.com/.well-known/openid-configuration"
Tom tries each enabled provider in order until one validates the token.
Multiple Providers of Same Type
You can configure multiple providers of the same type with different names. This is useful for service-to-service authentication where different services have different OAuth clients:
jwt_providers:
# Accept tokens from your primary app
- name: duo-primary
type: duo
client_id: "PRIMARY_CLIENT_ID"
discovery_url: "https://sso.example.com/oidc/PRIMARY_CLIENT_ID/.well-known/openid-configuration"
# Accept tokens from an internal service
- name: duo-internal-service
type: duo
client_id: "INTERNAL_SERVICE_CLIENT_ID"
discovery_url: "https://sso.example.com/oidc/INTERNAL_SERVICE_CLIENT_ID/.well-known/openid-configuration"
audience:
- "PRIMARY_CLIENT_ID" # Accept tokens intended for primary app
- "INTERNAL_SERVICE_CLIENT_ID"
Provider Options
| Option | Required | Default | Description |
|---|---|---|---|
name |
Yes | - | Unique identifier for this provider instance |
type |
Yes | - | Provider type: duo, google, or entra |
enabled |
No | true |
Enable/disable this provider |
client_id |
Yes | - | OAuth client ID |
discovery_url |
Yes | - | OIDC discovery endpoint |
audience |
No | client_id |
Expected token audience (string or list) |
tenant_id |
No | - | Microsoft Entra tenant ID |
leeway_seconds |
No | 30 |
Clock skew tolerance |
Authorization
After validating the JWT, Tom checks if the user is authorized:
# Allow specific users (exact match, case-insensitive)
allowed_users:
- alice@example.com
- bob@company.com
- service.account
# Allow by email domain
allowed_domains:
- example.com
- subsidiary.com
# Allow by regex pattern
allowed_user_regex:
- '^netops-.*@example\.com$'
- '^[a-z]+\.admin@example\.com$'
Authorization Logic
- Check
allowed_users- exact match on email/username - Check
allowed_domains- domain extracted from email - Check
allowed_user_regex- pattern match
Any match grants access (OR logic). If all three lists are empty, any valid JWT is authorized (which would be terrible in production! Do you REALLY want 'any authenticated google user' to login? Probably not!).
User Identifier
The user identifier depends on the provider:
| Provider | Identifier Source |
|---|---|
email or sub |
|
| Duo | preferred_username or email or sub |
| Entra | preferred_username or upn or email or sub |
Usage
Include the JWT in the Authorization header:
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
"http://localhost:8000/api/device/router1/send_command?command=show+version&wait=true"
Provider-Specific Setup
Google OAuth
- Create project in Google Cloud Console
- Enable OAuth consent screen
- Create OAuth 2.0 Client ID (Web application or Desktop)
- Note the Client ID
jwt_providers:
- name: google
type: google
enabled: true
client_id: "123456789-abc.apps.googleusercontent.com"
discovery_url: "https://accounts.google.com/.well-known/openid-configuration"
Note: Google access tokens are opaque. Use ID tokens for Tom authentication.
Microsoft Entra ID
- Register application in Azure Portal > App Registrations
- Note the Application (client) ID and Directory (tenant) ID
- Configure redirect URIs as needed
jwt_providers:
- name: entra
type: entra
enabled: true
client_id: "your-application-id"
tenant_id: "your-tenant-id"
discovery_url: "https://login.microsoftonline.com/your-tenant-id/v2.0/.well-known/openid-configuration"
Duo Security
- Create OIDC application in Duo Admin Panel
- Note the Client ID and API hostname
jwt_providers:
- name: duo
type: duo
enabled: true
client_id: "your-duo-client-id"
discovery_url: "https://your-tenant.duosecurity.com/.well-known/openid-configuration"
CLI Client
For an example of oAuth for command-line access, see tomclient - a reference implementation that handles OAuth PKCE flow for CLI authentication.
# Authenticate with OAuth provider
tomclient auth login
# oAuth flow runs in browser
# Make API calls (token handled automatically)
tomclient device router1 "show version"
Security Settings
HTTPS Enforcement
jwt_require_https: true # Default: true
Keep enabled in production. Only disable for local development.
Token Leeway
Allow clock skew between Tom and the OAuth provider:
jwt_providers:
- name: google
type: google
leeway_seconds: 30 # Default
Testing
Tom includes a test page for OAuth flow debugging:
oauth_test_enabled: true # Enable test endpoints (dev only)
jwt_providers:
- name: google
type: google
oauth_test_client_secret: "your-client-secret" # Only for test flow
oauth_test_scopes: ["openid", "email", "profile"]
Access at http://localhost:8000/static/oauth-test.html
Warning: Never enable oauth_test_enabled in production.
Debug Endpoint
Check authentication status:
curl -H "Authorization: Bearer eyJ..." \
"http://localhost:8000/api/auth/debug"
Returns:
{
"method": "jwt",
"user": "alice@example.com",
"provider": "google",
"all_claims": { ... },
"custom_claims": { ... }
}
The custom_claims field filters out standard JWT claims (iss, sub, aud, exp, etc.) to show only provider-specific claims.