ExchangeToken performs a token exchange request per RFC 8693 for Enterprise Managed Authorization (SEP-990). It exchanges an identity assertion (typically an ID Token) for an Identity Assertion JWT Authorization Grant (ID-JAG) that can be used to obtain an access token from an MCP Server. The token
( ctx context.Context, tokenEndpoint string, req *TokenExchangeRequest, clientCreds *ClientCredentials, httpClient *http.Client, )
| 79 | // - Extra("scope") may contain the scope if different from the request |
| 80 | // - Expiry is when the token expires |
| 81 | func ExchangeToken( |
| 82 | ctx context.Context, |
| 83 | tokenEndpoint string, |
| 84 | req *TokenExchangeRequest, |
| 85 | clientCreds *ClientCredentials, |
| 86 | httpClient *http.Client, |
| 87 | ) (*oauth2.Token, error) { |
| 88 | if tokenEndpoint == "" { |
| 89 | return nil, fmt.Errorf("token endpoint is required") |
| 90 | } |
| 91 | if req == nil { |
| 92 | return nil, fmt.Errorf("token exchange request is required") |
| 93 | } |
| 94 | if clientCreds == nil { |
| 95 | return nil, fmt.Errorf("client credentials are required") |
| 96 | } |
| 97 | if err := clientCreds.Validate(); err != nil { |
| 98 | return nil, fmt.Errorf("invalid client credentials: %w", err) |
| 99 | } |
| 100 | |
| 101 | // Validate required fields per SEP-990 Section 4. |
| 102 | if req.RequestedTokenType == "" { |
| 103 | return nil, fmt.Errorf("requested_token_type is required") |
| 104 | } |
| 105 | if req.Audience == "" { |
| 106 | return nil, fmt.Errorf("audience is required") |
| 107 | } |
| 108 | if req.Resource == "" { |
| 109 | return nil, fmt.Errorf("resource is required") |
| 110 | } |
| 111 | if req.SubjectToken == "" { |
| 112 | return nil, fmt.Errorf("subject_token is required") |
| 113 | } |
| 114 | if req.SubjectTokenType == "" { |
| 115 | return nil, fmt.Errorf("subject_token_type is required") |
| 116 | } |
| 117 | |
| 118 | // Validate URL schemes to prevent XSS attacks (see #526). |
| 119 | if err := checkURLScheme(tokenEndpoint); err != nil { |
| 120 | return nil, fmt.Errorf("invalid token endpoint: %w", err) |
| 121 | } |
| 122 | if err := checkURLScheme(req.Audience); err != nil { |
| 123 | return nil, fmt.Errorf("invalid audience: %w", err) |
| 124 | } |
| 125 | if err := checkURLScheme(req.Resource); err != nil { |
| 126 | return nil, fmt.Errorf("invalid resource: %w", err) |
| 127 | } |
| 128 | |
| 129 | // Per RFC 6749 Section 3.2, parameters sent without a value (like the empty |
| 130 | // "code" parameter) MUST be treated as if they were omitted from the request. |
| 131 | // The oauth2 library's Exchange method sends an empty code, but compliant |
| 132 | // servers should ignore it. |
| 133 | cfg := &oauth2.Config{ |
| 134 | ClientID: clientCreds.ClientID, |
| 135 | Endpoint: oauth2.Endpoint{ |
| 136 | TokenURL: tokenEndpoint, |
| 137 | AuthStyle: oauth2.AuthStyleInParams, |
| 138 | }, |
searching dependent graphs…