-
-
Notifications
You must be signed in to change notification settings - Fork 364
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
feat: [#628] PAR implementation #660
Conversation
Just fyi, currently on vacation and not able to review this most likely in the next 2-3 weeks! |
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.
Thank you very much, I have done a preliminary review of the PR. I still need to read the spec very carefully to see what we can and can't set as part of PAR.
What would be immensely helpful is if you could add code comments to the sections that specify things like "this value MUST be set" or similar. That way, we always know what values are set and why according to the spec.
I'm also not 100% sure how far we need to go with validation here? Do we validate e.g. redirect URLs as part of PAR, or do we validate them when we actually execute the PAR request? Or what about response types? I saw that the PAR handler checks e.g. scope and audience, but it's missing (unless I overlooked it) further validation steps.
Finally, it's not quite clear to me how an exemplary HTTP handler would look like - maybe you could add such a handler to ory/fosite-example?
Thank you so much!
defaultPARKeyLength = 32 | ||
) | ||
|
||
var b64 = base64.URLEncoding.WithPadding(base64.NoPadding) |
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.
Is this up to us, or is it specified in a spec?
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 took a look at the spec and nothing specifically references the format of the reference value, it only references entropy requirements. Here is the specific section that discusses it (removed an irrelevant paragraph):
2.2. Successful Response:
If the verification is successful, the server MUST generate a request
URI and provide it in the response with a "201" HTTP status code.
The following parameters are included as top-level members in the
message body of the HTTP response using the "application/json" media
type as defined by [RFC8259].request_uri
The request URI corresponding to the authorization request posted.
This URI is a single-use reference to the respective request data
in the subsequent authorization request. The way the
authorization process obtains the authorization request data is at
the discretion of the authorization server and is out of scope of
this specification. There is no need to make the authorization
request data available to other parties via this URI.The format of the "request_uri" value is at the discretion of the
authorization server, but it MUST contain some part generated using a
cryptographically strong pseudorandom algorithm such that it is
computationally infeasible to predict or guess a valid value (see
Section 10.10 of [RFC6749] for specifics). The authorization server
MAY construct the "request_uri" value using the form
"urn:ietf:params:oauth:request_uri:<reference-value>" with
"<reference-value>" as the random part of the URI that references the
respective authorization request data.
10.10. Credentials-Guessing Attacks:
The authorization server MUST prevent attackers from guessing access
tokens, authorization codes, refresh tokens, resource owner
passwords, and client credentials.The probability of an attacker guessing generated tokens (and other
credentials not intended for handling by end-users) MUST be less than
or equal to 2^(-128) and SHOULD be less than or equal to 2^(-160).The authorization server MUST utilize other means to protect
credentials intended for end-user usage.
Thus the use of github.com/ory/fosite/token/hmac.RandomBytes(32) is correct and exceeds the entropy requirements at 2^(-256). Though I suspect you're asking about the encoding.
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.
It's just a state type value. I don't really see a need to make this configurable as long as it meets the spec requirements, which it does.
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 agree, what I wanted to know also was whether the vocabulary of B64 without padding (I think it's [a-zA-Z0-9+/]+
) matches the one defined in the RFC
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 charset isn't dictated. It just requires the entropy for which I use the random number generator as @james-d-elliott indicated.
handler/par/flow_pushed_authorize.go
Outdated
func (c *PushedAuthorizeHandler) HandlePushedAuthorizeEndpointRequest(ctx context.Context, ar fosite.AuthorizeRequester, resp fosite.PushedAuthorizeResponder) error { | ||
storage, ok := c.Storage.(fosite.PARStorage) | ||
if !ok { | ||
return errorsx.WithStack(fosite.ErrServerError.WithHint("Invalid storage type")) |
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.
Could we return a better error here - such as OAuth2 storage provider does not support Pushed Authorization Requests
?
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, we can do that. The expectation would be to never hit this state anyway. This would be a major misconfiguration, where the service is writing a HTTP handler calling into the PAR handlers (NewPushAuthorizationRequest etc.) without actually implementing the storage interface. This was really more of a safety net.
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.
Makes sense! Good error messages can help tremendously when debugging, so even if this a coding error I think it just gives that extra 1% better developer experience :)
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.
Done
handler/par/flow_pushed_authorize.go
Outdated
return nil | ||
} | ||
|
||
func (c *PushedAuthorizeHandler) secureChecker() func(*url.URL) bool { |
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 would feel more comfortable with a signature of secureChecker(*url.URL) bool
to avoid mistakes where the inner function is not properly being invoked because the caller messed up.
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.
Ok let me take a look. I think I copied this from somewhere else in Fosite.
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.
Done
authorize_request_handler.go
Outdated
// Reject the request if the "request_uri" authorization request | ||
// parameter is provided. | ||
if requestURI, _ := claims["request_uri"].(string); isPARRequest && requestURI != "" { | ||
return errorsx.WithStack(ErrInvalidRequestObject.WithHint("The request must not contain 'request_uri'.")) |
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.
Clarify the error message a bit:
Pushed Authorization Requests can not contain the 'request_uri' parameter.
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.
Ok, I can make this change.
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.
Done
request.RedirectURI = parRequest.GetRedirectURI() | ||
request.ResponseTypes = parRequest.GetResponseTypes() | ||
request.State = parRequest.GetState() | ||
request.ResponseMode = parRequest.GetResponseMode() |
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.
Could you point to where these values are also set so that we ensure we did not forget setting some important ones? Could you please also point to the RFC section (and maybe add them as a comment) that state which parameters can be set as part of PAR?
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.
A client sends the parameters that comprise an authorization request directly to the PAR endpoint. A typical parameter set might include: client_id, response_type, redirect_uri, scope, state, code_challenge, and code_challenge_method as shown in the example below. However, the pushed authorization request can be composed of any of the parameters applicable for use at the authorization endpoint, including those defined in [[RFC6749](https://www.rfc-editor.org/rfc/rfc9126.html#RFC6749)] as well as all applicable extensions. The request_uri authorization request parameter is one exception, and it MUST NOT be provided.
These are set in the call to NewAuthorizeRequest from within the NewPushedAuthorizationRequest.
authorize_request_handler.go
Outdated
|
||
// validate the clients match | ||
if clientID != request.GetClient().GetID() { | ||
return false, errorsx.WithStack(ErrInvalidRequest.WithHint("The 'client_id' must match the pushed authorization request.")) |
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 'client_id' must match the one sent in the pushed authorization request.
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.
Done
authorize_request_handler.go
Outdated
// No need to continue | ||
return request, nil | ||
} else if f.EnforcePushedAuthorization { | ||
return request, errorsx.WithStack(ErrInvalidRequest.WithHint("Pushed authorization request is enforced.")) |
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.
Pushed Authorization Requests are enforced but no such request was sent.
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.
Done
@vivshankar just wanted to check in and see if you still have appetite to get this merged :) |
@aeneasr Yes. Just back from travel. I will look at this late next week. I responded to some of your comments. If you are happy with the ones where I have answered the question, perhaps you could mark those resolved. Please leave the ones that need changes from me unresolved, so I remember to fix those. |
Thank you for the update @vivshankar - glad to have you back! I will look at the comments as soon as possible |
@aeneasr I have addressed the review comments (guess it didn't need to wait till next week :-) ) Here is an example of how a HTTP handler could be written. This is similar or same as the authorize HTTP handler func.
|
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.
Thank you! Sorry for the long review times :(
Can you please add an integration tests for this feature? Since we're adding a completely new flow and endpoint it is required to ensure the code works as intended.
You can find an example here: https://github.com/ory/fosite/blob/master/integration/authorize_code_grant_test.go
And also add an example to: github.com/ory/fosite-example :) |
@aeneasr Not entirely clear what's failing with the linter. I have completed the changes as requested. I will add to fosite-example in a couple of days. |
The URL got defunked, I'll fix it on master quickly |
0d0fdcd
to
7e7319a
Compare
@aeneasr Will you be able to review the latest changes and we can close this item? I am happy to contribute the new config provider for PAR as well. |
@aeneasr I have now made the changes based on the |
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.
Thank you! I have some comments left but overall this looks quite good!
clientID := r.Form.Get("client_id") | ||
|
||
storage, ok := f.Store.(PARStorage) | ||
if !ok { |
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.
We check on multiple occasions that the config or store implement PAR. We could use a single error var PARNotImplemented = ...
and reuse that error instead of writing new errors for every check
Hello everyone! Thanks for this PR and bringing this topic. This is exactly what I was needing. I am using Ory Hydra (which is great btw, thanks for the hard work!) and I was missing the PAR notion. Hope we will all be able to test it soon on our implementation of Hydra. |
I have another question regarding this implementation.
The latest one would be a perfect fit for the PAR integration, while the 1st one would be a quick solution to manage custom parameters as secured as possible within financial sector. My question is: could we join forces to proactively work on integrating those features from IETF ? I know the second one is in draft status, but we could already maybe analyse how it would fit into fosite ? Thanks for your feedback :) |
@mchristophe I can take a crack because we seem to be on the same journey.
|
@vivshankar : Question to finally clear my mind on that. When something is possible on Fosite (the JAR in this matter), does it mean that it is available on Hydra as well ? I know Hydra is an implementation of Fosite, so I guess the answer is yes. But the question popped up in my head while reading the discussion here. |
@mchristophe I expect so but since I don't work in ory or on Hydra, I will defer to @aeneasr to answer that. |
Thank you @vivshankar - great answer!
Most of the times fixes land automatically in Ory Hydra. Here this is however not the case, as it requires implementation of additional endpoints and business logic. That in turn requires e2e tests, API specs, docs, ... From maintainer side, this topic is far back in the list of priorities for Ory Hydra to support as we're focusing on v2 and releasing ory hydra to ory cloud |
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.
Thank you for your perseverance! This was not an easy change to get merged and with other priorities coming in between I was not able to be as responsive as I wish I could have been. That's the price once you go professional, you don't have as much time any more to be on GitHub :/
A job well done - looking forward to see this in prod!
Hello @aeneasr , thanks for your job! Indeed a very nice feature nicely handled. I did not understand your answer to my last question: does this mean PAR will be part of the next version of Hydra ? Sorry for asking this again, but I want to clear that out :). Thanks |
Hello, we are using Hydra version 2.2.0, but no PAR flow there, which version will support PAR flow? |
This PR implements RFC9126 - Pushed Authorization Request.
It introduces the following:
PushedAuthorizeHandler
that complies with thePushedAuthorizeEndpointHandler
interfaceRelated Issue or Design Document
#628 covers the details of the feature
Checklist
and signed the CLA.
introduces a new feature.
vulnerability. If this pull request addresses a security vulnerability, I
confirm that I got green light (please contact
[email protected]) from the maintainers to push
the changes.
works.
appropriate).
Further comments
N/A