User authentication (CLI)
OAuth2 device flow
CLI users authenticate using the OAuth2 device authorization flow, which is designed for devices without browsers:- CLI requests device authorization from OAuth provider
- Provider returns verification URL and user code
- CLI displays: “Visit https://auth.make87.com/activate and enter code: ABCD-EFGH”
- User opens browser and completes authentication
- CLI polls token endpoint until user approves
- CLI receives access token and refresh token
- Tokens stored in
~/.config/m87/credentials.json
The device flow is perfect for CLIs because it doesn’t require embedding a web server or handling redirects. Users authenticate in their regular browser with full security features.
Token lifecycle
Token structure:- Refresh token expires
- You explicitly logout (
m87 logout) - Credentials file is deleted
Token refresh implementation details
Token refresh implementation details
Process:
- Check if current access token is expired
- If expired, use refresh token to get new access token
- Update stored credentials with new tokens
- If provider rotates refresh token, save new one
- Retry original command with fresh token
- If refresh fails (invalid/expired refresh token): Prompt user to run
m87 loginagain - If network error: Retry with exponential backoff
- If auth server down: Show helpful error message
- HTTP client configured with no redirects (prevents SSRF)
- Tokens transmitted only over HTTPS
- Client uses PKCE if supported by provider
OAuth2 configuration
For self-hosted deployments, configure OAuth settings: Server environment variables:Device authentication
Registration and approval workflow
Devices use an API key-based system with manual approval:Step 1: Initiate registration
On the device:Step 2: List pending requests
From your workstation:Step 3: Approve device
- Validate approver has permission for requested owner scope
- Generate cryptographically secure API key
- Store API key hashed in database
- Mark request as approved
- Return API key to polling runtime
Step 4: Device receives credentials
The runtime polls every 10 seconds:- Saves API key to
~/.config/m87/credentials.json - Establishes QUIC tunnel to server
- Becomes available for remote access
API key storage
Device credentials stored separately from user credentials:- Permissions:
0o600(owner read/write only) - Location:
~/.config/m87/credentials.json - Format: JSON with pretty printing
Environment variable authentication
For automation and CI/CD, provide credentials via environment:Credential precedence order
Credential precedence order
- Environment variables (highest priority)
M87_API_KEY: Device API keyOWNER_REFERENCE: Owner scope for registration
- Config file:
~/.config/m87/credentials.json - Interactive prompts (lowest priority)
- Registration prompts for owner if not set
- Login prompts for OAuth if no credentials
Authorization and access control
Scope-based model
Access control uses a flexible scope system: Scope formats:user:<email>: Personal ownership (e.g.,user:alice@example.com)org:<org-id>: Organization ownership (e.g.,org:acme-corp)
-
Owner access: User’s scope matches
owner_scope -
Shared access: User’s scope in
allowed_scopes -
Organization access: User belongs to organization
Query filtering
The server automatically filters queries based on user scopes:user:alice@example.com) runs m87 devices list:
- Server extracts scopes from Alice’s JWT:
["user:alice@example.com", "org:acme-corp"] - Server queries MongoDB:
- Returns only devices Alice can access
Every API endpoint applies scope filtering, providing defense in depth even if application logic has bugs.
Role-based permissions
Within organizations, roles control capabilities: Roles:admin: Full access to all org devices, can approve registrationsmember: Access to assigned devices onlyviewer: Read-only access (logs, metrics, status)
Authentication flows
First-time setup (user)
First-time setup (device)
Command execution (authenticated)
Session management
CLI sessions
Login persistence:- OAuth tokens persist until refresh token expires
- Typical lifetime: 30 days (configurable by OAuth provider)
- Automatic refresh on every command
Device sessions
Persistent connection:- Device maintains long-lived QUIC tunnel
- Automatic reconnection on network changes
- Connection migration (QUIC feature) handles IP changes
Connection state
Server tracks active tunnels:- Device establishes QUIC connection with API key in initial packet
- Server validates API key, extracts device ID
- Server stores tunnel in
RelayState - CLI requests are routed through tunnel
- On disconnect, server marks device as “lost”
- On reconnect, server replaces old tunnel atomically
Security considerations
Token security
Access tokens:- Short-lived (default: 1 hour)
- Transmitted only over HTTPS
- Never logged or displayed
- Stored in memory, not written to disk between refreshes
- Longer-lived (default: 30 days)
- Stored in credentials file with restrictive permissions
- Used only to obtain new access tokens
- Should be rotated periodically by OAuth provider
- Cryptographically random (256-bit entropy)
- Hashed before storage in database
- Transmitted only during initial approval
- Stored locally with file permissions
Best practices
Rotate device credentials
Periodically remove and re-register devices to rotate API keys, especially after personnel changes.
Use organization scopes
Register devices under
org: scopes for team access rather than personal user: scopes.Monitor audit logs
Regularly review device audit logs (
m87 <device> audit) for unexpected access.Secure credentials file
Never commit
~/.config/m87/credentials.json to version control or share publicly.Troubleshooting authentication
CLI login fails
Symptoms:- “Failed to auth” error after entering code
- Token request times out
- Check network connectivity to OAuth provider
- Verify system clock is accurate (JWT validation requires correct time)
- Try
m87 logoutthenm87 loginagain - Check OAuth provider status page
Device registration stuck
Symptoms:- “Waiting for approval” never completes
- Request not visible in
m87 devices list
- Verify device can reach m87 server on port 443
- Check request_id matches between device and CLI
- Ensure approving user has permission for requested owner scope
- Look for firewall rules blocking outbound QUIC/UDP
Token refresh fails
Symptoms:- “Invalid token” errors after successful login
- Commands fail with authentication errors
- Check refresh token hasn’t expired:
m87 status - Run
m87 logout && m87 loginto get fresh tokens - Verify OAuth provider hasn’t revoked your tokens
- Check credentials file permissions:
ls -la ~/.config/m87/credentials.json
Permission denied errors
Symptoms:- “Device not found” for device you know exists
- “Access denied” when trying to access device
- Verify your user scope matches device owner scope or is in allowed scopes
- Check you’re logged in with correct account:
m87 status - Ask device owner to add your scope to
allowed_scopes - For org devices, ensure you’re a member of the correct organization