Class: StandardId::Api::Oauth::IntrospectionsController
- Inherits:
-
BaseController
- Object
- ActionController::API
- BaseController
- BaseController
- StandardId::Api::Oauth::IntrospectionsController
- 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
-
#create ⇒ Object
POST /oauth/introspect.
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
#create ⇒ Object
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 |