Class: PetstoreApiClient::Authentication::OAuth2

Inherits:
Base
  • Object
show all
Defined in:
lib/petstore_api_client/authentication/oauth2.rb

Overview

OAuth2 authentication strategy for Petstore API

Implements OAuth2 Client Credentials flow for server-to-server authentication. Automatically handles token fetching, caching, and refresh on expiration.

This implementation follows the same security best practices as the ApiKey strategy:

  • HTTPS enforcement with warnings for insecure connections

  • Token masking in logs and debug output

  • Environment variable support for secure credential storage

  • Thread-safe token management

Examples:

Basic usage with explicit credentials

auth = OAuth2.new(
  client_id: "my-client-id",
  client_secret: "my-secret",
  token_url: "https://petstore.swagger.io/oauth/token"
)
auth.configured? # => true

Loading from environment variables

ENV['PETSTORE_OAUTH2_CLIENT_ID'] = 'my-client-id'
ENV['PETSTORE_OAUTH2_CLIENT_SECRET'] = 'my-secret'
ENV['PETSTORE_OAUTH2_TOKEN_URL'] = 'https://petstore.swagger.io/oauth/token'
auth = OAuth2.from_env

With custom scope

auth = OAuth2.new(
  client_id: "my-client-id",
  client_secret: "my-secret",
  token_url: "https://petstore.swagger.io/oauth/token",
  scope: "read:pets write:pets"
)

See Also:

Since:

  • 0.2.0

Constant Summary collapse

DEFAULT_TOKEN_URL =

Default token URL for Petstore API

Since:

  • 0.2.0

"https://petstore.swagger.io/oauth/token"
DEFAULT_SCOPE =

Default OAuth2 scope for Petstore API (supports both read and write)

Since:

  • 0.2.0

"read:pets write:pets"
ENV_CLIENT_ID =

Environment variable names for OAuth2 credentials

Since:

  • 0.2.0

"PETSTORE_OAUTH2_CLIENT_ID"
ENV_CLIENT_SECRET =

Since:

  • 0.2.0

"PETSTORE_OAUTH2_CLIENT_SECRET"
ENV_TOKEN_URL =

Since:

  • 0.2.0

"PETSTORE_OAUTH2_TOKEN_URL"
ENV_SCOPE =

Since:

  • 0.2.0

"PETSTORE_OAUTH2_SCOPE"
TOKEN_REFRESH_BUFFER =

Minimum seconds before expiration to trigger token refresh If token expires in less than this time, fetch a new one

Since:

  • 0.2.0

60
MIN_ID_LENGTH =

Minimum length requirements for credentials (security validation)

Since:

  • 0.2.0

3
MIN_SECRET_LENGTH =

Since:

  • 0.2.0

3

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#type

Constructor Details

#initialize(client_id: nil, client_secret: nil, token_url: DEFAULT_TOKEN_URL, scope: nil) ⇒ OAuth2

Initialize OAuth2 authenticator with client credentials

rubocop:disable Lint/MissingSuper

Examples:

auth = OAuth2.new(
  client_id: "my-app",
  client_secret: "secret123",
  token_url: "https://api.example.com/oauth/token",
  scope: "read write"
)

Parameters:

  • client_id (String, nil) (defaults to: nil)

    OAuth2 client ID

  • client_secret (String, nil) (defaults to: nil)

    OAuth2 client secret

  • token_url (String) (defaults to: DEFAULT_TOKEN_URL)

    OAuth2 token endpoint URL

  • scope (String, nil) (defaults to: nil)

    OAuth2 scope (space-separated permissions)

Raises:

Since:

  • 0.2.0



99
100
101
102
103
104
105
106
107
108
# File 'lib/petstore_api_client/authentication/oauth2.rb', line 99

def initialize(client_id: nil, client_secret: nil, token_url: DEFAULT_TOKEN_URL, scope: nil)
  @client_id = client_id&.to_s&.strip
  @client_secret = client_secret&.to_s&.strip
  @token_url = token_url || DEFAULT_TOKEN_URL
  @scope = scope&.to_s&.strip
  @access_token = nil
  @token_mutex = Mutex.new # Thread-safe token access

  validate! if configured?
end

Instance Attribute Details

#client_idObject (readonly)

Since:

  • 0.2.0



67
68
69
# File 'lib/petstore_api_client/authentication/oauth2.rb', line 67

def client_id
  @client_id
end

#client_secretObject (readonly)

Since:

  • 0.2.0



71
72
73
# File 'lib/petstore_api_client/authentication/oauth2.rb', line 71

def client_secret
  @client_secret
end

#scopeObject (readonly)

Since:

  • 0.2.0



79
80
81
# File 'lib/petstore_api_client/authentication/oauth2.rb', line 79

def scope
  @scope
end

#token_urlObject (readonly)

Since:

  • 0.2.0



75
76
77
# File 'lib/petstore_api_client/authentication/oauth2.rb', line 75

def token_url
  @token_url
end

Class Method Details

.from_envOAuth2

Create OAuth2 authenticator from environment variables

Loads credentials from:

  • PETSTORE_OAUTH2_CLIENT_ID

  • PETSTORE_OAUTH2_CLIENT_SECRET

  • PETSTORE_OAUTH2_TOKEN_URL (optional, defaults to Petstore API)

  • PETSTORE_OAUTH2_SCOPE (optional)

Examples:

ENV['PETSTORE_OAUTH2_CLIENT_ID'] = 'my-id'
ENV['PETSTORE_OAUTH2_CLIENT_SECRET'] = 'my-secret'
auth = OAuth2.from_env
auth.configured? # => true

Returns:

  • (OAuth2)

    New authenticator instance

Since:

  • 0.2.0



127
128
129
130
131
132
133
134
# File 'lib/petstore_api_client/authentication/oauth2.rb', line 127

def self.from_env
  new(
    client_id: ENV.fetch(ENV_CLIENT_ID, nil),
    client_secret: ENV.fetch(ENV_CLIENT_SECRET, nil),
    token_url: ENV.fetch(ENV_TOKEN_URL, DEFAULT_TOKEN_URL),
    scope: ENV.fetch(ENV_SCOPE, nil)
  )
end

Instance Method Details

#apply(env) ⇒ void

This method returns an undefined value.

Apply OAuth2 authentication to Faraday request

Adds Authorization header with Bearer token. Automatically fetches or refreshes token if needed.

Examples:

auth = OAuth2.new(client_id: "id", client_secret: "secret")
auth.apply(faraday_env)
# Request now has: Authorization: Bearer <access_token>

Parameters:

  • env (Faraday::Env)

    The Faraday request environment

Raises:

Since:

  • 0.2.0



151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/petstore_api_client/authentication/oauth2.rb', line 151

def apply(env)
  return unless configured?

  # Warn if sending credentials over insecure connection
  warn_if_insecure!(env)

  # Ensure we have a valid token
  ensure_valid_token!

  # Add Authorization header with Bearer token
  env.request_headers["Authorization"] = "Bearer #{@access_token.token}"
end

#configured?Boolean

Check if OAuth2 credentials are configured

Examples:

auth = OAuth2.new(client_id: "id", client_secret: "secret")
auth.configured? # => true

auth = OAuth2.new
auth.configured? # => false

Returns:

  • (Boolean)

    true if client_id and client_secret are present

Since:

  • 0.2.0



175
176
177
178
# File 'lib/petstore_api_client/authentication/oauth2.rb', line 175

def configured?
  !@client_id.nil? && !@client_id.empty? &&
    !@client_secret.nil? && !@client_secret.empty?
end

#fetch_token!OAuth2::AccessToken

Fetch a new access token from OAuth2 server

Uses Client Credentials flow to obtain an access token. Token is cached and reused until expiration.

Examples:

auth = OAuth2.new(client_id: "id", client_secret: "secret")
token = auth.fetch_token!
token.token # => "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Returns:

  • (OAuth2::AccessToken)

    The access token object

Raises:

Since:

  • 0.2.0



194
195
196
197
198
199
200
201
202
# File 'lib/petstore_api_client/authentication/oauth2.rb', line 194

def fetch_token!
  @token_mutex.synchronize do
    client = build_oauth2_client
    @access_token = client.client_credentials.get_token(scope: @scope)
  rescue ::OAuth2::Error => e
    raise AuthenticationError,
          "OAuth2 token fetch failed: #{e.message}"
  end
end

#inspectString Also known as: to_s

String representation (masks client secret for security)

Examples:

auth = OAuth2.new(client_id: "my-app", client_secret: "secret123")
auth.inspect # => "#<OAuth2 client_id=my-app secret=sec*******>"

Returns:

  • (String)

    Masked representation of OAuth2 config

Since:

  • 0.2.0



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/petstore_api_client/authentication/oauth2.rb', line 234

def inspect
  return unconfigured_inspect unless configured?

  # Use base class method to mask credentials
  masked_secret = mask_credential(@client_secret, 3)

  token_status = if @access_token.nil?
                   "no token"
                 elsif token_expired?
                   "token expired"
                 else
                   "token valid"
                 end

  "#<#{self.class.name} client_id=#{@client_id} secret=#{masked_secret} (#{token_status})>"
end

#token_expired?Boolean

Check if current access token is expired or will expire soon

Returns true if token is nil, already expired, or expires within TOKEN_REFRESH_BUFFER seconds.

Examples:

auth.token_expired? # => true (no token yet)
auth.fetch_token!
auth.token_expired? # => false (fresh token)

Returns:

  • (Boolean)

    true if token needs refresh

Since:

  • 0.2.0



216
217
218
219
220
221
222
223
224
# File 'lib/petstore_api_client/authentication/oauth2.rb', line 216

def token_expired?
  return true if @access_token.nil?
  return true if @access_token.expired?

  # Refresh if expiring soon (within buffer window)
  return false if @access_token.expires_at.nil?

  Time.now.to_i >= (@access_token.expires_at - TOKEN_REFRESH_BUFFER)
end