mcp: client registration/token fixes (#5649)

## Summary

Fixes to MCP code registration and token requests. 

1. ease some requirements on fields that are RECOMMENDED 
2. fill in defaults
3. store both request and response in the client registration
4. check client secret in the /token request

## Related issues

- Fixes
https://linear.app/pomerium/issue/ENG-2462/mcp-ignore-unknown-grant-types-in-the-client-registration
- Fixes
https://linear.app/pomerium/issue/ENG-2461/mcp-support-client-secret-in-dynamic-client-registration
 
## User Explanation

<!-- How would you explain this change to the user? If this
change doesn't create any user-facing changes, you can leave
this blank. If filled out, add the `docs` label -->

## Checklist

- [x] reference any related issues
- [x] updated unit tests
- [x] add appropriate label (`enhancement`, `bug`, `breaking`,
`dependencies`, `ci`)
- [ ] ready for review
This commit is contained in:
Denis Mishin 2025-06-11 08:28:24 -07:00 committed by GitHub
parent 200f2e8164
commit 777b3b12d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1440 additions and 461 deletions

View file

@ -20,7 +20,7 @@ func ParseCodeGrantAuthorizeRequest(r *http.Request) (*gen.AuthorizationRequest,
RedirectUri: optionalFormParam(r, "redirect_uri"),
ResponseType: r.Form.Get("response_type"),
State: optionalFormParam(r, "state"),
CodeChallenge: r.Form.Get("code_challenge"),
CodeChallenge: optionalFormParam(r, "code_challenge"),
CodeChallengeMethod: optionalFormParam(r, "code_challenge_method"),
}

View file

@ -2,5 +2,5 @@
version: v2
deps:
- name: buf.build/bufbuild/protovalidate
commit: 8976f5be98c146529b1cc15cd2012b60
digest: b5:5d513af91a439d9e78cacac0c9455c7cb885a8737d30405d0b91974fe05276d19c07a876a51a107213a3d01b83ecc912996cdad4cddf7231f91379079cf7488d
commit: b52ab10f44684cb19d1fbcad56aedd36
digest: b5:5f464399f5ea7546eb3c6a4e822a306da538298f3d87e9f974f4523d4955de396eb2b8b52a2f3f06ac290764d80cecbaa0a4c96560558e43d9b1c722e61a9d5c

View file

@ -43,8 +43,9 @@ type AuthorizationRequest struct {
State *string `protobuf:"bytes,4,opt,name=state,proto3,oneof" json:"state,omitempty"`
// OPTIONAL. The scope of the access request as described by Section 1.4.1.
Scopes []string `protobuf:"bytes,5,rep,name=scopes,proto3" json:"scopes,omitempty"`
// REQUIRED, assumes https://www.rfc-editor.org/rfc/rfc7636.html#section-4.1
CodeChallenge string `protobuf:"bytes,6,opt,name=code_challenge,json=codeChallenge,proto3" json:"code_challenge,omitempty"`
// REQUIRED or RECOMMENDED, assumes https://www.rfc-editor.org/rfc/rfc7636.html#section-4.1
// subject to whether the client is public or confidential.
CodeChallenge *string `protobuf:"bytes,6,opt,name=code_challenge,json=codeChallenge,proto3,oneof" json:"code_challenge,omitempty"`
// OPTIONAL, defaults to plain if not present in the request. Code verifier
// transformation method is S256 or plain.
CodeChallengeMethod *string `protobuf:"bytes,7,opt,name=code_challenge_method,json=codeChallengeMethod,proto3,oneof" json:"code_challenge_method,omitempty"`
@ -124,8 +125,8 @@ func (x *AuthorizationRequest) GetScopes() []string {
}
func (x *AuthorizationRequest) GetCodeChallenge() string {
if x != nil {
return x.CodeChallenge
if x != nil && x.CodeChallenge != nil {
return *x.CodeChallenge
}
return ""
}
@ -158,7 +159,7 @@ var file_authorization_request_proto_rawDesc = string([]byte{
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6f,
0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69,
0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x22, 0xcb, 0x03, 0x0a, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
0x6f, 0x74, 0x6f, 0x22, 0xe0, 0x03, 0x0a, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x09,
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42,
0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,
@ -171,33 +172,34 @@ var file_authorization_request_proto_rawDesc = string([]byte{
0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52,
0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x88, 0x01, 0x01, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x6f,
0x70, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x70, 0x65,
0x73, 0x12, 0x34, 0x0a, 0x0e, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65,
0x6e, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0d, 0xba, 0x48, 0x0a, 0xc8, 0x01,
0x01, 0x72, 0x05, 0x10, 0x2b, 0x18, 0x80, 0x01, 0x52, 0x0d, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x68,
0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x12, 0x4b, 0x0a, 0x15, 0x63, 0x6f, 0x64, 0x65, 0x5f,
0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,
0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x12, 0xba, 0x48, 0x0f, 0x72, 0x0d, 0x52, 0x04, 0x53,
0x32, 0x35, 0x36, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x48, 0x02, 0x52, 0x13, 0x63, 0x6f,
0x64, 0x65, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x68, 0x6f,
0x64, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01,
0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x07, 0x75,
0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x42, 0x06, 0xba, 0x48,
0x03, 0xc8, 0x01, 0x01, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x42, 0x0f, 0x0a, 0x0d,
0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x42, 0x08, 0x0a,
0x06, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x63, 0x6f, 0x64, 0x65,
0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f,
0x64, 0x42, 0x97, 0x01, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x32,
0x31, 0x42, 0x19, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x31,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72,
0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0x2f, 0x67, 0x65,
0x6e, 0xa2, 0x02, 0x03, 0x4f, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32,
0x31, 0xca, 0x02, 0x07, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0xe2, 0x02, 0x13, 0x4f, 0x61,
0x75, 0x74, 0x68, 0x32, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0xea, 0x02, 0x07, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
0x73, 0x12, 0x36, 0x0a, 0x0e, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65,
0x6e, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05,
0x10, 0x2b, 0x18, 0x80, 0x01, 0x48, 0x02, 0x52, 0x0d, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x68, 0x61,
0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x88, 0x01, 0x01, 0x12, 0x4b, 0x0a, 0x15, 0x63, 0x6f, 0x64,
0x65, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x68,
0x6f, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x12, 0xba, 0x48, 0x0f, 0x72, 0x0d, 0x52,
0x04, 0x53, 0x32, 0x35, 0x36, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x48, 0x03, 0x52, 0x13,
0x63, 0x6f, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x4d, 0x65, 0x74,
0x68, 0x6f, 0x64, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8,
0x01, 0x01, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a,
0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x42, 0x06,
0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x42, 0x0f,
0x0a, 0x0d, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x42,
0x08, 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x63, 0x6f,
0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x42, 0x18, 0x0a, 0x16,
0x5f, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f,
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x42, 0x97, 0x01, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x6f,
0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0x42, 0x19, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x74,
0x6f, 0x50, 0x01, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x70, 0x6f, 0x6d, 0x65, 0x72, 0x69, 0x75,
0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6f, 0x61, 0x75, 0x74, 0x68,
0x32, 0x31, 0x2f, 0x67, 0x65, 0x6e, 0xa2, 0x02, 0x03, 0x4f, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x4f,
0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0xca, 0x02, 0x07, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x31,
0xe2, 0x02, 0x13, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x31,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (

View file

@ -34,10 +34,10 @@ message AuthorizationRequest {
// OPTIONAL. The scope of the access request as described by Section 1.4.1.
repeated string scopes = 5;
// REQUIRED, assumes https://www.rfc-editor.org/rfc/rfc7636.html#section-4.1
string code_challenge = 6 [
(buf.validate.field).required = true,
(buf.validate.field).string = {min_len: 43, max_len: 128}
// REQUIRED or RECOMMENDED, assumes https://www.rfc-editor.org/rfc/rfc7636.html#section-4.1
// subject to whether the client is public or confidential.
optional string code_challenge = 6 [
(buf.validate.field).string = {min_len: 43, max_len: 128}
];
// OPTIONAL, defaults to plain if not present in the request. Code verifier

View file

@ -15,6 +15,9 @@ func ParseTokenRequest(r *http.Request) (*gen.TokenRequest, error) {
return nil, fmt.Errorf("failed to parse form: %w", err)
}
// extract client credentials from HTTP Basic Authorization header, if present
basicID, basicSecret, basicOK := r.BasicAuth()
v := &gen.TokenRequest{
GrantType: r.Form.Get("grant_type"),
Code: optionalFormParam(r, "code"),
@ -25,6 +28,15 @@ func ParseTokenRequest(r *http.Request) (*gen.TokenRequest, error) {
ClientSecret: optionalFormParam(r, "client_secret"),
}
if basicOK {
if v.ClientId == nil && basicID != "" {
v.ClientId = &basicID
}
if v.ClientSecret == nil && basicSecret != "" {
v.ClientSecret = &basicSecret
}
}
err = protovalidate.Validate(v)
if err != nil {
return nil, fmt.Errorf("failed to validate token request: %w", err)

View file

@ -0,0 +1,48 @@
package oauth21_test
import (
"net/http"
"net/url"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/pomerium/pomerium/internal/oauth21"
)
func TestParseTokenRequest_BasicAuth(t *testing.T) {
form := url.Values{}
form.Set("grant_type", "authorization_code")
form.Set("code", "abc")
req, err := http.NewRequest(http.MethodPost, "/token", strings.NewReader(form.Encode()))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth("myclient", "secret")
tr, err := oauth21.ParseTokenRequest(req)
require.NoError(t, err)
require.NotNil(t, tr.ClientId)
require.Equal(t, "myclient", *tr.ClientId)
require.NotNil(t, tr.ClientSecret)
require.Equal(t, "secret", *tr.ClientSecret)
}
func TestParseTokenRequest_BasicAuthWithBodyOverride(t *testing.T) {
form := url.Values{}
form.Set("grant_type", "authorization_code")
form.Set("code", "abc")
form.Set("client_id", "bodyid")
form.Set("client_secret", "bodysecret")
req, err := http.NewRequest(http.MethodPost, "/token", strings.NewReader(form.Encode()))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth("basicid", "basicsecret")
tr, err := oauth21.ParseTokenRequest(req)
require.NoError(t, err)
require.NotNil(t, tr.ClientId)
require.Equal(t, "bodyid", *tr.ClientId) // body should win
require.NotNil(t, tr.ClientSecret)
require.Equal(t, "bodysecret", *tr.ClientSecret)
}

View file

@ -1,6 +1,7 @@
package oauth21
import (
"fmt"
"slices"
"github.com/pomerium/pomerium/internal/oauth21/gen"
@ -8,17 +9,45 @@ import (
)
func ValidateAuthorizationRequest(
client *rfc7591v1.ClientMetadata,
client *rfc7591v1.Metadata,
req *gen.AuthorizationRequest,
) error {
if err := ValidateAuthorizationRequestRedirectURI(client, req.RedirectUri); err != nil {
return err
}
if err := ValidateAuthorizationRequestCodeChallenge(client, req); err != nil {
return err
}
return nil
}
func ValidateAuthorizationRequestCodeChallenge(
client *rfc7591v1.Metadata,
req *gen.AuthorizationRequest,
) error {
m := client.GetTokenEndpointAuthMethod()
switch m {
case rfc7591v1.TokenEndpointAuthMethodNone:
if req.GetCodeChallenge() == "" {
return Error{
Code: InvalidRequest,
Description: "code challenge are required when token endpoint auth method is 'none'",
}
}
case rfc7591v1.TokenEndpointAuthMethodClientSecretBasic,
rfc7591v1.TokenEndpointAuthMethodClientSecretPost:
// code challenge is recommended but not required for these methods
default:
return Error{
Code: InvalidRequest,
Description: fmt.Sprintf("unsupported token endpoint auth method: %s", m),
}
}
return nil
}
func ValidateAuthorizationRequestRedirectURI(
client *rfc7591v1.ClientMetadata,
client *rfc7591v1.Metadata,
redirectURI *string,
) error {
if len(client.RedirectUris) == 0 {

View file

@ -13,17 +13,67 @@ import (
func TestValidateRequest(t *testing.T) {
t.Parallel()
clientBasic := rfc7591v1.TokenEndpointAuthMethodClientSecretBasic
clientNone := rfc7591v1.TokenEndpointAuthMethodNone
for _, tc := range []struct {
name string
client *rfc7591v1.ClientMetadata
client *rfc7591v1.Metadata
req *gen.AuthorizationRequest
err bool
}{
{
"optional redirect_uri, multiple redirect_uris",
&rfc7591v1.ClientMetadata{
"default token auth method, no code challenge",
&rfc7591v1.Metadata{
RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"},
},
&gen.AuthorizationRequest{
RedirectUri: proto.String("https://example.com/callback"),
},
true,
},
{
"none token auth method, no code challenge",
&rfc7591v1.Metadata{
RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"},
TokenEndpointAuthMethod: &clientNone,
},
&gen.AuthorizationRequest{
RedirectUri: proto.String("https://example.com/callback"),
},
true,
},
{
"none token auth method, code challenge is provided",
&rfc7591v1.Metadata{
RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"},
TokenEndpointAuthMethod: &clientNone,
},
&gen.AuthorizationRequest{
RedirectUri: proto.String("https://example.com/callback"),
CodeChallenge: proto.String("challenge"),
},
false,
},
{
"none token auth method, code challenge and method are provided",
&rfc7591v1.Metadata{
RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"},
TokenEndpointAuthMethod: &clientNone,
},
&gen.AuthorizationRequest{
RedirectUri: proto.String("https://example.com/callback"),
CodeChallenge: proto.String("challenge"),
CodeChallengeMethod: proto.String("S256"),
},
false,
},
{
"optional redirect_uri, multiple redirect_uris",
&rfc7591v1.Metadata{
RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"},
TokenEndpointAuthMethod: &clientBasic,
},
&gen.AuthorizationRequest{
RedirectUri: nil,
},
@ -31,8 +81,9 @@ func TestValidateRequest(t *testing.T) {
},
{
"optional redirect_uri, single redirect_uri",
&rfc7591v1.ClientMetadata{
RedirectUris: []string{"https://example.com/callback"},
&rfc7591v1.Metadata{
RedirectUris: []string{"https://example.com/callback"},
TokenEndpointAuthMethod: &clientBasic,
},
&gen.AuthorizationRequest{
RedirectUri: nil,
@ -41,8 +92,9 @@ func TestValidateRequest(t *testing.T) {
},
{
"matching redirect_uri",
&rfc7591v1.ClientMetadata{
RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"},
&rfc7591v1.Metadata{
RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"},
TokenEndpointAuthMethod: &clientBasic,
},
&gen.AuthorizationRequest{
RedirectUri: proto.String("https://example.com/callback"),
@ -51,8 +103,9 @@ func TestValidateRequest(t *testing.T) {
},
{
"non-matching redirect_uri",
&rfc7591v1.ClientMetadata{
RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"},
&rfc7591v1.Metadata{
RedirectUris: []string{"https://example.com/callback", "https://example.com/other-callback"},
TokenEndpointAuthMethod: &clientBasic,
},
&gen.AuthorizationRequest{
RedirectUri: proto.String("https://example.com/invalid-callback"),