@@ -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.
279278func (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