golang/oauth2

Feature request: Add Support for RFC 9126: Pushed Authorization Requests (PAR)

theadell opened this issue · 3 comments

I would like to propose adding support for the Pushed Authorization Requests (PAR)

What version of Go are you using?
go version go1.20 linux/amd64

What operating system and processor architecture are you using?
Ubuntu 20.04 amd64

What did you do?
Currently, the golang/oauth2 package does not support the Pushed Authorization Requests (PAR) feature.

What did you expect to see?
I expected to see support for the PAR feature as specified by the OAuth working group in RFC 9126.

What did you see instead?
There is no support for the PAR feature in the golang.org/x/oauth2 package.

Adding support for PAR is advantageous as it improves security by sending the authorization request via the back channel (HTTPS) rather than the front channel (browser redirects). This minimizes the exposure of sensitive data and reduces the possibility of Man-In-The-Middle (MITM) attacks.

Moreover, with PAR, the authorization request may be authenticated, meaning that only legitimate clients can initiate the OAuth flow. This strengthens security by ensuring that only clients with the appropriate credentials can make authorization requests.

This feature is also particularly useful when used in conjunction with JWT Secured Authorization Request (RFC 9101), making the handling of more complex requests more efficient.

To implement RFC 9126 I propose extending the Endpoint struct to include an AuthPARURL field:

type Endpoint struct {
	AuthURL    string
	TokenURL   string
	AuthPARURL string // Optional URL for the Pushed Authorization Request endpoint

	// AuthStyle optionally specifies how the endpoint wants the
	// client ID & client secret sent. The zero value means to auto-detect.
	AuthStyle  AuthStyle
}

This additional field allows users to specify the PAR endpoint. Then we can add a new method RequestPAR to the Config type

func (c *Config) RequestPAR(ctx context.Context, opts ...AuthCodeOption) (string, error) {}

This RequestPAR method would be responsible for creating a Pushed Authorization Request (PAR) by sending an HTTP POST request to the authorization server with the necessary parameters.

POST /as/par HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic ZXhhbXBsZTpzZWNyZXQ=

response_type=code&state=af0ifjsldkj&client_id=s6BhdRkqt3
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
&code_challenge=K2-ltc83acc4h0c9w6ESC_rEMTJ3bww-uCHaoeK1t8U
&code_challenge_method=S256&scope=account-information
&client_assertion_type=
  urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJraWQiOiJrMmJkYyIsImFsZyI6IlJTMjU2In0.eyJpc3Mi
  OiJzNkJoZFJrcXQzIiwic3ViIjoiczZCaGRSa3F0MyIsImF1ZCI6Imh0dHBzOi8vc
  2VydmVyLmV4YW1wbGUuY29tIiwiZXhwIjoxNjI1ODY5Njc3fQ.te4IdnP_DK4hWrh
  TWA6fyhy3fxlAQZAhfA4lmzRdpoP5uZb-E90R5YxzN1YDA8mnVdpgj_Bx1lG5r6se
  f5TlckApA3hahhC804dcqlE4naEmLISmN1pds2WxTMOUzZY8aKKSDzNTDqhyTgE-K
  dTb3RafRj7tdZb09zWs7c_moOvfVcQIoy5zz1BvLQKW1Y8JsYvdpu2AvpxRPbcP8W
  yeW9B6PL6_fy3pXYKG3e-qUcvPa9kan-mo9EoSgt-YTDQjK1nZMdXIqTluK9caVJE
  RWW0fD1Y11_tlOcJn-ya7v7d8YmFyJpkhZfm8x1FoeH0djEicXTixEkdRuzsgUCm6
  GQ

It would return the request_uri received from the server (or an error if the request fails), which must be used in the subsequent redirect to the authorization endpoint.

{
  "request_uri": "urn:ietf:params:oauth:request_uri:fd0e7748-2399-4309-88f2-560b98bf611f",
  "expires_in": 60
}

the request_uri can then be utilized in AuthCodeURL like so:

requestURI, err := conf.RequestPAR(ctx, oauth2.SetAuthURLParam("key", "value"))
// handle error
url := conf.AuthCodeURL(
    state,
    oauth2.SetAuthURLParam("request_uri", requestURI),
    oauth2.SetAuthURLParam("code_challenge", codeChallenge),
    oauth2.SetAuthURLParam("code_challenge_method", "S256"),
    // add any other necessary parameters
)

This modification doesn't change the method signatures of the existing API, ensuring backward compatibility.

As per the Contribution Guidelines, should there be consensus on this proposal and it gets approved, I am ready to submit a PR to add this feature.

I look forward to hearing your thoughts. Thanks!

Looking at this myself there is at least one gotcha that may need to be accounted for.

The AuthURL field may contain authorization endpoint query parameters which may need to be included in the pushed authorization request but excluded from the subsequent authorization request. This is clear from the following snippet:

	if strings.Contains(c.Endpoint.AuthURL, "?") {
		buf.WriteByte('&')
	} else {
		buf.WriteByte('?')
	}

It should be also noted that the only parameter that should be present for authorization requests leveraging RFC9126 should be the request_uri and client_id parameters. All other parameters should be part of the pushed authorization, at least by my reading of the spec.