-
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 refresh_token logic and tests to OIDC API #2672
Conversation
5349d64
to
79eca30
Compare
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.
Looks good! Left some feedback points.
Scenario: A valid refresh token to get Conjur access token and OIDC refresh token | ||
Given I enable OIDC V2 refresh token flows for "keycloak2" | ||
And I fetch a code for username "alice" and password "alice" | ||
And I authenticate via OIDC V2 with code |
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 step still uses a get
request - if we force it to use post
, it might be a better test of our generalization. (Maybe this is a separate test case from the one highlighted here)
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.
Isn't this step emulating the redirect from the OIDC provider back to Conjur containing the code as a query param? My understanding is that this would always be a GET request but maybe I'm mistaken
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 don't think so. They're similar, but:
- The OIDC redirect doesn't contain all the information it needs - it needs to be supplemented with both
nonce
andcode_verifier
parameters. - Conjur is responding to the
code
with an access token - the requester should be the Conjur client.
In terms of the UI:
- The UI server redirects to the OIDC provider
- The OIDC provider redirects back to the UI server using a GET request with
code
andstate
query parameters - The UI server will evaluate the
state
, and send thecode
,nonce
, andcode_verifier
values to Conjur in a POST request - Conjur exchanges the
code
with the OIDC provider, and responds to the UI server with an access token
else | ||
# Update the input to have the username from the token and authenticate to Conjur | ||
input = Authentication::AuthnOidc::UpdateInputWithUsernameFromIdToken.new.( | ||
authenticator_input: authenticator_input | ||
) | ||
# We don't audit success here as the authentication process is not done | ||
end | ||
rescue => 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.
Given that the rescue-else
blocks only apply to the login in this else
, would it make sense to embed them? Especially given the log_audit_failure
references authenticator_input
.
else
begin
input = ...
rescue => e
...
else
authenticate(input)
end
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.
Although, we probably need to create audit logs in both cases. Maybe it's structured best as-is, and we just need to make sure we're generating accurate audit logs.
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.
The authenticator_input
obj seems to sufficiently cover the incoming request in both cases imo. The request body is logged in the credentials field which would reflect the refresh_token, authorization code, nonce, code_verifier, etc. that was part of the failing request.
# Re-use the same refresh token if rotation not enabled | ||
refresh_token = args[:refresh_token] if refresh_token.nil? |
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 wonder if this has potential security implications. Given that the client already has their valid refresh token, maybe we should avoid re-transmitting it on each authentication request.
it 'returns the role and refresh token when passed a refresh token' do | ||
expect(current_client).to receive(:get_token_with_refresh_token) | ||
.with(:refresh_token => 'token', :nonce => nil) |
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 should probably be an error case. Given this discussion, we might want to require a nonce
input, and choose to ignore it if that's what validation requires.
@@ -15,17 +15,27 @@ def initialize( | |||
|
|||
# Don't love this name... | |||
def callback(args) |
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 think part of the reason why the AuthenticationHandler
class was created was to see if it could be adopted as the new standard for all our authn schemes (see commit 1c458bb in this draft PR from @jvanderhoof).
Given that this Strategy
class could become a catch-all for what could be described as 3 distinct strategies, maybe we should create a class for each:
module Strategies
class Code
...
end
class RefreshToken
...
end
class IdToken
...
end
class Common
...
end
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.
Ack - realizing now that this is a separate task. Ignore for now!
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.
Thanks for the updates - LGTM 👍
Desired Outcome
Generalize Conjur's existing OIDC API to accept refresh tokens in the request body on POST requests to
/authn-oidc(/:service_id)/:account/authenticate
. This endpoint should accept form-encoded data representing an OIDC credential in the request body, and respond with a Conjur access token.Implemented Changes
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