Nothing has been indexed for 1.3.7 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.

Installation

# Add this to your shard.yml
dependencies:
  authly:
    github: azutoolkit/authly
    version: ~> 1.3.7

Then run:

shards install

shard.yml

No shard.yml has been indexed for 1.3.7. You can read it on the repository.

Dependencies

Unknown: the shard.yml for this version has not been read yet.

README

This README is the one indexed from the repository at its latest ref, not from the tag for this version.

Authly

Codacy Badge Crystal CI

Authly is a zero-framework OAuth 2.0 and OpenID Connect library built directly on Crystal's standard HTTP::Handler. Mount it as an Authorization Server (Authly::Handler), protect your API routes as a Resource Server (Authly::Handler::Bearer and Authly::Handler::RequireScope), or run both together in the same process or across microservices.


Key Capabilities

AreaSupported Standards & Features
OAuth 2.0 GrantsAuthorization Code (authorization_code), Client Credentials (client_credentials), Refresh Token (refresh_token with rotation & family reuse detection), Device Authorization (urn:ietf:params:oauth:grant-type:device_code), Cryptographic Challenge-Response (challenge_response / urn:ietf:params:oauth:grant-type:challenge), Social Sign-In (social / urn:ietf:params:oauth:grant-type:social), Password (password), Implicit (implicit)
Social Sign-InBuilt-in adapters for Google, GitHub, Apple, Microsoft / Entra ID, Discord, and generic OAuth2/OIDC providers (GET /oauth/social/:provider, GET|POST /oauth/social/:provider/callback, and POST /oauth/token)
OpenID Connectid_token issuance (nonce, acr, amr, auth_time), GET /oauth/userinfo, OIDC Discovery (/.well-known/openid-configuration), OAuth 2.0 Authorization Server Metadata (/.well-known/oauth-authorization-server — RFC 8414), JWKS (/.well-known/jwks.json — RFC 7517)
FAPI & Advanced SecurityPKCE with S256 enforcement (RFC 7636), Pushed Authorization Requests (POST /oauth/par — RFC 9126), JWT Secured Authorization Response Mode (JARM — RFC 9101), Certificate-Bound Access Tokens (cnf.x5t#S256 mTLS — RFC 8705), Dynamic Client Registration (POST /oauth/register — RFC 7591)
Token Lifecycle:jwt (HS*, RS*, ES*) and :opaque token strategies, asymmetric key rotation (signing_key_id + verification_keys), Token Introspection (POST /oauth/introspect — RFC 7662), Token Revocation (POST /oauth/revoke — RFC 7009), host-controlled IssuanceOptions
Resource Server ProtectionAuthly::Handler::Bearer (RFC 6750 verification, audience checking, context.authly_claims!) and Authly::Handler::RequireScope (route-level all / any scope enforcement)

Installation

Add authly to your shard.yml:

dependencies:
  authly:
    github: azutoolkit/authly

Then run:

shards install

Requirements: Crystal >= 1.22.0


Quick Start

Create a working OAuth 2.0 Authorization Server and protected Resource API in a single file:

require "http/server"
require "json"
require "authly"

Authly.configure do |config|
  config.issuer = "http://localhost:8080"
  config.base_url = "http://localhost:8080"
  config.secret_key = ENV.fetch("AUTHLY_SECRET_KEY", "dev-secret-change-in-production-32b")
  config.algorithm = JWT::Algorithm::HS256
  config.token_strategy = :jwt
end

# Register a client for machine-to-machine authentication
Authly.clients << Authly::Client.new(
  name: "Billing Service",
  secret: "billing-secret-123",
  redirect_uri: "http://localhost:8080/callback",
  id: "billing-service",
  scopes: "invoices:read invoices:write",
  allowed_grant_types: ["client_credentials"]
)

app = HTTP::Handler::HandlerProc.new do |context|
  case context.request.path
  when "/health"
    context.response.content_type = "application/json"
    context.response.print({status: "ok"}.to_json)
  when "/api/invoices"
    claims = context.authly_claims!
    context.response.content_type = "application/json"
    context.response.print({
      client_id: claims.client_id,
      scopes:    claims.scopes,
      invoices:  [{id: "inv_01", amount_cents: 4500}],
    }.to_json)
  else
    context.response.status = HTTP::Status::NOT_FOUND
  end
end

server = HTTP::Server.new([
  Authly::Handler.new,
  Authly::Handler::Bearer.new(
    realm: "api",
    except_exact: ["/health"],
    except: ["/oauth", "/.well-known"]
  ),
  Authly::Handler::RequireScope.all("invoices:read", prefixes: ["/api/invoices"]),
  app,
] of HTTP::Handler)

address = server.bind_tcp("127.0.0.1", 8080)
puts "Listening on http://#{address}"
server.listen

Issue a token and call the protected API:

# 1. Request an access token
TOKEN=$(curl -s -X POST http://127.0.0.1:8080/oauth/token \
  -u "billing-service:billing-secret-123" \
  -d "grant_type=client_credentials&scope=invoices:read" | jq -r .access_token)

# 2. Call the protected endpoint
curl -i http://127.0.0.1:8080/api/invoices \
  -H "Authorization: Bearer $TOKEN"

Documentation

Full documentation is available in docs/:

Tutorials

Step-by-step guides that walk you through building working applications from scratch:

How-To Guides

Practical recipes for configuring production features:

Reference

Complete technical specifications:

Explanation

Architectural overviews and security design:


HTTP Endpoints Overview

Authly::Handler mounts the following endpoints out of the box:

Method(s)PathPurpose
GET, POST/oauth/authorizeInteractive authorization & user consent (RFC 6749, OIDC, PKCE, PAR, JARM)
POST/oauth/tokenToken issuance & refresh (authorization_code, client_credentials, refresh_token, password, device_code, challenge_response)
POST/oauth/parPushed Authorization Requests (RFC 9126)
POST/oauth/challengeCryptographic Challenge-Response nonce issuance
GET/oauth/social/:providerSocial Sign-In redirect initiation (PKCE + signed CSRF state)
GET, POST/oauth/social/:provider/callbackSocial Sign-In provider callback
POST/oauth/device/codeDevice Authorization code issuance (RFC 8628)
GET, POST/deviceEnd-user device code verification page (RFC 8628)
POST/oauth/introspect, /introspectToken Introspection (RFC 7662)
POST/oauth/revoke, /revokeToken Revocation (RFC 7009)
GET/oauth/userinfoOpenID Connect UserInfo claims
POST/oauth/registerDynamic Client Registration (RFC 7591)
GET, HEAD/.well-known/openid-configurationOpenID Connect Discovery 1.0 metadata
GET, HEAD/.well-known/oauth-authorization-serverOAuth 2.0 Authorization Server Metadata (RFC 8414)
GET, HEAD/.well-known/jwks.jsonJSON Web Key Set (RFC 7517)

Development & Contributing

Contributions are welcome! To work on Authly locally:

# Install dependencies
shards install

# Run the test suite
crystal spec

# Check formatting
crystal tool format --check src spec

# Run static code analysis
crystal run lib/ameba/src/cli.cr
  1. Fork the repository (https://github.com/azutoolkit/authly/fork)
  2. Create your feature branch (git checkout -b feat/my-feature)
  3. Write specs covering your changes in spec/
  4. Ensure crystal spec, crystal tool format --check src spec, and ameba pass
  5. Open a Pull Request

License

Distributed under the MIT License. See LICENSE for details.

Contact

Elias J. Perez — @eliasjpr · https://github.com/azutoolkit/authly