comrade
Version, currently 0.2.02 versions
- 0.2.0latestOct 30, 2025
- 0.1.0not indexedJan 17, 2026
github.com/ButterbaseApp/comrade
A Crystal OAuth library inspired by Laravel Socialite for simple, elegant OAuth authentication.
Installation
# Add this to your shard.yml
dependencies:
comrade:
github: ButterbaseApp/comrade
version: ~> 0.2.0Then run:
shards installshard.yml
- Crystal
>= 1.17.1- License
- MIT
- Author
- Chris Watson
Dependencies
Runtime Dependencies
- dotenv~> 1.0github: gdotdesign/cr-dotenv
Development Dependencies
- ameba~> 1.5github: crystal-ameba/amebadev
README
Comrade
A Crystal OAuth library inspired by Laravel Socialite for simple, elegant OAuth authentication with enterprise SSO support.
Table of Contents
Background
Comrade provides a simple, elegant interface for OAuth authentication with various providers like GitHub, Google, and more. Inspired by Laravel Socialite, it offers a fluent API for handling OAuth flows in Crystal applications with enterprise-ready SSO capabilities through WorkOS.
The library follows a singleton pattern with a centralized Manager for provider configuration and supports both confidential and public client flows, including PKCE for enhanced security. It seamlessly integrates with popular OAuth providers while also offering enterprise SSO through SAML and OIDC connections.
Install
Add the dependency to your shard.yml:
dependencies:
comrade:
github: watzon/comrade
version: ~> 0.1.0
Run shards install to install the dependency.
Usage
Basic Setup
require "comrade"
# Configure a provider from environment variables
Comrade.register_provider(
:github,
"GITHUB_CLIENT_ID",
"GITHUB_CLIENT_SECRET",
"http://localhost:3000/auth/github/callback",
["user:email"]
)
# Configure WorkOS for enterprise SSO
Comrade.register_provider(
:workos,
"WORKOS_CLIENT_ID",
"WORKOS_CLIENT_SECRET",
"http://localhost:3000/auth/workos/callback"
)
# Or configure globally
Comrade.configure do |config|
config.http_timeout = 30
end
Provider Configuration
Environment Variables
# Configure GitHub OAuth
Comrade.register_provider(
:github,
"GITHUB_CLIENT_ID",
"GITHUB_CLIENT_SECRET",
"http://localhost:3000/auth/github/callback",
["user:email"]
)
# Configure Google OAuth
Comrade.register_provider(
:google,
"GOOGLE_CLIENT_ID",
"GOOGLE_CLIENT_SECRET",
"http://localhost:3000/auth/google/callback",
["openid", "profile", "email"]
)
# Configure WorkOS enterprise SSO
Comrade.register_provider(
:workos,
"WORKOS_CLIENT_ID",
"WORKOS_CLIENT_SECRET",
"http://localhost:3000/auth/workos/callback"
)
Hash Configuration
config = {
"name" => "github",
"client_id" => "your-github-client-id",
"client_secret" => "your-github-client-secret",
"redirect_uri" => "http://localhost:3000/auth/github/callback",
"scopes" => ["user:email"]
}
Comrade.register_provider(:github, config)
OAuth Flow
1. Redirect User for Authorization
# Get provider instance
github = Comrade.driver(:github)
# Generate authorization URL with state parameter
auth_url = github.redirect(
scopes: ["user:email"],
state: "random-state-string"
)
# Redirect user to auth_url
redirect_to auth_url
2. Handle Callback and Get User
# After user authorizes, exchange code for token
code = params["code"]
state = params["state"]
# Get access token
token = github.get_token(code, state: state)
# Get user information
user = github.user(token)
puts "User: #{user.name} (#{user.email})"
puts "Avatar: #{user.avatar}"
3. Token Management
# Check if token is expired
if token.expired?
# Refresh token if available
if token.refreshable?
token = github.refresh_token(token.refresh_token.not_nil!)
end
end
# Revoke token (if supported by provider)
github.revoke_token(token.access_token)
Supported Providers
Provider Implementation Status
- [x] GitHub - OAuth 2.0 with email fetching, basic scopes
- [x] Google - OAuth 2.0 + OpenID Connect, PKCE support, token revocation
- [x] Facebook/Meta - OAuth 2.0, Graph API, permissions system
- [x] Twitter/X - OAuth 2.0, user profile and tweets access
- [x] Discord - OAuth 2.0, rich user/guild data
- [x] WorkOS - Enterprise SSO via SAML/OIDC, organization-based authentication
- [ ] Microsoft - OAuth 2.0 + OpenID Connect, Azure AD support
- [ ] Slack - OAuth 2.0, workspace integration
- [ ] LinkedIn - OAuth 2.0, professional profile data
- [ ] Apple - OAuth 2.0 + OpenID Connect, Sign in with Apple
- [ ] GitLab - OAuth 2.0, developer platform integration
- [ ] Bitbucket - OAuth 2.0, Atlassian ecosystem
- [ ] Spotify - OAuth 2.0, music and user data
- [ ] Strava - OAuth 2.0, fitness and activity data
GitHub
github = Comrade.driver(:github)
auth_url = github.redirect(scopes: ["user", "repo"])
user = github.user(token)
# GitHub automatically fetches primary email if not included
# Default scopes: ["user:email"]
google = Comrade.driver(:google)
auth_url = google.redirect(
scopes: ["openid", "profile", "email"],
# Google supports offline access for refresh tokens
code_verifier: google.generate_code_verifier # PKCE for public clients
)
user = google.user(token)
# Default scopes: ["openid", "profile", "email"]
# Supports token revocation
Facebook/Meta
facebook = Comrade.driver(:facebook)
auth_url = facebook.redirect(
scopes: ["email", "public_profile"],
state: "random-state-string"
)
user = facebook.user(token)
# Facebook uses Graph API for user data
# Default scopes: ["email", "public_profile"]
# Supports token revocation via permission removal
# Note: Requires Facebook App with OAuth configuration
Twitter/X
twitter = Comrade.driver(:twitter)
auth_url = twitter.redirect(
scopes: ["tweet.read", "users.read"],
state: "random-state-string"
)
user = twitter.user(token)
# Twitter/X uses OAuth 2.0 with v2 API
# Default scopes: ["tweet.read", "users.read"]
# Supports token revocation
# Note: Requires Twitter Developer App with OAuth 2.0 enabled
Discord
discord = Comrade.driver(:discord)
auth_url = discord.redirect(
scopes: ["identify", "email", "guilds"],
state: "random-state-string"
)
user = discord.user(token)
# Get user's guilds (servers)
guilds = discord.get_user_guilds(token)
# Discord uses OAuth 2.0 with rich user/guild data
# Default scopes: ["identify", "email"]
# Supports avatar URLs, guild information, and token revocation
# Note: Requires Discord Application with OAuth2 configured
WorkOS
WorkOS provides enterprise-ready SSO capabilities through SAML and OIDC connections, perfect for B2B applications.
# Configure WorkOS provider
Comrade.register_provider(
:workos,
"WORKOS_CLIENT_ID",
"WORKOS_CLIENT_SECRET",
"http://localhost:3000/auth/workos/callback"
)
# Connection-based SSO (recommended for specific organizations)
workos = Comrade.driver(:workos)
auth_url = workos.redirect(
connection: "conn_123456789", # Specific SSO connection
domain_hint: "acme.com", # Optional: help users select IdP
login_hint: "user@acme.com", # Optional: pre-fill email
state: "random-state-string"
)
# Organization-based SSO (for enterprise customers)
auth_url = workos.redirect(
organization: "org_123456789", # WorkOS organization
state: "random-state-string"
)
# Direct OAuth providers (via WorkOS)
auth_url = workos.redirect(
provider: "GoogleOAuth", # "GoogleOAuth", "MicrosoftOAuth", "GitHubOAuth", "AppleOAuth"
state: "random-state-string"
)
# Exchange authorization code for user
code = params["code"]
token = workos.get_token(code, state: state)
user = workos.user(token)
# Access enterprise-specific data
user.raw["organization_id"]?.try(&.as_s) # Organization ID
user.raw["connection_id"]?.try(&.as_s) # Connection ID
user.raw["connection_type"]?.try(&.as_s) # "SAML", "OIDC", etc.
user.raw["groups"]?.try(&.as_a) # User groups from IdP
# WorkOS enterprise features
# - No refresh tokens (tokens are long-lived)
# - Supports SAML and OIDC providers
# - Organization and connection management
# - Group-based access control
# - Token revocation support
# Note: Requires WorkOS account with SSO connections configured
API
Comrade Module
Comrade.driver(name : Symbol)- Get configured provider instanceComrade.configure(&block)- Configure global settingsComrade.register_provider(name, ...)- Register provider configurationComrade.provider_configured?(name)- Check if provider is configuredComrade.remove_provider(name)- Remove provider configuration
Provider Methods
All providers implement the following interface:
redirect(scopes, state, **options)- Generate authorization URLget_token(code, state, **options)- Exchange code for access tokenget_user(token)- Get user information using access tokenrefresh_token(refresh_token, **options)- Refresh access tokenrevoke_token(token)- Revoke access token (if supported)
User Object
user.id # String - User ID from provider
user.nickname # String? - Username/handle
user.name # String? - Full display name
user.email # String? - Email address
user.avatar # String? - Avatar URL
user.raw # JSON::Any - Raw provider response
# Helper methods
user.has_email? # Bool
user.has_name? # Bool
user.has_avatar? # Bool
user.display_name # String - name || nickname || id
user.to_h # Hash - User data as hash
user.get_raw_field("field") # Get raw field from provider response
Token Object
token.access_token # String - OAuth access token
token.refresh_token # String? - Refresh token (if available)
token.expires_in # Int32? - Token lifetime in seconds
token.scope # String? - Granted scopes
token.token_type # String? - Token type (usually "Bearer")
token.created_at # Time - Token creation time
# Helper methods
token.expired? # Bool
token.expiring_soon? # Bool (within 5 minutes by default)
token.expires_at # Time?
token.refreshable? # Bool
token.to_h # Hash - Token data as hash
Security
- State Parameter: Always use state parameters to prevent CSRF attacks
- PKCE Support: Use PKCE for public clients (mobile apps, SPAs)
- Token Storage: Store tokens securely, consider encryption
- HTTPS: Always use HTTPS in production for OAuth redirects
- Scope Limitation: Request only necessary scopes
- Token Expiration: Check token expiration before use
# Secure example with state and PKCE
provider = Comrade.driver(:google)
state = provider.generate_state
code_verifier = provider.generate_code_verifier
# Store state and code_verifier in session
session["oauth_state"] = state
session["code_verifier"] = code_verifier
auth_url = provider.redirect(
scopes: ["openid", "profile", "email"],
state: state,
code_verifier: code_verifier
)
# In callback, verify state first
if params["state"] != session["oauth_state"]
raise "Invalid state parameter"
end
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Development
- Clone the repository
- Create your feature branch (
git checkout -b my-new-feature) - Install dependencies (
shards install) - Run tests (
crystal spec) - Run linter (
ameba) - Commit your changes using Conventional Commits
- Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
Commit Messages
This project follows the Conventional Commits specification. Commit messages should be structured as follows:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Common types:
feat: A new featurefix: A bug fixdocs: Documentation only changesstyle: Code formatting changes (white-space, etc)refactor: Code change that neither fixes a bug nor adds a featuretest: Adding missing tests or correcting existing testsbuild: Changes that affect the build system or external dependenciesci: Changes to CI configuration files and scriptschore: Other changes that don't modify src or test files
Examples:
feat: add GitHub OAuth providerfix(auth): resolve token expiration issuedocs: update installation instructionsci: add multi-version Crystal testing
Running Tests
# Run all tests
crystal spec
# Run specific test file
crystal spec spec/comrade_spec.cr
# Run with coverage
crystal spec --coverage
Code Style
This project uses Ameba for static analysis. Run ameba to check code style before submitting pull requests.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
0.2.0- Tagged
- Oct 30, 2025
- Commit
3f5cf08f5dbf- Crystal
>= 1.17.1- Indexed
- yes
Dependents
No indexed shard depends on this one yet.
Repository
github.com/ButterbaseApp/comrade
Metadata
- Created
- Aug 12, 2026
- Updated
- Aug 14, 2026
- Synced
- Aug 14, 2026
- Versions
- 2