authly v1.2.7
Authly

A robust OAuth2 and OpenID Connect authentication library for Crystal
Authly is a production-ready Crystal library that provides secure, standards-compliant OAuth2 and OpenID Connect authentication capabilities. Built with developer experience in mind, Authly offers a comprehensive suite of grant types, flexible token strategies, and intuitive APIs that make implementing authentication straightforward and maintainable.
Table of Contents
- Features
- Installation
- Quick Start
- Configuration
- OAuth2 Grant Types
- Authentication Endpoints
- Customization
- Token Strategies
- Roadmap
- Contributing
- License
- Contact
Features
Authly provides a comprehensive authentication solution with the following capabilities:
OAuth2 Grant Types
- ✅ Authorization Code Grant (with PKCE support)
- ✅ Client Credentials Grant
- ✅ Resource Owner Password Credentials Grant
- ✅ Implicit Grant
- ✅ Refresh Token Grant
- ✅ Device Authorization Grant
OpenID Connect
- ✅ ID Token generation and validation
- ✅ Standard claims support
- ✅ Custom claims integration
- ✅ UserInfo endpoint
- ✅ OpenID Connect Discovery (/.well-known/openid-configuration)
- ✅ OAuth2 Dynamic Client Registration (RFC 7591)
Token Management
- ✅ JWT and Opaque token strategies
- ✅ Token introspection endpoint
- ✅ Token revocation endpoint
- ✅ Configurable token expiration (TTL)
- ✅ Pluggable token storage backend
Security
- ✅ PKCE (Proof Key for Code Exchange) support with S256 enforcement
- ✅ Certificate-bound access tokens (mTLS / RFC 8705)
- ✅ JWT Secured Authorization Response Mode (JARM / RFC 9101)
- ✅ State parameter validation
- ✅ Redirect URI validation
- ✅ Secure token generation
- ✅ Multiple JWT algorithms support (HS256, RS256, etc.)
Developer Experience
- ✅ Intuitive, type-safe API
- ✅ Comprehensive documentation
- ✅ Easy-to-configure HTTP handlers
- ✅ Flexible customization points
- ✅ Built-in middleware support
Installation
Add Authly to your shard.yml
:
dependencies:
authly:
github: azutoolkit/authly
Then run:
shards install
Requirements:
- Crystal 1.0 or higher
- A supported operating system (Linux, macOS, or Windows)
Quick Start
Get up and running with Authly in minutes:
require "authly"
# Configure Authly
Authly.configure do |config|
config.issuer = "https://your-app.com"
config.secret_key = ENV["AUTHLY_SECRET_KEY"]
config.access_ttl = 1.hour
config.refresh_ttl = 1.day
config.token_strategy = :jwt
config.algorithm = JWT::Algorithm::HS256
end
# Start the authentication server
server = HTTP::Server.new([
Authly::Handler.new,
])
server.bind_tcp("0.0.0.0", 8080)
puts "Authly server listening on http://0.0.0.0:8080"
server.listen
This minimal setup provides a working OAuth2 server with JWT token support. For production use, you'll want to implement custom clients, owners, and token storage (see Customization).
Configuration
Authly offers extensive configuration options to tailor authentication to your application's needs:
Authly.configure do |config|
# Issuer Configuration
config.issuer = "https://your-app.com"
config.base_url = "https://your-app.com" # Base URL for OpenID Connect Discovery
# Security Keys
config.secret_key = ENV["AUTHLY_SECRET_KEY"] # Required for JWT signing
config.public_key = ENV["AUTHLY_PUBLIC_KEY"] # Optional, for asymmetric algorithms
# Token Time-to-Live (TTL)
config.access_ttl = 1.hour # Access token expiration
config.refresh_ttl = 1.day # Refresh token expiration
config.code_ttl = 5.minutes # Authorization code expiration
# Token Strategy
config.token_strategy = :jwt # Options: :jwt or :opaque
config.algorithm = JWT::Algorithm::HS256 # JWT signing algorithm
# PKCE Enforcement
config.enforce_pkce = false # Require PKCE for all authorization code flows
config.enforce_pkce_s256 = false # Enforce S256 method only (reject plain)
# Dynamic Client Registration
config.allow_dynamic_registration = false
config.client_store = InMemoryClientStore.new
# Certificate-Bound Tokens (mTLS)
config.require_certificate_bound_tokens = false
config.trusted_ca_certificates = [] of String
# Custom Implementations (see Customization section)
config.owners = CustomAuthorizableOwner.new
config.clients = CustomAuthorizableClient.new
config.token_store = CustomTokenStore.new
config.state_store = CustomStateStore.new
config.device_code_store = CustomDeviceCodeStore.new
end
Configuration Options
Option | Type | Description | Default |
---|---|---|---|
issuer |
String | The issuer identifier for tokens | Required |
secret_key |
String | Secret key for JWT signing | Required |
public_key |
String | Public key for asymmetric algorithms | nil |
access_ttl |
Time::Span | Access token lifetime | 1.hour |
refresh_ttl |
Time::Span | Refresh token lifetime | 1.day |
code_ttl |
Time::Span | Authorization code lifetime | 5.minutes |
token_strategy |
Symbol | Token type (:jwt or :opaque ) |
:jwt |
algorithm |
JWT::Algorithm | JWT signing algorithm | HS256 |
owners |
AuthorizableOwner | Resource owner authentication | Required |
clients |
AuthorizableClient | Client authentication | Required |
token_store |
TokenStore | Token persistence layer | In-memory |
OAuth2 Grant Types
Authly implements all standard OAuth2 grant types, allowing you to choose the appropriate authentication flow for your use case:
Authorization Code Grant
The most secure OAuth2 flow, recommended for server-side applications. Supports PKCE for enhanced security.
Use Case: Web applications with a backend server, mobile applications
Endpoint: POST /oauth/token
# Request parameters
{
"grant_type": "authorization_code",
"code": "AUTH_CODE",
"redirect_uri": "https://your-app.com/callback",
"client_id": "CLIENT_ID",
"client_secret": "CLIENT_SECRET",
"code_verifier": "CODE_VERIFIER" # Optional, for PKCE
}
Client Credentials Grant
Used for server-to-server authentication where the client acts on its own behalf.
Use Case: Microservices, background jobs, API integrations
Endpoint: POST /oauth/token
# Request parameters
{
"grant_type": "client_credentials",
"client_id": "CLIENT_ID",
"client_secret": "CLIENT_SECRET",
"scope": "read write" # Optional
}
Resource Owner Password Credentials Grant
Allows exchanging username and password directly for tokens. Only use for highly trusted applications.
Use Case: First-party applications, legacy system migrations
Endpoint: POST /oauth/token
# Request parameters
{
"grant_type": "password",
"username": "USER_NAME",
"password": "PASSWORD",
"client_id": "CLIENT_ID",
"client_secret": "CLIENT_SECRET",
"scope": "read write" # Optional
}
Implicit Grant
Returns tokens directly from the authorization endpoint without an intermediate code exchange.
Use Case: Single-page applications (deprecated in favor of Authorization Code with PKCE)
Endpoint: GET /oauth/authorize
Refresh Token Grant
Allows obtaining a new access token without requiring user interaction.
Use Case: Maintaining user sessions, token renewal
Endpoint: POST /oauth/token
# Request parameters
{
"grant_type": "refresh_token",
"refresh_token": "REFRESH_TOKEN",
"client_id": "CLIENT_ID",
"client_secret": "CLIENT_SECRET"
}
Device Authorization Grant
Enables authentication on devices with limited input capabilities.
Use Case: Smart TVs, IoT devices, CLI tools
Endpoints:
POST /oauth/device/authorize
- Request device codePOST /oauth/token
- Exchange device code for tokens
Authentication Endpoints
Authly provides standard OAuth2 endpoints for authentication flows:
Pushed Authorization Request Endpoint
Path: POST /oauth/par
Allows clients to push authorization parameters directly to the authorization server, receiving a short-lived request_uri
for use in the authorization flow (RFC 9126).
Request:
POST /oauth/par HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
response_type=code&
redirect_uri=https://client.example.com/callback&
scope=openid profile&
state=xyz&
code_challenge=CHALLENGE&
code_challenge_method=S256
Response:
{
"request_uri": "urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY",
"expires_in": 90
}
See Pushed Authorization Requests for detailed information.
Authorization Endpoint
Path: GET /oauth/authorize
Initiates the authorization flow for grant types requiring user interaction.
Parameters:
response_type
- Type of response (code
,token
)client_id
- Client identifierredirect_uri
- Callback URLscope
- Requested permissions (optional)state
- CSRF protection token (recommended)code_challenge
- PKCE challenge (optional)code_challenge_method
- PKCE method (S256
orplain
)
Token Endpoint
Path: POST /oauth/token
Exchanges authorization grants for access tokens.
Parameters: Vary by grant type (see OAuth2 Grant Types)
Introspection Endpoint
Path: POST /oauth/introspect
Validates and retrieves metadata about a token.
# Request
{
"token": "ACCESS_TOKEN",
"token_type_hint": "access_token" # Optional
}
# Response
{
"active": true,
"client_id": "CLIENT_ID",
"username": "USER_NAME",
"exp": 1640000000,
"iat": 1639999000,
"scope": "read write"
}
Revocation Endpoint
Path: POST /oauth/revoke
Invalidates an access or refresh token.
# Request
{
"token": "TOKEN_TO_REVOKE",
"token_type_hint": "access_token" # Optional
}
Device Authorization Endpoints
Request Device Code: POST /oauth/device/authorize
# Response
{
"device_code": "DEVICE_CODE",
"user_code": "USER_CODE",
"verification_uri": "https://your-app.com/device",
"expires_in": 300,
"interval": 5
}
Verify Device Code: GET /oauth/device/verify
User visits this endpoint to enter the user code and authorize the device.
UserInfo Endpoint
Path: GET /oauth/userinfo
Returns user claims based on the granted scopes in the access token.
Request:
GET /oauth/userinfo HTTP/1.1
Authorization: Bearer ACCESS_TOKEN
Response:
{
"sub": "user_id_123",
"name": "John Doe",
"email": "john@example.com",
"email_verified": true,
"picture": "https://example.com/avatar.jpg"
}
Supported Scopes:
openid
: Returnssub
(subject/user ID)profile
: Returns profile information (name, picture, etc.)email
: Returns email and email_verifiedaddress
: Returns address informationphone
: Returns phone number information
OpenID Connect Discovery Endpoint
Path: GET /.well-known/openid-configuration
Returns OpenID Connect discovery metadata for automatic client configuration.
Response:
{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/oauth/authorize",
"token_endpoint": "https://auth.example.com/oauth/token",
"userinfo_endpoint": "https://auth.example.com/oauth/userinfo",
"jwks_uri": "https://auth.example.com/.well-known/jwks.json",
"registration_endpoint": "https://auth.example.com/oauth/register",
"scopes_supported": ["openid", "profile", "email", "address", "phone"],
"response_types_supported": ["code", "token", "id_token"],
"grant_types_supported": ["authorization_code", "client_credentials", ...],
"code_challenge_methods_supported": ["S256", "plain"]
}
Dynamic Client Registration Endpoint
Path: POST /oauth/register
Allows clients to dynamically register with the authorization server (RFC 7591).
Configuration Required:
Authly.configure do |config|
config.allow_dynamic_registration = true
config.client_store = CustomClientStore.new
end
Request:
{
"redirect_uris": ["https://client.example.com/callback"],
"client_name": "My Application",
"client_uri": "https://client.example.com",
"logo_uri": "https://client.example.com/logo.png",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "client_secret_basic",
"scope": "openid profile email",
"contacts": ["admin@client.example.com"]
}
Response:
{
"client_id": "client_abcdef123456",
"client_secret": "secret_xyz789",
"client_id_issued_at": 1640000000,
"client_secret_expires_at": 0,
"redirect_uris": ["https://client.example.com/callback"],
"client_name": "My Application",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "client_secret_basic"
}
Public Clients:
For public clients (e.g., SPAs, mobile apps), set token_endpoint_auth_method
to "none"
:
{
"redirect_uris": ["https://spa.example.com/callback"],
"token_endpoint_auth_method": "none"
}
Public clients receive a client_id
but no client_secret
.
Advanced Security Features
Enhanced PKCE with S256 Enforcement
PKCE (Proof Key for Code Exchange) is a security extension for OAuth2 that prevents authorization code interception attacks. Authly now supports enforcing PKCE and mandating the S256 method.
Configuration:
Authly.configure do |config|
# Require PKCE for all authorization code flows
config.enforce_pkce = true
# Enforce S256 method, reject plain method
config.enforce_pkce_s256 = true
end
Usage:
When PKCE is enforced, all authorization requests must include:
code_challenge
: The code challenge derived from the code verifiercode_challenge_method
: Must beS256
when enforcement is enabled
Example Authorization Request:
GET /oauth/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=REDIRECT_URI&
code_challenge=CHALLENGE&
code_challenge_method=S256&
state=STATE
Code Verifier Generation:
# Generate a cryptographically random code verifier
verifier = Random::Secure.hex(32)
# Calculate the S256 challenge
challenge = Digest::SHA256.base64digest(verifier)
Token Exchange:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTH_CODE&
client_id=CLIENT_ID&
redirect_uri=REDIRECT_URI&
code_verifier=VERIFIER
Pushed Authorization Requests (PAR)
Pushed Authorization Requests (RFC 9126) is a security enhancement that moves authorization parameters from the front-channel (browser) to a back-channel (direct server-to-server) request. This prevents parameter tampering, reduces URL length limitations, and provides better security for sensitive authorization data.
Configuration:
Authly.configure do |config|
# Enable PAR endpoint (always available)
config.par_ttl = 90.seconds # Request URI expiration (default: 90 seconds)
# Optionally require PAR for all authorization requests
config.require_par = true
# Optional: Custom PAR store implementation
config.par_store = CustomPARStore.new
end
How It Works:
PAR operates in two steps:
Step 1: Push Authorization Parameters (Back-channel)
The client sends authorization parameters directly to the authorization server:
POST /oauth/par HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
response_type=code&
redirect_uri=https://client.example.com/callback&
scope=openid profile email&
state=xyz&
code_challenge=CHALLENGE&
code_challenge_method=S256
Response:
{
"request_uri": "urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY",
"expires_in": 90
}
Step 2: Authorization Request (Front-channel)
The client redirects the user with only the request_uri
:
GET /oauth/authorize?
client_id=CLIENT_ID&
request_uri=urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY
Benefits:
- Enhanced Security: Parameters aren't exposed in browser URLs or server logs
- Prevents Tampering: Authorization parameters can't be modified by attackers
- Large Requests: Bypasses URL length limitations for complex authorization requests
- Server-Side Validation: Parameters validated before user interaction
- Replay Protection: Request URIs are single-use and time-limited
- FAPI Compliant: Required for Financial-grade API security profiles
Client Authentication:
PAR requires client authentication using one of:
client_secret_basic
: HTTP Basic authentication (recommended)client_secret_post
: Client credentials in request body
Security Features:
- Request URIs expire after 90 seconds (configurable)
- Single-use request URIs (consumed after authorization)
- Client ID validation to prevent unauthorized use
- Automatic cleanup of expired request URIs
Certificate-Bound Access Tokens (mTLS)
Certificate-bound tokens (RFC 8705) bind access tokens to client certificates, preventing token theft and replay attacks. This feature requires mTLS (mutual TLS) configuration at the HTTP server level.
Configuration:
Authly.configure do |config|
# Enable certificate-bound tokens
config.require_certificate_bound_tokens = true
# Optional: Specify trusted CA certificates for validation
config.trusted_ca_certificates = [
File.read("path/to/ca1.pem"),
File.read("path/to/ca2.pem")
]
end
How It Works:
- Client presents a certificate during the TLS handshake
- Authly calculates the SHA-256 thumbprint of the certificate
- The thumbprint is embedded in the access token's
cnf
(confirmation) claim - When using the token, the client must present the same certificate
- Authly validates that the certificate matches the bound token
Token Structure (JWT):
{
"sub": "user_id",
"iss": "https://auth.example.com",
"exp": 1640000000,
"cnf": {
"x5t#S256": "bwcK0esc3ACC3DB2Y5_lESsXE8o9ltc05O89jdN-dg2"
}
}
Server Configuration:
For mTLS to work, your HTTP server must be configured to request client certificates. With Crystal's HTTP::Server and a reverse proxy like nginx:
server {
listen 443 ssl;
ssl_client_certificate /path/to/ca.pem;
ssl_verify_client optional;
ssl_verify_depth 2;
location / {
proxy_pass http://authly_backend;
proxy_set_header X-Client-Certificate $ssl_client_cert;
}
}
Benefits:
- Prevents token theft - stolen tokens cannot be used without the certificate
- Suitable for high-security environments (banking, healthcare, government)
- Compliant with FAPI (Financial-grade API) security profile
JWT Secured Authorization Response Mode (JARM)
JARM (RFC 9101) protects authorization responses by encoding them in signed and optionally encrypted JWTs. This prevents response tampering, injection attacks, and ensures the integrity and authenticity of authorization responses.
Configuration:
Authly.configure do |config|
# JARM signing configuration
config.jarm_signing_key = ENV["JARM_SIGNING_KEY"]
config.jarm_signing_alg = JWT::Algorithm::RS256
# Optional: JARM encryption configuration
config.jarm_encryption_key = ENV["JARM_ENCRYPTION_KEY"]
config.jarm_encryption_alg = "RSA-OAEP"
config.jarm_encryption_enc = "A256GCM"
# Optional: Default JARM response mode
config.default_jarm_response_mode = "query.jwt"
# JARM JWT lifetime (default: 10 minutes)
config.jarm_lifetime = 10.minutes
end
How It Works:
When JARM is enabled, instead of returning authorization parameters in the query string or fragment:
https://client.example.com/callback?code=AUTH_CODE&state=xyz
The authorization server returns a signed JWT containing the response:
https://client.example.com/callback?response=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
The JWT payload contains:
{
"iss": "https://auth.example.com",
"aud": "client_id",
"exp": 1640000600,
"code": "AUTH_CODE",
"state": "xyz"
}
Response Modes:
JARM supports multiple response modes:
-
query.jwt - JWT in query parameter
response
GET /callback?response=<JWT>
-
fragment.jwt - JWT in fragment parameter
response
GET /callback#response=<JWT>
-
form_post.jwt - Auto-submitting HTML form with JWT
<form method="post" action="callback_url"> <input type="hidden" name="response" value="<JWT>" /> </form>
-
jwt - Direct JWT response (for JAR flows)
HTTP/1.1 200 OK Content-Type: application/jwt <JWT>
Per-Client Configuration:
Clients can request JARM during dynamic registration:
{
"redirect_uris": ["https://client.example.com/callback"],
"authorization_signed_response_alg": "RS256",
"authorization_encrypted_response_alg": "RSA-OAEP",
"authorization_encrypted_response_enc": "A256GCM"
}
Or configure it statically:
client = Authly::Client.new(
name: "Secure Client",
secret: "client_secret",
redirect_uri: "https://client.example.com/callback",
id: "client_123",
jarm_signing_alg: "RS256",
jarm_encryption_alg: "RSA-OAEP",
jarm_encryption_enc: "A256GCM"
)
Usage Example:
Client requests authorization with JARM:
GET /oauth/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=REDIRECT_URI&
response_mode=query.jwt&
state=STATE
Authorization server redirects with JARM response:
HTTP/1.1 302 Found
Location: https://client.example.com/callback?response=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Client decodes and validates the JWT:
# Extract JWT from response parameter
jwt = params["response"]
# Verify signature and decode
payload, header = JWT.decode(jwt, server_public_key, JWT::Algorithm::RS256,
iss: "https://auth.example.com",
aud: client_id
)
# Extract authorization code
authorization_code = payload["code"]
state = payload["state"]
Supported Algorithms:
Signing Algorithms (JWS):
- RS256, RS384, RS512 (RSA with SHA)
- ES256, ES384, ES512 (ECDSA with SHA)
- HS256, HS384, HS512 (HMAC with SHA)
- PS256, PS384, PS512 (RSA PSS with SHA)
Encryption Algorithms (JWE - Key Encryption):
- RSA-OAEP, RSA-OAEP-256
- ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW
Encryption Encodings (JWE - Content Encryption):
- A128GCM, A192GCM, A256GCM
- A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
Benefits:
- Response Integrity: Prevents tampering with authorization responses
- Response Authentication: Verifies the authorization server issued the response
- Confidentiality: Optional encryption protects sensitive response data
- Injection Prevention: Prevents authorization response injection attacks
- FAPI Compliance: Required for Financial-grade API Advanced security profile
Security Considerations:
- JARM JWTs should have short lifetimes (default: 10 minutes)
- Clients must validate the
iss
(issuer) andaud
(audience) claims - Clients must verify JWT signatures using the authorization server's public key
- Use RS256 or ES256 for production deployments (asymmetric algorithms)
- Enable encryption for highly sensitive authorization responses
Customization
Authly provides extension points for integrating with your application's authentication logic:
Custom Authorizable Client
Implement the AuthorizableClient
module to define client authentication and validation:
class CustomAuthorizableClient
include Authly::AuthorizableClient
def valid_redirect?(redirect_uri : String) : Bool
# Validate redirect URI against allowed URLs
# This should query your database or configuration
allowed_uris = [
"https://your-app.com/callback",
"https://your-app.com/oauth/callback"
]
allowed_uris.includes?(redirect_uri)
end
def authorized?(client_id : String, client_secret : String) : Bool
# Verify client credentials
# This should query your database or authentication service
client = Client.find_by_id(client_id)
client && client.secret == client_secret && client.active?
end
end
Custom Authorizable Owner
Implement the AuthorizableOwner
module to define resource owner authentication:
class CustomAuthorizableOwner
include Authly::AuthorizableOwner
def authorized?(username : String, password : String) : Bool
# Verify user credentials
# This should use your authentication system (bcrypt, argon2, etc.)
user = User.find_by_username(username)
user && user.verify_password(password) && user.active?
end
def id_token(user_data : Hash(String, String)) : String
# Generate an OpenID Connect ID Token
# Include standard claims like sub, name, email, etc.
user_id = user_data["user_id"]
user = User.find(user_id)
JWT.encode({
"sub" => user.id,
"name" => user.full_name,
"email" => user.email,
"email_verified" => user.email_verified?,
"iat" => Time.utc.to_unix,
"exp" => (Time.utc + 1.hour).to_unix
}, Authly.configuration.secret_key, Authly.configuration.algorithm)
end
end
Custom Token Store
For production deployments, implement a persistent token store using your preferred database:
class CustomTokenStore
include Authly::TokenStore
def store(token : String, data : Hash(String, String))
# Store token in database with expiration
Token.create(
token: token,
data: data.to_json,
expires_at: Time.utc + data["ttl"].to_i.seconds
)
end
def fetch(token : String) : Hash(String, String)?
# Fetch non-expired token from database
token_record = Token.find_by_token(token)
return nil unless token_record && !token_record.expired?
JSON.parse(token_record.data).as(Hash(String, String))
rescue
nil
end
def revoke(token : String)
# Mark token as revoked
Token.where(token: token).update_all(revoked: true)
end
def revoked?(token : String) : Bool
# Check if token has been revoked
token_record = Token.find_by_token(token)
token_record.nil? ? false : token_record.revoked?
end
def valid?(token : String) : Bool
# Verify token is not revoked and not expired
token_record = Token.find_by_token(token)
return false unless token_record
!token_record.revoked? && !token_record.expired?
end
end
Benefits of Custom Token Store:
- Persistence across server restarts
- Scalability for distributed systems
- Token audit trail and analytics
- Advanced token management features
Token Strategies
Authly supports two token strategies:
JWT Tokens (Recommended)
Advantages:
- Self-contained and stateless
- No database lookup required for validation
- Contains user information and metadata
- Industry-standard format
Configuration:
Authly.configure do |config|
config.token_strategy = :jwt
config.algorithm = JWT::Algorithm::HS256 # or RS256, ES256, etc.
config.secret_key = ENV["JWT_SECRET_KEY"]
end
Supported Algorithms:
- HS256, HS384, HS512 (HMAC with SHA)
- RS256, RS384, RS512 (RSA with SHA)
- ES256, ES384, ES512 (ECDSA with SHA)
Opaque Tokens
Advantages:
- Revocable without database lookups
- No sensitive data exposure
- Smaller token size
Configuration:
Authly.configure do |config|
config.token_strategy = :opaque
config.token_store = CustomTokenStore.new # Required for opaque tokens
end
Roadmap
Completed Features ✅
- OAuth2 Dynamic Client Registration (RFC 7591)
- OpenID Connect UserInfo endpoint
- OpenID Connect Discovery endpoint
- Certificate-bound access tokens (mTLS / RFC 8705)
- Enhanced PKCE implementation with S256 method enforcement
- Pushed Authorization Requests (PAR / RFC 9126)
- JWT Secured Authorization Response Mode (JARM / RFC 9101)
Planned Enhancements
- Rate limiting and throttling for authentication endpoints
- Session management capabilities
- Additional token storage backends (Redis, Memcached)
- JWKS (JSON Web Key Set) endpoint for key rotation
- Comprehensive integration examples
- Admin UI for client and token management
- Detailed logging and monitoring hooks
See the open issues for a list of proposed features and known issues.
Contributing
Contributions are what make the open-source community an amazing place to learn, inspire, and create. We welcome contributions of all kinds!
How to Contribute
- Fork the Project - Create your own fork of the repository
- Create a Feature Branch -
git checkout -b feature/amazing-feature
- Make Your Changes - Implement your feature or bug fix
- Write Tests - Ensure your changes are covered by tests
- Run the Test Suite -
crystal spec
to verify all tests pass - Run the Linter -
bin/ameba
to ensure code quality - Commit Your Changes -
git commit -m 'Add some amazing feature'
- Push to Your Branch -
git push origin feature/amazing-feature
- Open a Pull Request - Submit your PR with a clear description
Code Style
- Follow Crystal's official style guide
- Write clear, descriptive commit messages
- Add documentation for new features
- Include specs for all new functionality
Reporting Issues
Found a bug or have a feature request? Please open an issue on GitHub with:
- A clear, descriptive title
- Detailed description of the issue or feature
- Steps to reproduce (for bugs)
- Expected vs actual behavior
- Environment details (Crystal version, OS, etc.)
License
Distributed under the MIT License. See LICENSE
for more information.
Contact
Elias J. Perez
- GitHub: @eliasjpr
- Project Link: https://github.com/azutoolkit/authly
authly
- 30
- 1
- 1
- 1
- 3
- 4 days ago
- December 13, 2019
MIT License
Fri, 17 Oct 2025 03:12:15 GMT