Authorize performs the authorization flow. It is designed to perform the whole Authorization Code Grant flow. On success, [AuthorizationCodeHandler.TokenSource] will return a token source with the fetched token.
(ctx context.Context, req *http.Request, resp *http.Response)
| 215 | // It is designed to perform the whole Authorization Code Grant flow. |
| 216 | // On success, [AuthorizationCodeHandler.TokenSource] will return a token source with the fetched token. |
| 217 | func (h *AuthorizationCodeHandler) Authorize(ctx context.Context, req *http.Request, resp *http.Response) error { |
| 218 | defer resp.Body.Close() |
| 219 | defer io.Copy(io.Discard, resp.Body) |
| 220 | |
| 221 | wwwChallenges, err := oauthex.ParseWWWAuthenticate(resp.Header[http.CanonicalHeaderKey("WWW-Authenticate")]) |
| 222 | if err != nil { |
| 223 | return fmt.Errorf("failed to parse WWW-Authenticate header: %v", err) |
| 224 | } |
| 225 | |
| 226 | if resp.StatusCode == http.StatusForbidden && errorFromChallenges(wwwChallenges) != "insufficient_scope" { |
| 227 | // We only want to perform step-up authorization for insufficient_scope errors. |
| 228 | // Returning nil, so that the call is retried immediately and the response |
| 229 | // is handled appropriately by the connection. |
| 230 | // Step-up authorization is defined at |
| 231 | // https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#step-up-authorization-flow |
| 232 | return nil |
| 233 | } |
| 234 | |
| 235 | prm, err := h.getProtectedResourceMetadata(ctx, wwwChallenges, req.URL.String()) |
| 236 | if err != nil { |
| 237 | return err |
| 238 | } |
| 239 | |
| 240 | asm, err := GetAuthServerMetadata(ctx, prm.AuthorizationServers[0], h.config.Client) |
| 241 | if err != nil { |
| 242 | return fmt.Errorf("failed to get authorization server metadata: %w", err) |
| 243 | } |
| 244 | if asm == nil { |
| 245 | // Fallback to 2025-03-26 spec: predefined endpoints. |
| 246 | // https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization#fallbacks-for-servers-without-metadata-discovery |
| 247 | authServerURL := prm.AuthorizationServers[0] |
| 248 | asm = &oauthex.AuthServerMeta{ |
| 249 | Issuer: authServerURL, |
| 250 | AuthorizationEndpoint: authServerURL + "/authorize", |
| 251 | TokenEndpoint: authServerURL + "/token", |
| 252 | RegistrationEndpoint: authServerURL + "/register", |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | resolvedClientConfig, err := h.handleRegistration(ctx, asm) |
| 257 | if err != nil { |
| 258 | return err |
| 259 | } |
| 260 | |
| 261 | scps := scopesFromChallenges(wwwChallenges) |
| 262 | if len(scps) == 0 && len(prm.ScopesSupported) > 0 { |
| 263 | scps = prm.ScopesSupported |
| 264 | } |
| 265 | |
| 266 | cfg := &oauth2.Config{ |
| 267 | ClientID: resolvedClientConfig.clientID, |
| 268 | ClientSecret: resolvedClientConfig.clientSecret, |
| 269 | |
| 270 | Endpoint: oauth2.Endpoint{ |
| 271 | AuthURL: asm.AuthorizationEndpoint, |
| 272 | TokenURL: asm.TokenEndpoint, |
| 273 | AuthStyle: resolvedClientConfig.authStyle, |
| 274 | }, |