This gem server now supports OAuth2 authentication alongside traditional API keys, providing enterprise-grade authentication with token introspection, scopes, and integration with external identity providers.
- Multiple Authentication Schemes: OAuth2 Bearer tokens, RubyGems API keys, HTTP Basic, and custom headers
- OAuth2 Token Introspection (RFC 7662): Validate tokens against authorization servers
- Provider Support: GitHub, GitLab, and custom OAuth2 providers
- Backwards Compatible: Existing API key authentication continues to work
- Security: WWW-Authenticate headers, proper error responses, timeout protection
Configure OAuth2 via environment variables:
# Required
export OAUTH2_PROVIDER_URL="https://github.com"
export OAUTH2_CLIENT_ID="your-client-id"
export OAUTH2_CLIENT_SECRET="your-client-secret"
# Optional - for token introspection (recommended)
export OAUTH2_INTROSPECTION_URL="https://github.com/oauth/token/introspect"
# Optional - custom endpoints (auto-detected for GitHub/GitLab)
export OAUTH2_TOKEN_URL="/oauth/token"
export OAUTH2_AUTHORIZE_URL="/oauth/authorize"- Create a GitHub OAuth App at https://github.com/settings/developers
- Set Authorization callback URL to
http://localhost:9292/oauth/callback(for development) - Copy the Client ID and generate a Client Secret
- Configure environment variables:
export OAUTH2_PROVIDER_URL="https://github.com"
export OAUTH2_CLIENT_ID="Iv1.abc123..."
export OAUTH2_CLIENT_SECRET="your-secret-here"- Create a GitLab application at https://gitlab.com/-/profile/applications
- Set Redirect URI and select scopes (api, read_user)
- Configure:
export OAUTH2_PROVIDER_URL="https://gitlab.com"
export OAUTH2_CLIENT_ID="your-application-id"
export OAUTH2_CLIENT_SECRET="your-secret"Push a gem using an OAuth2 access token:
# Get an OAuth2 access token from your provider
export GEM_HOST_API_KEY="your-oauth2-access-token"
gem push my-gem-1.0.0.gem --host http://localhost:9292Or use curl with Bearer authentication:
curl -X POST http://localhost:9292/api/v1/gems \
-H "Authorization: Bearer your-oauth2-access-token" \
-F "gem=@my-gem-1.0.0.gem"All existing authentication methods continue to work:
# RubyGems API key header
curl -X POST http://localhost:9292/api/v1/gems \
-H "X-Rubygems-Api-Key: your-api-key" \
-F "gem=@my-gem-1.0.0.gem"
# HTTP Basic authentication
curl -X POST http://localhost:9292/api/v1/gems \
-u "your-api-key:" \
-F "gem=@my-gem-1.0.0.gem"
# Custom API key header
curl -X POST http://localhost:9292/api/v1/gems \
-H "X-Api-Key: your-api-key" \
-F "gem=@my-gem-1.0.0.gem"The OAuth2 integration consists of three main components:
Manages OAuth2 client configuration and token validation:
- Creates and caches OAuth2::Client instances
- Validates tokens via introspection (RFC 7662)
- Falls back to provider-specific token info endpoints
- Auto-detects endpoints for GitHub and GitLab
Unified authentication handler that tries multiple schemes:
- OAuth2 Bearer tokens (validated against provider)
- RubyGems API keys
- HTTP Basic authentication
- Custom header-based authentication
Returns structured authentication results with user info, scopes, and token metadata.
The gem push and yank actions use the authenticator:
app/actions/gems/create.rb- Gem publishing with OAuth2 supportapp/actions/gems/yank.rb- Gem yanking with OAuth2 support
Check OAuth2 configuration and connectivity:
GET /admin/oauth2/statusResponse:
{
"enabled": true,
"provider_url": "https://github.com",
"client_id": "Iv1.abc123...",
"has_client_secret": true,
"introspection_enabled": false,
"introspection_url": null,
"client_configured": true
}- OAuth2 tokens are validated on every request via introspection or token info endpoints
- Invalid or expired tokens receive 401 Unauthorized responses
- Network timeouts prevent denial-of-service via slow token validation (10s timeout)
When OAuth2 is enabled, 401 responses include proper WWW-Authenticate headers:
WWW-Authenticate: Bearer realm="them-server"
This allows OAuth2-aware clients to automatically request new tokens.
Never commit OAUTH2_CLIENT_SECRET to version control. Use:
- Environment variables (12-factor app methodology)
- Secret management systems (AWS Secrets Manager, HashiCorp Vault)
- Encrypted deployment configurations
OAuth2 tokens include scopes that can be used for fine-grained authorization:
auth_result = Them::Server::Authenticator.authenticate(env)
if auth_result[:authenticated]
scopes = auth_result[:scopes]
if scopes.include?("write:packages")
# Allow gem push
end
endTest OAuth2 integration with VCR for deterministic HTTP recording:
RSpec.describe "OAuth2 authentication" do
it "validates tokens via introspection", :vcr do
ENV["OAUTH2_PROVIDER_URL"] = "https://oauth.example.com"
ENV["OAUTH2_CLIENT_ID"] = "test-client"
ENV["OAUTH2_CLIENT_SECRET"] = "test-secret"
result = Them::Server::Authenticator.authenticate({
"HTTP_AUTHORIZATION" => "Bearer valid-token"
})
expect(result[:authenticated]).to be true
expect(result[:scheme]).to eq(:oauth2)
end
endCheck the admin status endpoint:
curl http://localhost:9292/admin/oauth2/status | jqVerify configuration:
enabledshould betrueclient_configuredshould betrue- Check for any
errorfield
- Ensure the OAuth2 provider is reachable
- Check that
OAUTH2_PROVIDER_URLis correct - Verify the token hasn't expired
- Look for errors in server logs
Some providers don't support RFC 7662 introspection. The system automatically falls back to token info endpoints:
- GitHub:
/user - GitLab:
/api/v4/user - Custom:
/oauth/token/info
- RFC 6749 - OAuth 2.0 Authorization Framework
- RFC 6750 - OAuth 2.0 Bearer Token Usage
- RFC 7662 - OAuth 2.0 Token Introspection
- RFC 7617 - HTTP Basic Authentication
- RFC 8414 - OAuth 2.0 Authorization Server Metadata
OAuth2 is opt-in and backwards compatible:
- Keep existing API key authentication working
- Add OAuth2 configuration for new users
- Gradually migrate users to OAuth2 tokens
- Eventually deprecate API keys if desired
The gem server will accept both authentication methods simultaneously.