Skip to main content
The m87 platform uses different authentication mechanisms for CLI users and devices, with a scope-based authorization model for access control.

User authentication (CLI)

OAuth2 device flow

CLI users authenticate using the OAuth2 device authorization flow, which is designed for devices without browsers:
What happens:
  1. CLI requests device authorization from OAuth provider
  2. Provider returns verification URL and user code
  3. CLI displays: “Visit https://auth.make87.com/activate and enter code: ABCD-EFGH”
  4. User opens browser and completes authentication
  5. CLI polls token endpoint until user approves
  6. CLI receives access token and refresh token
  7. 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:
Automatic refresh: The CLI automatically refreshes expired tokens:
Every command checks token validity and refreshes if needed, so you never need to manually re-authenticate unless:
  • Refresh token expires
  • You explicitly logout (m87 logout)
  • Credentials file is deleted
Process:
  1. Check if current access token is expired
  2. If expired, use refresh token to get new access token
  3. Update stored credentials with new tokens
  4. If provider rotates refresh token, save new one
  5. Retry original command with fresh token
Error handling:
  • If refresh fails (invalid/expired refresh token): Prompt user to run m87 login again
  • If network error: Retry with exponential backoff
  • If auth server down: Show helpful error message
Security:
  • 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:
Client configuration:
When using custom OAuth providers, ensure they support the device authorization grant type (RFC 8628). Not all OAuth2 providers implement this flow.

Device authentication

Registration and approval workflow

Devices use an API key-based system with manual approval:

Step 1: Initiate registration

On the device:
Request payload:
Server response:
The runtime displays:

Step 2: List pending requests

From your workstation:
Output shows pending devices:

Step 3: Approve device

What happens on the server:
  1. Validate approver has permission for requested owner scope
  2. Generate cryptographically secure API key
  3. Store API key hashed in database
  4. Mark request as approved
  5. Return API key to polling runtime

Step 4: Device receives credentials

The runtime polls every 10 seconds:
Once approved, the device:
  1. Saves API key to ~/.config/m87/credentials.json
  2. Establishes QUIC tunnel to server
  3. Becomes available for remote access
The approval timeout is 60 minutes by default. If it expires, simply run m87 runtime run again to create a new registration request.

API key storage

Device credentials stored separately from user credentials:
File security:
  • 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:
  1. Environment variables (highest priority)
    • M87_API_KEY: Device API key
    • OWNER_REFERENCE: Owner scope for registration
  2. Config file: ~/.config/m87/credentials.json
  3. 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)
Device ownership:
Access rules:
  1. Owner access: User’s scope matches owner_scope
  2. Shared access: User’s scope in allowed_scopes
  3. Organization access: User belongs to organization

Query filtering

The server automatically filters queries based on user scopes:
Example: User Alice (user:alice@example.com) runs m87 devices list:
  1. Server extracts scopes from Alice’s JWT: ["user:alice@example.com", "org:acme-corp"]
  2. Server queries MongoDB:
  3. Returns only devices Alice can access
This ensures users never see devices they don’t have permission for, even if they guess device IDs.
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 registrations
  • member: Access to assigned devices only
  • viewer: Read-only access (logs, metrics, status)
Implementation:
Role enforcement happens server-side. The CLI cannot bypass these restrictions, even with a valid token.

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
Logout:
Removes credentials from local file but does not revoke tokens (follow OAuth provider’s revocation process for that).

Device sessions

Persistent connection:
  • Device maintains long-lived QUIC tunnel
  • Automatic reconnection on network changes
  • Connection migration (QUIC feature) handles IP changes
Deregistration:

Connection state

Server tracks active tunnels:
Tunnel lifecycle:
  1. Device establishes QUIC connection with API key in initial packet
  2. Server validates API key, extracts device ID
  3. Server stores tunnel in RelayState
  4. CLI requests are routed through tunnel
  5. On disconnect, server marks device as “lost”
  6. On reconnect, server replaces old tunnel atomically
QUIC’s connection migration feature allows devices to maintain sessions even when switching networks (e.g., Ethernet to WiFi).

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
Refresh tokens:
  • 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
API keys (devices):
  • Cryptographically random (256-bit entropy)
  • Hashed before storage in database
  • Transmitted only during initial approval
  • Stored locally with file permissions
If a device’s credentials file is compromised, an attacker gains access to that device. Immediately run m87 devices reject <device-id> to revoke access.

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
Solutions:
  1. Check network connectivity to OAuth provider
  2. Verify system clock is accurate (JWT validation requires correct time)
  3. Try m87 logout then m87 login again
  4. Check OAuth provider status page

Device registration stuck

Symptoms:
  • “Waiting for approval” never completes
  • Request not visible in m87 devices list
Solutions:
  1. Verify device can reach m87 server on port 443
  2. Check request_id matches between device and CLI
  3. Ensure approving user has permission for requested owner scope
  4. Look for firewall rules blocking outbound QUIC/UDP

Token refresh fails

Symptoms:
  • “Invalid token” errors after successful login
  • Commands fail with authentication errors
Solutions:
  1. Check refresh token hasn’t expired: m87 status
  2. Run m87 logout && m87 login to get fresh tokens
  3. Verify OAuth provider hasn’t revoked your tokens
  4. 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
Solutions:
  1. Verify your user scope matches device owner scope or is in allowed scopes
  2. Check you’re logged in with correct account: m87 status
  3. Ask device owner to add your scope to allowed_scopes
  4. For org devices, ensure you’re a member of the correct organization
Enable debug logging to see authentication details: RUST_LOG=debug m87 <command>