Class: Doorkeeper::OAuth::ClientAuthentication::PrivateKeyJwt

Inherits:
Object
  • Object
show all
Defined in:
lib/doorkeeper/oauth/client_authentication/private_key_jwt.rb,
lib/doorkeeper/oauth/client_authentication/private_key_jwt/key_resolver.rb,
lib/doorkeeper/oauth/client_authentication/private_key_jwt/replay_guard.rb

Overview

"private_key_jwt" client authentication (RFC 7523 / OIDC Core §9): the client authenticates with a JWT assertion signed by its private key; the server verifies it against the client's published public keys (a jwks attribute on the application model, or keys fetched from its jwks_uri). No shared secret is involved.

The "jwt" gem is required only when an assertion is actually authenticated, so servers that don't enable this method don't need the dependency. Its constants are always referenced as ::JWT: doorkeeper-jwt defines Doorkeeper::JWT, which would otherwise shadow the gem everywhere inside this class.

Defined Under Namespace

Modules: KeyResolver Classes: ReplayGuard

Constant Summary collapse

CLIENT_ASSERTION_TYPE =
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
ALLOWED_ALGORITHMS =

Asymmetric signature algorithms only: HMAC family (and "none") are shared-secret/unauthenticated and must never verify an assertion.

%w[RS256 RS384 RS512 PS256 PS384 PS512 ES256 ES384 ES512].freeze
REQUIRED_CLAIMS =

iss/sub identify the client, aud prevents cross-server replay, exp bounds the assertion lifetime and jti makes it single-use (OIDC Core §9 requires all of these for private_key_jwt).

%w[iss sub aud exp jti].freeze
MAX_LIFETIME =

Upper bound on how far in the future an assertion may expire. This both rejects sloppily long-lived assertions and bounds the replay guard's memory.

3600

Class Method Summary collapse

Class Method Details

.authenticate(request) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/doorkeeper/oauth/client_authentication/private_key_jwt.rb', line 53

def self.authenticate(request)
  require_jwt!

  params = request.request_parameters.with_indifferent_access
  assertion = params[:client_assertion].to_s

  client_id = unverified_client_id(assertion)
  return if client_id.blank?
  # RFC 7521 §4.2: a client_id parameter sent alongside the assertion
  # must agree with the assertion's issuer.
  return if params[:client_id].present? && params[:client_id] != client_id

  application = OAuth::Client.find(client_id)&.application
  return unless application

  jwk_set = KeyResolver.jwk_set_for(application)
  return unless jwk_set

  claims = verified_claims(assertion, client_id, jwk_set, request)
  return unless claims
  return unless replay_guard.first_use?(
    "#{client_id}:#{claims["jti"]}",
    expires_at: claims["exp"].to_i,
  )

  Doorkeeper::ClientAuthentication::VerifiedCredentials.new(client_id)
end

.matches_request?(request) ⇒ Boolean

Returns:

  • (Boolean)


45
46
47
48
49
50
51
# File 'lib/doorkeeper/oauth/client_authentication/private_key_jwt.rb', line 45

def self.matches_request?(request)
  params = request.request_parameters.with_indifferent_access

  request.post? &&
    params[:client_assertion].present? &&
    params[:client_assertion_type] == CLIENT_ASSERTION_TYPE
end

.uses_shared_secret?Boolean

Assertions are verified against the client's published public keys; no shared secret is involved.

Returns:

  • (Boolean)


27
28
29
# File 'lib/doorkeeper/oauth/client_authentication/private_key_jwt.rb', line 27

def self.uses_shared_secret?
  false
end