Skip to content

Commit be227ba

Browse files
committed
fix: bug fixes
1 parent 9819eed commit be227ba

File tree

2 files changed

+32
-24
lines changed

2 files changed

+32
-24
lines changed

app/app.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ func (app *App) setupMux() *http.ServeMux {
223223
mux.Handle("/token", app.rateLimiter.Middleware(http.HandlerFunc(app.oauthHandler.Token)))
224224
mux.Handle("/register", app.rateLimiter.Middleware(http.HandlerFunc(app.oauthHandler.Register)))
225225
mux.HandleFunc("/.well-known/oauth-authorization-server", app.oauthHandler.Discovery)
226+
mux.HandleFunc("/.well-known/oauth-protected-resource", app.oauthHandler.ProtectedResourceMetadata)
226227
return mux
227228
}
228229

web/oauth_handlers.go

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,7 @@ func (h *OAuthHandler) Callback(w http.ResponseWriter, r *http.Request) {
145145
// Complete the OAuth flow, which will redirect to the client
146146
// We initialize a new session for the user here. The user's ID will be the subject.
147147
mySession := &fosite.DefaultSession{
148-
Subject: userSession.ID,
149-
// The user suggested that an uninitialized ExpiresAt map might cause issues.
150-
// While Fosite's setters/getters are nil-safe, other parts of the library
151-
// or its dependencies might not be. Initializing it is a safe bet.
148+
Subject: userSession.ID,
152149
ExpiresAt: make(map[fosite.TokenType]time.Time),
153150
}
154151

@@ -162,29 +159,13 @@ func (h *OAuthHandler) Callback(w http.ResponseWriter, r *http.Request) {
162159
}
163160
}
164161

165-
h.Logger.Debug("Preparing to complete OAuth flow",
166-
"session_id", sessionID,
167-
"user_id", userSession.ID,
168-
"redirect_uri", ar.GetRedirectURI().String(),
169-
"state", ar.GetState(),
170-
"response_types", ar.GetResponseTypes(),
171-
"requested_scopes", ar.GetRequestedScopes(),
172-
"granted_scopes", ar.GetGrantedScopes(),
173-
"fosite_session_subject", mySession.Subject)
174-
175162
response, err := h.FositeProvider.NewAuthorizeResponse(ctx, ar, mySession)
176163
if err != nil {
177-
// Log the full Fosite error for debugging, including debug and hint fields.
178-
if rfcErr, ok := err.(*fosite.RFC6749Error); ok {
179-
h.Logger.Error("Fosite failed to create authorize response", "error", rfcErr.Error(), "debug", rfcErr.Debug(), "hint", rfcErr.HintField, "session_id", sessionID)
180-
} else {
181-
h.Logger.Error("Fosite failed to create authorize response with a non-fosite error", "error", err, "session_id", sessionID)
182-
}
164+
h.Logger.Error("Fosite failed to create authorize response", "error", err, "session_id", sessionID)
183165
h.FositeProvider.WriteAuthorizeError(ctx, w, ar, err)
184166
return
185167
}
186168

187-
h.Logger.Debug("Fosite successfully created authorize response", "session_id", sessionID)
188169
h.FositeProvider.WriteAuthorizeResponse(ctx, w, ar, response)
189170
return
190171
}
@@ -275,27 +256,45 @@ func (h *OAuthHandler) Discovery(w http.ResponseWriter, r *http.Request) {
275256
json.NewEncoder(w).Encode(response)
276257
}
277258

259+
// ProtectedResourceMetadata is the handler for the /.well-known/oauth-protected-resource endpoint.
260+
// It provides clients with information about how to obtain an access token for the MCP server.
261+
func (h *OAuthHandler) ProtectedResourceMetadata(w http.ResponseWriter, r *http.Request) {
262+
// As per RFC 9728, this endpoint provides metadata about the resource server (this MCP server).
263+
issuer := "http://" + h.AppConfig.Host // Should be https in production
264+
response := map[string]interface{}{
265+
"resource": "http://" + h.AppConfig.Host + "/mcp",
266+
"authorization_servers": []string{
267+
issuer,
268+
},
269+
"scopes_supported": []string{"default", "offline", "openid"},
270+
"bearer_methods_supported": []string{"header"},
271+
}
272+
273+
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
274+
json.NewEncoder(w).Encode(response)
275+
}
276+
278277
// Middleware is the OAuth 2.1 middleware for protecting MCP endpoints.
279278
func (h *OAuthHandler) Middleware(next http.Handler) http.Handler {
280279
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
281280
ctx := r.Context()
282281
token := fosite.AccessTokenFromRequest(r)
283282
if token == "" {
284-
http.Error(w, "Unauthorized: No access token provided", http.StatusUnauthorized)
283+
h.writeUnauthorizedError(w, "Unauthorized: No access token provided")
285284
return
286285
}
287286

288287
_, ar, err := h.FositeProvider.IntrospectToken(ctx, token, fosite.AccessToken, &fosite.DefaultSession{})
289288
if err != nil {
290-
http.Error(w, "Invalid or expired OAuth token", http.StatusUnauthorized)
289+
h.writeUnauthorizedError(w, "Invalid or expired OAuth token")
291290
return
292291
}
293292

294293
sessionID := ar.GetSession().GetSubject()
295294

296295
// Check if the underlying Kite credentials are still valid.
297296
if _, err := h.KCManager.GetAuthenticatedClient(sessionID); err != nil {
298-
http.Error(w, err.Error(), http.StatusUnauthorized)
297+
h.writeUnauthorizedError(w, err.Error())
299298
return
300299
}
301300

@@ -304,3 +303,11 @@ func (h *OAuthHandler) Middleware(next http.Handler) http.Handler {
304303
next.ServeHTTP(w, r)
305304
})
306305
}
306+
307+
// writeUnauthorizedError sets the WWW-Authenticate header and writes a 401 error.
308+
func (h *OAuthHandler) writeUnauthorizedError(w http.ResponseWriter, message string) {
309+
// As per RFC9728 Section 5.1, we must include the WWW-Authenticate header
310+
// to point clients to the resource metadata endpoint.
311+
w.Header().Set("WWW-Authenticate", `Bearer, resource_metadata="/.well-known/oauth-protected-resource"`)
312+
http.Error(w, message, http.StatusUnauthorized)
313+
}

0 commit comments

Comments
 (0)