Skip to content

Commit e2978e9

Browse files
authored
feat: add authorization handler to inject openid scope if missing, return scope in DCR response (#32)
1 parent 09c5943 commit e2978e9

File tree

7 files changed

+115
-13
lines changed

7 files changed

+115
-13
lines changed

.vscode/launch.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "MCP Gateway",
9+
"type": "go",
10+
"request": "launch",
11+
"mode": "auto",
12+
"program": "${workspaceFolder}"
13+
}
14+
]
15+
}

config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Config struct {
2020
type Authorization struct {
2121
Server string `yaml:"server" json:"server"`
2222
ServerMetadataProxyEnabled bool `yaml:"serverMetadataProxyEnabled" json:"serverMetadataProxyEnabled"`
23+
AuthorizationProxyEnabled bool `yaml:"authorizationProxyEnabled" json:"authorizationProxyEnabled"`
2324
DynamicClientRegistrationEnabled bool `yaml:"dynamicClientRegistrationEnabled" json:"dynamicClientRegistrationEnabled"`
2425
}
2526

examples/who-am-i/config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
host: http://localhost:9000/
22
authorization:
33
server: http://dex:5556/
4+
authorizationProxyEnabled: true
45
serverMetadataProxyEnabled: true
56
dynamicClientRegistrationEnabled: true
67
dexGRPCClient:

oauth/authorization.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package oauth
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net/http"
7+
"net/url"
8+
"slices"
9+
"strings"
10+
11+
"github.com/hyprmcp/mcp-gateway/config"
12+
)
13+
14+
const AuthorizationPath = "/oauth/authorize"
15+
16+
func NewAuthorizationHandler(config *config.Config, meta map[string]any) (http.Handler, error) {
17+
supportedScopes := getSupportedScopes(meta)
18+
var requiredScopes = slices.DeleteFunc(
19+
[]string{"openid", "profile", "email"},
20+
func(s string) bool { return !slices.Contains(supportedScopes, s) },
21+
)
22+
23+
if authorizationEndpointStr, ok := meta["authorization_endpoint"].(string); !ok {
24+
return nil, errors.New("authorization metadata is missing authorization_endpoint field")
25+
} else if _, err := url.Parse(authorizationEndpointStr); err != nil {
26+
return nil, fmt.Errorf("could not parse authorization endpoint: %w", err)
27+
} else {
28+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
29+
redirectURI, _ := url.Parse(authorizationEndpointStr)
30+
q := r.URL.Query()
31+
scopes := q.Get("scope")
32+
for _, scope := range requiredScopes {
33+
if !strings.Contains(scopes, scope) {
34+
scopes = strings.TrimSpace(scopes + " " + scope)
35+
}
36+
}
37+
q.Set("scope", scopes)
38+
redirectURI.RawQuery = q.Encode()
39+
http.Redirect(w, r, redirectURI.String(), http.StatusFound)
40+
}), nil
41+
}
42+
}

oauth/authorization_server_metadata.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,22 @@ func NewAuthorizationServerMetadataHandler(config *config.Config) http.Handler {
2222
http.Error(w, "Failed to retrieve authorization server metadata", http.StatusInternalServerError)
2323
}
2424

25-
if _, ok := metadata["registration_endpoint"]; !ok {
26-
registrationURL, _ := url.Parse(config.Host.String())
27-
registrationURL.Path = DynamicClientRegistrationPath
28-
metadata["registration_endpoint"] = registrationURL.String()
29-
log.Get(r.Context()).Info("Adding registration endpoint to authorization server metadata",
30-
"url", metadata["registration_endpoint"])
25+
if config.Authorization.DynamicClientRegistrationEnabled {
26+
if _, ok := metadata["registration_endpoint"]; !ok {
27+
registrationURI, _ := url.Parse(config.Host.String())
28+
registrationURI.Path = DynamicClientRegistrationPath
29+
metadata["registration_endpoint"] = registrationURI.String()
30+
log.Get(r.Context()).Info("Adding registration endpoint to authorization server metadata",
31+
"url", metadata["registration_endpoint"])
32+
}
33+
}
34+
35+
if config.Authorization.AuthorizationProxyEnabled {
36+
authorizationURI, _ := url.Parse(config.Host.String())
37+
authorizationURI.Path = AuthorizationPath
38+
metadata["authorization_endpoint"] = authorizationURI.String()
39+
log.Get(r.Context()).Info("Adding authorization endpoint to authorization server metadata",
40+
"url", metadata["authorization_endpoint"])
3141
}
3242

3343
w.Header().Set("Content-Type", "application/json")

oauth/dynamic_client_registration.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"crypto/rand"
55
"encoding/json"
66
"net/http"
7+
"slices"
8+
"strings"
79

810
"github.com/dexidp/dex/api/v2"
911
"github.com/hyprmcp/mcp-gateway/config"
@@ -21,9 +23,10 @@ type ClientInformation struct {
2123
ClientName string `json:"client_name,omitempty"`
2224
RedirectURIs []string `json:"redirect_uris"`
2325
LogoURI string `json:"logo_uri,omitempty"`
26+
Scope string `json:"scope,omitempty"`
2427
}
2528

26-
func NewDynamicClientRegistrationHandler(config *config.Config) (http.Handler, error) {
29+
func NewDynamicClientRegistrationHandler(config *config.Config, meta map[string]any) (http.Handler, error) {
2730
grpcClient, err := grpc.NewClient(
2831
config.DexGRPCClient.Addr,
2932
grpc.WithTransportCredentials(insecure.NewCredentials()),
@@ -61,13 +64,19 @@ func NewDynamicClientRegistrationHandler(config *config.Config) (http.Handler, e
6164
w.WriteHeader(http.StatusCreated)
6265
w.Header().Set("Content-Type", "application/json")
6366

64-
err = json.NewEncoder(w).Encode(ClientInformation{
67+
resp := ClientInformation{
6568
ClientID: clientResponse.Client.Id,
6669
ClientSecret: clientResponse.Client.Secret,
6770
ClientName: clientResponse.Client.Name,
6871
RedirectURIs: clientResponse.Client.RedirectUris,
6972
LogoURI: clientResponse.Client.LogoUrl,
70-
})
73+
}
74+
75+
if scopesSupported := getSupportedScopes(meta); len(scopesSupported) > 0 {
76+
resp.Scope = strings.Join(scopesSupported, " ")
77+
}
78+
79+
err = json.NewEncoder(w).Encode(resp)
7180
if err != nil {
7281
log.Get(r.Context()).Error(err, "Failed to encode response")
7382
}
@@ -79,3 +88,18 @@ func NewDynamicClientRegistrationHandler(config *config.Config) (http.Handler, e
7988
func genRandom() string {
8089
return rand.Text()
8190
}
91+
92+
func getSupportedScopes(meta map[string]any) []string {
93+
if scopesSupported, ok := meta["scopes_supported"].([]any); ok {
94+
scopesSupportedStr := make([]string, 0, len(scopesSupported))
95+
for _, v := range scopesSupported {
96+
if s, ok := v.(string); ok {
97+
scopesSupportedStr = append(scopesSupportedStr, s)
98+
}
99+
}
100+
101+
return slices.Clip(scopesSupportedStr)
102+
}
103+
104+
return nil
105+
}

oauth/oauth.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import (
1717
)
1818

1919
type Manager struct {
20-
jwkSet jwk.Set
21-
config *config.Config
20+
jwkSet jwk.Set
21+
config *config.Config
22+
authServerMeta map[string]any
2223
}
2324

2425
func NewManager(ctx context.Context, config *config.Config) (*Manager, error) {
@@ -35,7 +36,7 @@ func NewManager(ctx context.Context, config *config.Config) (*Manager, error) {
3536
} else if s, err := cache.CachedSet(jwksURI); err != nil {
3637
return nil, fmt.Errorf("jwks cache set error: %w", err)
3738
} else {
38-
return &Manager{jwkSet: s, config: config}, nil
39+
return &Manager{jwkSet: s, config: config, authServerMeta: meta}, nil
3940
}
4041
}
4142

@@ -47,14 +48,22 @@ func (mgr *Manager) Register(mux *http.ServeMux) error {
4748
}
4849

4950
if mgr.config.Authorization.DynamicClientRegistrationEnabled {
50-
if handler, err := NewDynamicClientRegistrationHandler(mgr.config); err != nil {
51+
if handler, err := NewDynamicClientRegistrationHandler(mgr.config, mgr.authServerMeta); err != nil {
5152
return err
5253
} else {
5354
rateLimiter := httprate.LimitByRealIP(3, 10*time.Minute)
5455
mux.Handle(DynamicClientRegistrationPath, rateLimiter(handler))
5556
}
5657
}
5758

59+
if mgr.config.Authorization.AuthorizationProxyEnabled {
60+
if handler, err := NewAuthorizationHandler(mgr.config, mgr.authServerMeta); err != nil {
61+
return err
62+
} else {
63+
mux.Handle(AuthorizationPath, handler)
64+
}
65+
}
66+
5867
return nil
5968
}
6069

0 commit comments

Comments
 (0)