-
-
Notifications
You must be signed in to change notification settings - Fork 356
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(oauth): Add RFC7636 aka PKCE support. #244
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,11 @@ type AuthorizeRequest struct { | |
State string `json:"state" gorethink:"state"` | ||
HandledResponseTypes Arguments `json:"handledResponseTypes" gorethink:"handledResponseTypes"` | ||
|
||
// Optional code_challenge as described in rfc7636 | ||
CodeChallenge string `json:"code_challenge" gorethink:"code_challenge"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really think this can be covered by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I consider that CodeChallenge is like State that's why in coded it here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My first problem was the Session usage, due to the fact it's an interface and shared in openid and oauth2 handler (I fix missing implementation on first commit). I had to store code_challenge to compare it on the token retrieval step, maybe Request.Form is a better place if it is shareable like session. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the |
||
// Optional code_challenge_method as described in rfc7636 | ||
CodeChallengeMethod string `json:"code_challenge_method" gorethink:"code_challenge_method"` | ||
|
||
Request | ||
} | ||
|
||
|
@@ -62,6 +67,14 @@ func (d *AuthorizeRequest) GetState() string { | |
return d.State | ||
} | ||
|
||
func (d *AuthorizeRequest) GetCodeChallenge() string { | ||
return d.CodeChallenge | ||
} | ||
|
||
func (d *AuthorizeRequest) GetCodeChallengeMethod() string { | ||
return d.CodeChallengeMethod | ||
} | ||
|
||
func (d *AuthorizeRequest) GetRedirectURI() *url.URL { | ||
return d.RedirectURI | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,13 +15,12 @@ | |
package fosite | ||
|
||
import ( | ||
"net/http" | ||
"strings" | ||
|
||
"context" | ||
|
||
"fmt" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/ory/fosite/pkce" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
|
@@ -80,5 +79,41 @@ func (c *Fosite) NewAuthorizeRequest(ctx context.Context, r *http.Request) (Auth | |
|
||
// Remove empty items from arrays | ||
request.SetRequestedScopes(removeEmpty(strings.Split(r.Form.Get("scope"), " "))) | ||
|
||
// Proof Key for Code Exchange by OAuth Public Clients | ||
// https://tools.ietf.org/html/rfc7636 | ||
// | ||
// PKCE is only for code response type | ||
if request.ResponseTypes.Has("code") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same thing here, this shouldn't be a hardcoded thing but instead an add-in via handlers There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep I'm agree. Applying "Do it, do it right, to do better" philosophy ^^ |
||
if codeChallenge := r.Form.Get("code_challenge"); len(codeChallenge) == 0 { | ||
// Force code_challenge only for code response_type | ||
if c.RequirePKCEForPublicClients { | ||
// https://tools.ietf.org/html/rfc7636#section-4.4.1 | ||
return request, errors.WithStack(ErrCodeChallengeRequired) | ||
} | ||
} else { | ||
codeChallengeMethod := r.Form.Get("code_challenge_method") | ||
// https://tools.ietf.org/html/rfc7636#section-4.3, default to plain if not specified | ||
if len(codeChallengeMethod) == 0 { | ||
codeChallengeMethod = pkce.Plain | ||
} | ||
|
||
// Validate code challenge method value | ||
if pkce.GetVerifier(codeChallengeMethod) == nil { | ||
// https://tools.ietf.org/html/rfc7636#section-4.4.1 | ||
return request, errors.WithStack(ErrCodeChallengeMethodNotSupported) | ||
} | ||
|
||
// Validate code challenge syntax | ||
// https://tools.ietf.org/html/rfc7636#section-4.2 | ||
if !pkce.IsValid(codeChallenge) { | ||
return request, errors.WithStack(ErrInvalidCodeChallenge) | ||
} | ||
|
||
// Assign to request | ||
request.CodeChallenge = codeChallenge | ||
} | ||
} | ||
|
||
return request, 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 really think this should be a custom handler and not hardcoded 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.
Ok it's more like an OAuth2 extension. I'm not really aware of project code organization yet.
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.
Most of the code are (re)generated mocks ^^
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.
About PKCE settings (RequiredForPublicClient), maybe via a decorator (sort of middleware) in the authorize and token endpoints.
This could be helpful to add / process parameters before handling core OAuth operations.
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 idea of this framework is to have handlers such as this one for the auth endpoint which implement parts of the oauth2 spec. What you would do (probably) is some checks at the
/auth
endpoint and then some checks or updates to the response at the/token
endpoint