package

github.com/aluminumio/oauth-device-flow.cr

0.1.1 / published Apr 26, 2026 / repository

OAuth 2.0 Device Authorization Grant (RFC 8628) client for Crystal CLIs. Full UX, auto-refresh, pluggable token store, zero runtime dependencies.

oauth-device-flow

OAuth 2.0 Device Authorization Grant (RFC 8628) for Crystal CLIs. Full UX, auto-refresh, pluggable token store, zero runtime dependencies.

Install

# shard.yml
dependencies:
  oauth-device-flow:
    github: aluminumio/oauth-device-flow.cr
    version: ~> 0.1.0
shards install

Use

require "oauth-device-flow"

store = OAuth::DeviceFlow::NetrcStore.new(machine: "rightdocuments.com")
client = OAuth::DeviceFlow::Client.new(
  base_url:  "https://rightdocuments.com",
  client_id: ENV["RIGHTDOCUMENTS_CLIENT_ID"],
  store:     store,
)

client.authenticate(scope: "documents:read documents:write") unless client.authenticated?

# On every API request — auto-refreshes if expired:
HTTP::Client.get(
  "https://rightdocuments.com/api/v1/me",
  headers: HTTP::Headers{"Authorization" => "Bearer #{client.access_token}"},
)

API

MethodReturnsBehavior
Client#authenticate(scope)TokenFull RFC 8628 flow: request device code, open browser, poll, persist.
Client#access_tokenStringLoads from store; auto-refreshes if expired. Raises NotAuthenticated if empty.
Client#authenticated?BoolTrue if any token is stored (no expiry check, no network).
Client#refresh!TokenForce a refresh. Raises InvalidGrant if no refresh_token.
Client#logoutNilClears the store.

Stores

  • MemoryStore — for tests.
  • NetrcStore.new(machine:) — ~/.netrc adapter, 0600 permissions, preserves other entries.

To plug in your own (Keychain, libsecret, JSON file): subclass OAuth::DeviceFlow::Store and implement load, save, clear.

Errors

All under OAuth::DeviceFlow::Error::*, all descend from OAuth::DeviceFlow::Error::Base: AuthorizationPending, SlowDown, AccessDenied, ExpiredToken, InvalidClient, InvalidGrant, NotAuthenticated, NetworkError.

authenticate only surfaces AccessDenied (user clicked Deny) or ExpiredToken (timeout) for normal failures — pending and slow_down are handled internally by the polling loop.

License

MIT

API