Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions oauthex/auth_meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ func GetAuthServerMeta(ctx context.Context, issuerURL string, c *http.Client) (*
// Security violation; don't keep trying.
return nil, fmt.Errorf("metadata issuer %q does not match issuer URL %q", asm.Issuer, issuerURL)
}

if len(asm.CodeChallengeMethodsSupported) == 0 {
return nil, fmt.Errorf("authorization server at %s does not implement PKCE", issuerURL)
}

return asm, nil
}
errs = append(errs, err)
Expand Down
93 changes: 93 additions & 0 deletions oauthex/auth_meta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
package oauthex

import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
)

Expand All @@ -28,3 +33,91 @@ func TestAuthMetaParse(t *testing.T) {
t.Errorf("got %q, want %q", g, w)
}
}

func TestGetAuthServerMetaPKCESupport(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
hasPKCESupport bool
expectError bool
expectedError string
}{
{
name: "server_with_pkce_support",
hasPKCESupport: true,
expectError: false,
},
{
name: "server_without_pkce_support",
hasPKCESupport: false,
expectError: true,
expectedError: "does not implement PKCE",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Start a fake OAuth 2.1 auth server
wrapper := http.NewServeMux()
wrapper.HandleFunc("/.well-known/oauth-authorization-server", func(w http.ResponseWriter, r *http.Request) {
u, _ := url.Parse("https://" + r.Host)
issuer := "https://localhost:" + u.Port()
metadata := AuthServerMeta{
Issuer: issuer,
AuthorizationEndpoint: issuer + "/authorize",
TokenEndpoint: issuer + "/token",
RegistrationEndpoint: issuer + "/register",
JWKSURI: issuer + "/.well-known/jwks.json",
ScopesSupported: []string{"openid", "profile", "email"},
ResponseTypesSupported: []string{"code"},
GrantTypesSupported: []string{"authorization_code"},
TokenEndpointAuthMethodsSupported: []string{"none"},
}

// Add PKCE support based on test case
if tt.hasPKCESupport {
metadata.CodeChallengeMethodsSupported = []string{"S256"}
}
// If hasPKCESupport is false, CodeChallengeMethodsSupported remains empty

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(metadata)
})
ts := httptest.NewTLSServer(wrapper)
defer ts.Close()

// The fake server sets issuer to https://localhost:<port>, so compute that issuer.
u, _ := url.Parse(ts.URL)
issuer := "https://localhost:" + u.Port()

// The fake server presents a cert for example.com; set ServerName accordingly.
httpClient := ts.Client()
if tr, ok := httpClient.Transport.(*http.Transport); ok {
clone := tr.Clone()
clone.TLSClientConfig.ServerName = "example.com"
httpClient.Transport = clone
}

meta, err := GetAuthServerMeta(ctx, issuer, httpClient)
if tt.expectError {
if err == nil {
t.Fatal("expected error but got none")
}
if !strings.Contains(err.Error(), tt.expectedError) {
t.Errorf("expected error to contain %q, but got: %v", tt.expectedError, err)
}
} else {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if meta == nil {
t.Fatal("expected metadata but got nil")
}
// Verify PKCE support is present
if len(meta.CodeChallengeMethodsSupported) == 0 {
t.Error("expected PKCE support but CodeChallengeMethodsSupported is empty")
}
}
})
}
}