Skip to content

Commit 40f7fb4

Browse files
committed
Rebase Using Main Branch, Move RequirePKCE To Auth Meta
1 parent e8685b3 commit 40f7fb4

File tree

3 files changed

+70
-1
lines changed

3 files changed

+70
-1
lines changed

internal/testing/fake_auth_server.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"encoding/base64"
1010
"encoding/json"
1111
"fmt"
12+
"net"
1213
"net/http"
1314
"time"
1415

@@ -44,7 +45,22 @@ func NewFakeAuthMux() *http.ServeMux {
4445
}
4546

4647
func (s *state) handleMetadata(w http.ResponseWriter, r *http.Request) {
47-
issuer := "https://localhost:" + r.URL.Port()
48+
// Derive the port from the request Host; r.URL.Port() is empty on server side.
49+
hostPort := r.Host
50+
port := ""
51+
52+
if hp := hostPort; hp != "" {
53+
if _, p, err := net.SplitHostPort(hp); err == nil {
54+
port = p
55+
}
56+
}
57+
58+
if port == "" {
59+
// Fallback attempt; may still be empty depending on server/request.
60+
port = r.URL.Port()
61+
}
62+
63+
issuer := "https://localhost:" + port
4864
metadata := map[string]any{
4965
"issuer": issuer,
5066
"authorization_endpoint": issuer + "/authorize",

oauthex/auth_meta.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,19 @@ func GetAuthServerMeta(ctx context.Context, issuerURL string, c *http.Client) (*
145145
}
146146
return nil, fmt.Errorf("failed to get auth server metadata from %q: %w", issuerURL, errors.Join(errs...))
147147
}
148+
149+
// RequirePKCE checks that the authorization server for issuerURL supports PKCE,
150+
// by verifying that CodeChallengeMethodsSupported is non-empty.
151+
// It returns an error if metadata cannot be fetched or PKCE is not advertised.
152+
func RequirePKCE(ctx context.Context, issuerURL string, c *http.Client) error {
153+
asm, err := GetAuthServerMeta(ctx, issuerURL, c)
154+
if err != nil {
155+
return err
156+
}
157+
158+
if len(asm.CodeChallengeMethodsSupported) == 0 {
159+
return fmt.Errorf("authorization server at %s does not implement PKCE", issuerURL)
160+
}
161+
162+
return nil
163+
}

oauthex/auth_meta_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
package oauthex
88

99
import (
10+
"context"
1011
"encoding/json"
12+
"net/http"
13+
"net/http/httptest"
14+
"net/url"
1115
"os"
1216
"path/filepath"
1317
"testing"
18+
19+
itesting "github.com/modelcontextprotocol/go-sdk/internal/testing"
1420
)
1521

1622
func TestAuthMetaParse(t *testing.T) {
@@ -28,3 +34,34 @@ func TestAuthMetaParse(t *testing.T) {
2834
t.Errorf("got %q, want %q", g, w)
2935
}
3036
}
37+
38+
func TestRequirePKCE(t *testing.T) {
39+
ctx := context.Background()
40+
41+
// Start a fake OAuth 2.1 auth server that advertises PKCE (S256).
42+
orig := itesting.NewFakeAuthMux()
43+
wrapper := http.NewServeMux()
44+
wrapper.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
45+
orig.ServeHTTP(w, r)
46+
})
47+
ts := httptest.NewTLSServer(wrapper)
48+
defer ts.Close()
49+
50+
// Validate that the server supports PKCE per MCP auth requirements.
51+
// The fake server sets issuer to https://localhost:<port>, so compute that issuer.
52+
u, _ := url.Parse(ts.URL)
53+
issuer := "https://localhost:" + u.Port()
54+
55+
// The fake server presents a cert for example.com; set ServerName accordingly.
56+
httpClient := ts.Client()
57+
if tr, ok := httpClient.Transport.(*http.Transport); ok {
58+
clone := tr.Clone()
59+
clone.TLSClientConfig.ServerName = "example.com"
60+
httpClient.Transport = clone
61+
}
62+
63+
if err := RequirePKCE(ctx, issuer, httpClient); err != nil {
64+
t.Fatal(err)
65+
}
66+
67+
}

0 commit comments

Comments
 (0)