Class: StandardId::Api::Oauth::IntrospectionsController

Inherits:
BaseController show all
Defined in:
app/controllers/standard_id/api/oauth/introspections_controller.rb

Overview

RFC 7662 OAuth 2.0 Token Introspection (POST /oauth/introspect).

Off by default. The endpoint is fully absent (404) unless StandardId.config.oauth.introspection_enabled is true, mirroring how dynamic_registration_enabled gates /oauth/register: an endpoint that answers questions about other people's tokens is not something to expose by accident.

Confidential clients only (RFC 7662 §2.1 requires the caller be authorized). Credentials arrive as HTTP Basic or in the form body, per RFC 6749 §2.3.1 — never both.

Every failure mode renders {"active": false} with no other members, per RFC 7662 §2.2. That includes bad client credentials, a blank token, a garbage token, a revoked token, and a tripped rate limit. The endpoint deliberately reveals nothing beyond that one bit.

What introspection can and cannot tell you here

Read this before building an authorization gate on it.

Access tokens are stateless: this engine never persists them and they carry no sid, so there is nothing to look up. A revoked session's access token therefore introspects as active: true until its exp. Introspection of an access token answers "was this minted by us, and is it unexpired?" — not "is it still honoured?". The only mitigation is a short access_token_lifetime; a caller who assumes otherwise will build a gate that keeps honouring revoked credentials for a full token lifetime.

Refresh tokens ARE persisted (as SHA256(jti)), so those are checked against the row AND against the parent session: a refresh token that is itself revoked or expired — or whose session is — introspects as inactive, correctly and immediately. That second half matters because the session can be revoked without the cascade ever running (a bare update!, a bulk update_all); see Oauth::RefreshTokenFlow and rarebit-one/rarebit-ops#297.

Constant Summary

Constants included from RateLimitHandling

RateLimitHandling::DEFAULT_RETRY_AFTER, RateLimitHandling::RATE_LIMIT_STORE

Instance Method Summary collapse

Methods included from RateLimitHandling

login_per_email, login_per_ip, resolve_login_alias

Methods included from ControllerPolicy

all_controllers, authenticated_controllers, public_controllers, register, registry_snapshot, reset_registry!

Instance Method Details

#createObject

POST /oauth/introspect



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'app/controllers/standard_id/api/oauth/introspections_controller.rb', line 83

def create
  return render_inactive if authenticate_client!.nil?

  token = params[:token].to_s
  return render_inactive if token.blank?

  payload = decode_token(token)
  return render_inactive if payload.nil?

  # Persisted refresh tokens are checkable; access tokens are not.
  #
  # eager_load the parent session in the same query: the answer must
  # match what Oauth::RefreshTokenFlow would do with the same token, and
  # that flow refuses a refresh whose session is revoked or expired
  # (rarebit-one/rarebit-ops#297). Reporting `active: true` here for a
  # token /oauth/token would refuse would make introspection an
  # authority that disagrees with the endpoint it describes.
  #
  # A nil session means no parent — the machine-to-machine shape — and
  # is not a reason to call the token inactive.
  if payload[:jti].present?
    persisted = StandardId::RefreshToken.eager_load(:session).find_by_jti(payload[:jti].to_s)
  end
  return render_inactive if persisted && !persisted.active?
  return render_inactive if persisted&.session && !persisted.session.active?

  render json: active_response(payload, persisted), status: :ok
end