This is a comprehensive collection of notes covering modern authentication and authorization protocols.
OAuth 2.0
The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf. - RFC-6749
Core RFC Documents
- RFC-6749 - Authorization Framework
- RFC-6750 - Bearer Token Usage
- RFC-8252 - OAuth 2.0 for Native Apps
- RFC-7636 - Proof Key for Code Exchange (PKCE)
OAuth 2.0 Roles
- Resource Owner: An entity capable of granting access to a protected resource (typically the end-user)
- Resource Server: The server hosting protected resources, capable of accepting and responding to protected resource requests using access tokens
- Client: An application making protected resource requests on behalf of the resource owner
- Authorization Server: The server issuing access tokens to the client after successfully authenticating the resource owner
Authorization Flows
Authorization Code Flow
The most secure flow for web applications. Here’s a production implementation from my OAuth2 server:
# Authorization endpoint - validates client and redirects
class Oauth::AuthorizationsController < ApplicationController
def show
redirect_to(error_url_for('unsupported_response_type')) && return unless %w[code token].include?(response_type)
redirect_to(error_url_for('invalid_client')) && return unless client&.active?
redirect_to(error_url_for('invalid_request')) && return unless client.redirect_uri_matches?(redirect_uri)
session[:oauth] = secure_params.to_h
end
def create
oauth = session[:oauth]&.with_indifferent_access
authorization = current_user.authorizations.create!(
client: client,
response_type: oauth[:response_type],
redirect_uri: oauth[:redirect_uri],
scope: oauth[:scope],
state: oauth[:state]
)
redirect_to authorization.redirect_url_for(client), allow_other_host: true
end
end
Token exchange endpoint:
# Token endpoint - exchanges authorization code for access token
def authorization_code_grant(code, code_verifier)
authorization = current_client.authorizations.active.find_by!(code: code)
return { error: 'invalid_grant' } unless authorization.valid_verifier?(code_verifier)
authorization.issue_tokens_to(current_client)
end
Client Credentials Flow
For server-to-server communication. Implementation from production:
# Simple but secure client credentials grant
def client_credentials_grant
current_client.issue_tokens_to(current_client)
end
# Client authentication using HTTP Basic Auth
def authenticate_client!
@current_client = authenticate_with_http_basic do |client_id, client_secret|
Client.find_by(client_id: client_id)&.authenticate(client_secret)
end
head :unauthorized unless @current_client
end
Token Exchange (RFC-8693)
Enables secure token exchange scenarios for complex distributed systems.
JWT Token Management
Production JWT token implementation from my OAuth2 server:
# app/models/token.rb
class Token < ApplicationRecord
enum token_type: { access: 0, refresh: 1 }
belongs_to :authorization
belongs_to :subject, polymorphic: true
belongs_to :audience, polymorphic: true
def revoke!
update!(revoked_at: Time.current)
Rails.cache.write("revoked:#{jti}", true, expires_in: expires_in)
end
def claims
{
iss: Rails.application.routes.url_helpers.root_url,
sub: subject.to_param,
aud: audience.entity_id,
exp: expires_at.to_i,
nbf: created_at.to_i,
iat: created_at.to_i,
jti: jti,
scope: authorization.scope
}
end
def to_jwt
JWT.encode(claims, Saml::Kit.configuration.private_keys.first, 'RS256')
end
def self.issue_tokens_to(subject, client)
authorization = Authorization.find_or_create_by(subject: subject, client: client)
tokens = {
access_token: authorization.tokens.create!(
token_type: :access,
subject: subject,
audience: client,
expires_at: 1.hour.from_now
),
refresh_token: authorization.tokens.create!(
token_type: :refresh,
subject: subject,
audience: client,
expires_at: 1.day.from_now
)
}
{
access_token: tokens[:access_token].to_jwt,
refresh_token: tokens[:refresh_token].to_jwt,
token_type: 'Bearer',
expires_in: 3600
}
end
def self.authenticate(token)
claims = JWT.decode(token, public_key, true, algorithm: 'RS256').first
return nil if revoked?(claims['jti'])
find(claims['jti'])
rescue JWT::DecodeError
nil
end
def self.revoked?(jti)
Rails.cache.exist?("revoked:#{jti}")
end
end
Multi-Factor Authentication (MFA)
TOTP-based MFA implementation using the rotp gem:
# app/models/mfa.rb
class Mfa < ApplicationRecord
belongs_to :user
def setup?
secret.present?
end
def build_secret
self.secret = ROTP::Base32.random
end
def provisioning_uri(email)
totp.provisioning_uri(email)
end
def disable!(current_password)
return false unless user.authenticate(current_password)
destroy!
end
def authenticate(code)
return false unless setup?
totp.verify(code, drift_behind: 15, drift_ahead: 15)
end
def valid_session?(session_token)
return true unless setup?
session_token.present? &&
authenticate(session_token) &&
!used_recently?(session_token)
end
private
def totp
@totp ||= ROTP::TOTP.new(secret, issuer: 'saml-kit')
end
def used_recently?(code)
# Prevent replay attacks by tracking used codes
Rails.cache.exist?("mfa:used:#{user.id}:#{code}")
end
end
OAuth2 Client Management
Production OAuth2 client registration and management:
# app/models/client.rb
class Client < ApplicationRecord
has_many :authorizations, dependent: :destroy
has_many :tokens, through: :authorizations
validates :client_name, presence: true
validates :redirect_uris, presence: true
def grant_types
%w[authorization_code refresh_token client_credentials]
end
def response_types
%w[code token]
end
def redirect_uri_matches?(uri)
redirect_uris.include?(uri)
end
def authenticate(client_secret)
ActiveSupport::SecurityUtils.secure_compare(
self.client_secret,
client_secret
)
end
def issue_tokens_to(subject)
Token.issue_tokens_to(subject, self)
end
def redirect_url_for(authorization)
case authorization.response_type
when 'code'
"#{authorization.redirect_uri}?code=#{authorization.code}&state=#{authorization.state}"
when 'token'
access_token = authorization.issue_tokens_to(self)
"#{authorization.redirect_uri}#access_token=#{access_token[:access_token]}&token_type=Bearer&expires_in=3600&state=#{authorization.state}"
end
end
end
Authorization Code and PKCE Implementation
Production PKCE (Proof Key for Code Exchange) validation:
# app/models/authorization.rb
class Authorization < ApplicationRecord
belongs_to :user
belongs_to :client
has_many :tokens, dependent: :destroy
scope :active, -> { where(revoked_at: nil).where('expires_at > ?', Time.current) }
scope :expired, -> { where('expires_at <= ?', Time.current) }
before_create :set_expiration
def valid_verifier?(code_verifier)
return true if code_challenge.blank?
case code_challenge_method
when 'S256'
expected = Base64.urlsafe_encode64(
Digest::SHA256.digest(code_verifier),
padding: false
)
ActiveSupport::SecurityUtils.secure_compare(code_challenge, expected)
when 'plain'
ActiveSupport::SecurityUtils.secure_compare(code_challenge, code_verifier)
else
false
end
end
def issue_tokens_to(client)
Token.issue_tokens_to(user, client)
end
def revoke!
update!(revoked_at: Time.current)
tokens.each(&:revoke!)
end
def redirect_url_for(client)
client.redirect_url_for(self)
end
private
def set_expiration
self.expires_at = 10.minutes.from_now
end
end
Security Considerations
- Always use HTTPS for all OAuth flows
- Implement PKCE for public clients
- Validate redirect URIs strictly
- Use short-lived access tokens with refresh tokens
- Implement proper token storage and handling
Complete Implementation
The examples above are from a complete, working OAuth2 and SAML authentication server. You can explore the full implementation:
GitHub Repository: https://github.com/xlgmokha/proof
Key files to examine:
- OAuth Controllers - Complete OAuth2 flow implementation
- Token Model - JWT token management
- Authorization Model - PKCE and authorization codes
- Client Model - OAuth2 client management
- MFA Model - Multi-factor authentication
- Sessions Controller - SAML SSO integration
Clone and run locally:
git clone https://github.com/xlgmokha/proof.git
cd proof
bundle install
rails db:setup
rails server
This provides a complete reference implementation of modern authentication protocols in Ruby on Rails, including OAuth2, SAML, JWT, and MFA.
SAML 2.0
The Security Assertion Markup Language is an XML-based protocol for completing authentication.
SAML Participants
Each SAML transaction includes at least 3 parties:
- Service Provider (SP): The application requesting authentication
- User Agent: The user’s browser or client
- Identity Provider (IDP): The authentication service
SAML Benefits
- Single Sign-On (SSO) across multiple applications
- Centralized identity management
- Standards-based approach
- Federation capabilities between organizations
SAML Flows
SP-Initiated Flow
@startuml
sp -> user_agent: AuthnRequest
user_agent -> idp: AuthnRequest
idp --> user_agent: Response
user_agent --> sp: Response
@enduml
- User accesses Service Provider
- SP generates SAML AuthnRequest
- SP redirects user to Identity Provider
- User authenticates with IDP
- IDP generates SAML Response
- IDP redirects user back to SP with Response
- SP validates Response and grants access
IDP-Initiated Flow
@startuml
idp -> user_agent: Response
user_agent -> sp: Response
@enduml
- User accesses Identity Provider directly
- User authenticates with IDP
- User selects target Service Provider
- IDP generates SAML Response
- IDP redirects user to SP with Response
- SP validates Response and grants access
SAML Metadata
SAML metadata describes the capabilities and configuration of SAML entities.
Service Provider Metadata
<?xml version="1.0"?>
<EntityDescriptor entityID="https://www.example.com/metadata" xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_9fd49a06-ee79-49a2-ba29-ddd4e3950bd2" >
<SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>x509 certificate</X509Certificate>
</X509Data>
</KeyInfo>
</KeyDescriptor>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.com/assertions" index="0" isDefault="true"/>
</SPSSODescriptor>
</EntityDescriptor>
Identity Provider Metadata
<?xml version="1.0"?>
<EntityDescriptor entityID="https://www.example.org/metadata" xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_afa7d243-a1af-44a9-9a9b-83ed35f537ba">
<IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>x509 certificate</X509Certificate>
</X509Data>
</KeyInfo>
</KeyDescriptor>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://www.example.org/session/new"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.org/session/new"/>
</IDPSSODescriptor>
</EntityDescriptor>
SAML Messages
AuthnRequest
<?xml version="1.0"?>
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_2ff118b4-a178-46ac-82b7-374ee6314e02" Version="2.0" IssueInstant="2019-02-23T18:10:03Z" Destination="https://www.example.org/session/new" AssertionConsumerServiceURL="https://www.example.com/assertions">
<saml:Issuer>https://www.example.com/metadata</saml:Issuer>
<samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
</samlp:AuthnRequest>
SAML Response
<?xml version="1.0"?>
<Response xmlns="urn:oasis:names:tc:SAML:2.0:protocol" ID="_8d623313-87ca-40a5-b299-694e8aaa5998" Version="2.0" IssueInstant="2019-02-23T18:10:25Z" Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" Destination="https://www.example.com/assertions" InResponseTo="_2ff118b4-a178-46ac-82b7-374ee6314e02">
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://www.example.org/metadata</Issuer>
<Status>
<StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</Status>
<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="_0d856f2f-fe06-4411-8bc8-163b17142e5c" IssueInstant="2019-02-23T18:10:25Z" Version="2.0">
<Issuer>https://www.example.org/metadata</Issuer>
<Subject>
<NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">22446ce2-710a-4a81-bad0-f0e3c56d1c46</NameID>
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<SubjectConfirmationData InResponseTo="_2ff118b4-a178-46ac-82b7-374ee6314e02" Recipient="https://www.example.com/assertions" NotOnOrAfter="2019-02-23T18:15:25Z"/>
</SubjectConfirmation>
</Subject>
<Conditions NotBefore="2019-02-23T18:10:25Z" NotOnOrAfter="2019-02-23T21:10:25Z">
<AudienceRestriction>
<Audience>https://www.example.com/metadata</Audience>
</AudienceRestriction>
</Conditions>
<AuthnStatement AuthnInstant="2019-02-23T18:10:25Z" SessionIndex="_0d856f2f-fe06-4411-8bc8-163b17142e5c">
<AuthnContext>
<AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef>
</AuthnContext>
</AuthnStatement>
</Assertion>
</Response>
SAML Bindings
- HTTP Redirect Binding: Parameters in URL query string
- HTTP POST Binding: Parameters in form POST body
- HTTP Artifact Binding: Reference to assertion stored at IDP
OpenID Connect
OpenID Connect Core 1.0 is an identity layer on top of OAuth 2.0.
OpenID Connect Flow
The OpenID Connect protocol, in abstract, follows these steps:
- The RP (Client) sends a request to the OpenID Provider (OP)
- The OP authenticates the End-User and obtains authorization
- The OP responds with an ID Token and usually an Access Token
- The RP can send a request with the Access Token to the UserInfo Endpoint
- The UserInfo Endpoint returns Claims about the End-User
ID Token Structure
The ID Token is a JWT containing claims about the authentication event:
{
"iss": "https://auth.example.com",
"sub": "248289761001",
"aud": "s6BhdRkqt3",
"nonce": "n-0S6_WzA2Mj",
"exp": 1311281970,
"iat": 1311280970,
"auth_time": 1311280969,
"acr": "urn:mace:incommon:iap:silver"
}
Standard Claims
sub: Subject identifiername: Full namegiven_name: Given namefamily_name: Surnameemail: Email addressemail_verified: Email verification statuspicture: Profile picture URL
Discovery and Registration
OpenID Connect supports dynamic discovery of OP configuration and dynamic client registration:
- Discovery:
/.well-known/openid_configuration - Registration: Dynamic client registration endpoint
- JWKs: JSON Web Key Set for token verification
Authentication Methods
- Authorization Code Flow: Most secure for web applications
- Implicit Flow: Deprecated, use Authorization Code with PKCE instead
- Hybrid Flow: Combination of code and implicit flows
Implementation Best Practices
Security Guidelines
- Always use HTTPS in production
- Validate all tokens before accepting them
- Implement proper session management
- Use secure storage for sensitive data
- Regular security audits and updates
Token Management
- Use short-lived access tokens (15-60 minutes)
- Implement refresh token rotation
- Secure token storage (httpOnly cookies, secure storage)
- Proper token revocation mechanisms
Error Handling
- Don’t leak sensitive information in error messages
- Implement proper logging for security events
- Use standard error codes and responses
- Graceful degradation for authentication failures
Performance Considerations
- Cache metadata and configuration
- Use connection pooling for HTTP requests
- Implement proper timeout handling
- Consider token caching strategies
Common Integration Patterns
API Gateway Integration
Centralize authentication and authorization at the API gateway level:
- Gateway validates tokens
- Gateway forwards authenticated requests
- Downstream services trust gateway
- Centralized policy enforcement
Microservices Authentication
Patterns for distributed authentication:
- Token Relay: Pass tokens between services
- Token Exchange: Convert tokens for different contexts
- Service Mesh: Centralized security at infrastructure level
- Sidecar Pattern: Authentication proxy per service
Mobile Application Integration
Considerations for mobile apps:
- Use Authorization Code flow with PKCE
- Implement secure token storage
- Handle network connectivity issues
- Consider offline authentication scenarios