-
Notifications
You must be signed in to change notification settings - Fork 124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add token revocation and session management to AuthnOIDC #2674
Conversation
27675ec
to
b2110c7
Compare
|
||
render(json: { oidc_logout_uri: logout_uri }) | ||
end | ||
|
||
def authenticate_oidc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the original "OIDC" functionality which is really just a glorified JWT validation specific to OIDC. If you're going to hijack the POST
route, I'd recommend splitting this up a bit differently, something like:
def authenticate_oidc
# Parse JSON request body
# If refresh token is present, do refresh token workflow
# Else, do this:
params[:authenticator] = "authn-oidc"
input = Authentication::AuthnOidc::UpdateInputWithUsernameFromIdToken.new.(
authenticator_input: authenticator_input
)
# We don't audit success here as the authentication process is not done
rescue => e
# At this point authenticator_input.username is always empty (e.g. cucumber:user:USERNAME_MISSING)
log_audit_failure(
authn_params: authenticator_input,
audit_event_class: Audit::Event::Authn::Authenticate,
error: e
)
handle_authentication_error(e)
else
authenticate(input)
end
Authentication::AuthnOidc::V2::Strategies::AuthzCode.new( | ||
authenticator: authenticator | ||
).call(params.to_hash.symbolize_keys) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd actually recommend pushing the logic for choosing a strategy up into the Authentication::AuthnOidc::V2::Strategy
class. This will allow you to keep details of a particular strategy out of the controller (which ultimately should be super generic). Something like:
module Authentication
module AuthnOidc
module V2
class Strategy
def callback(args)
if args.key?(:refresh_token)
# run refresh token workflow
elsif(args.key?(:code)
# run code validation workflow
end
...
end
end
end
end
end
params.permit! | ||
|
||
if params[:code] || params[:refresh_token] | ||
namespace = 'Authentication::AuthnOidc::V2::Strategies' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be pushed up into the Strategy as described above to keep the OIDC abstraction from bubbling up.
end | ||
|
||
def end_session_uri(id_token:, state:, redirect_uri:) | ||
query = URI.encode_www_form( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
URI
should be injected to simplify testing.
@@ -135,7 +171,7 @@ def discovery_information(invalidate: false) | |||
skip_nil: true | |||
) do | |||
@discovery_configuration.discover!(@authenticator.provider_uri) | |||
rescue HTTPClient::ConnectTimeoutError, Errno::ETIMEDOUT => e | |||
rescue Faraday::TimeoutError, Errno::ETIMEDOUT => e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did the OpenID-Connect
gem move from HTTPClient to Faraday?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, only recently in v2.0.0+
@logger = logger | ||
end | ||
module Strategies | ||
module Common |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please NEVER use modules to inject methods into downstream objects, particularly the initializer. Modules make it nearly impossible to reason about where a classes methods come from.
Instead either use inheritance (probably the correct solution for this section), or create a generic, stateless object "Utility" object with common methods. Utility objects should follow adhere to functional programming best practices (stateless, no mutation, etc.).
- Instead of returning arrays, return hashes with OIDC tokens. Consumers can be ignorant of position in favor of querying the returned hash with descriptive keys. - Refactor public-facing functions to reduce repitition.
- Create `Strategies` module, home to classes defining the relationship between the generalized OIDC V2 strategy, and the underlying OIDC client. This new pattern will be easy to extend to support additional flows. - Instead of passing arrays of data between `Client`, `Strategy` and `AuthenticationHandler` classes, pass hashes with descriptive keys.
b2110c7
to
11927f5
Compare
Closing. Refresh token support was abandoned in favor of a configurable Conjur access token TTL - see #2683. |
Desired Outcome
If we plan on distributing long-lived OIDC refresh tokens, we need more token management features. We should at least include token revocation for dangling tokens, but should also look into session termination.
Implemented Changes
Authentication::AuthnOidc::V2::Strategy
class into::Strategies::AuthzCode
and::Strategies::RefreshToken
. This will hopefully make it easier to:openid_connect
gem and Keycloak docker-compose service useddev
andci
environments. Both were missing token revocation features..revoke
method, for invalidating a given refresh token.end_session
method, for revoking dangling tokens and returning a URI to the configured OIDC provider's session management API/authn-oidc/:account/:service_id/logout
, which employ a new Logout handler and strategy.Open Questions
Authentication::Handler::LogoutHandler
class. This was an attempt to give the AuthenticateController a path to objects initialized in theAuthenticationHandler
class without legitimately authenticating and issuing a token. Wondering if there is a better solution to be found here.Connected Issue/Story
Resolves #[relevant GitHub issue(s), e.g. 76]
CyberArk internal issue link: insert issue ID
Definition of Done
At least 1 todo must be completed in the sections below for the PR to be
merged.
Changelog
CHANGELOG update
Test coverage
changes, or
Documentation
README
s) were updated in this PRBehavior
Security