@@ -43,6 +43,15 @@ of the MCP specification.
4343 - [ Initialize Hook] ( #initialize-hook )
4444 - [ Graceful Shutdown] ( #graceful-shutdown )
4545- [ Transports] ( #transports )
46+ - [ Authentication] ( #authentication )
47+ - [ Client: Client Credentials Flow] ( #client-client-credentials-flow )
48+ - [ Client: Authorization Code Flow] ( #client-authorization-code-flow )
49+ - [ Client: Custom Token Provider] ( #client-custom-token-provider )
50+ - [ Client: Custom Token Storage] ( #client-custom-token-storage )
51+ - [ Client: private\_ key\_ jwt Authentication] ( #client-private_key_jwt-authentication )
52+ - [ Client: Endpoint Overrides] ( #client-endpoint-overrides )
53+ - [ Server: Serving Protected Resource Metadata] ( #server-serving-protected-resource-metadata )
54+ - [ Server: Validating Bearer Tokens] ( #server-validating-bearer-tokens )
4655- [ Platform Availability] ( #platform-availability )
4756- [ Debugging and Logging] ( #debugging-and-logging )
4857- [ Additional Resources] ( #additional-resources )
@@ -1341,6 +1350,195 @@ public actor MyCustomTransport: Transport {
13411350}
13421351```
13431352
1353+ ## Authentication
1354+
1355+ ` HTTPClientTransport ` supports OAuth 2.1 Bearer token authorization per the
1356+ [ MCP authorization specification] ( https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization ) .
1357+ When a server returns ` 401 Unauthorized ` or ` 403 Forbidden ` , the transport automatically:
1358+
1359+ 1 . Discovers Protected Resource Metadata (RFC 9728) at ` /.well-known/oauth-protected-resource `
1360+ 2 . Discovers Authorization Server Metadata (RFC 8414 / OIDC Discovery 1.0)
1361+ 3 . Registers the client dynamically (RFC 7591) if needed
1362+ 4 . Acquires a Bearer token using the configured grant flow (PKCE enforced)
1363+ 5 . Retries the original request with the token attached
1364+
1365+ Authorization is opt-in and disabled by default.
1366+ Pass an ` OAuthAuthorizer ` to ` HTTPClientTransport(authorizer:) ` to enable it.
1367+
1368+ ### Client: Client Credentials Flow
1369+
1370+ Machine-to-machine authentication using a pre-shared client secret:
1371+
1372+ ``` swift
1373+ let config = OAuthConfiguration (
1374+ grantType : .clientCredentials ,
1375+ authentication : .clientSecretBasic (clientID : " my-app" , clientSecret : " s3cr3t" )
1376+ )
1377+ let authorizer = OAuthAuthorizer (configuration : config)
1378+ let transport = HTTPClientTransport (
1379+ endpoint : URL (string : " https://api.example.com/mcp" )! ,
1380+ authorizer : authorizer
1381+ )
1382+ let client = Client (name : " MyClient" , version : " 1.0.0" )
1383+ try await client.connect (transport : transport)
1384+ ```
1385+
1386+ ### Client: Authorization Code Flow
1387+
1388+ Interactive, browser-based authentication with PKCE.
1389+ Implement ` OAuthAuthorizationDelegate ` to open the authorization URL and capture the redirect:
1390+
1391+ ``` swift
1392+ struct MyAuthDelegate : OAuthAuthorizationDelegate {
1393+ func presentAuthorizationURL (_ url : URL) async throws -> URL {
1394+ // Open the URL in a browser/webview and wait for the callback redirect URI.
1395+ // The returned URL must include the authorization code and state parameters.
1396+ return try await openBrowserAndWaitForCallback (url)
1397+ }
1398+ }
1399+
1400+ let config = OAuthConfiguration (
1401+ grantType : .authorizationCode ,
1402+ authentication : .none (clientID : " my-app" ),
1403+ authorizationDelegate : MyAuthDelegate ()
1404+ )
1405+ let authorizer = OAuthAuthorizer (configuration : config)
1406+ let transport = HTTPClientTransport (
1407+ endpoint : URL (string : " https://api.example.com/mcp" )! ,
1408+ authorizer : authorizer
1409+ )
1410+ ```
1411+
1412+ ### Client: Custom Token Provider
1413+
1414+ Supply an externally acquired token (e.g., from a system credential store) via ` accessTokenProvider ` .
1415+ The SDK calls this closure after discovery completes. Return ` nil ` to fall back to the configured grant flow:
1416+
1417+ ``` swift
1418+ let config = OAuthConfiguration (
1419+ grantType : .clientCredentials ,
1420+ authentication : .none (clientID : " my-app" ),
1421+ accessTokenProvider : { context, session in
1422+ // context contains the discovered resource URI, token endpoint, scopes, etc.
1423+ return try await KeychainTokenStore.shared .loadToken (for : context.resource )
1424+ }
1425+ )
1426+ ```
1427+
1428+ ### Client: Custom Token Storage
1429+
1430+ By default, tokens are stored in memory and lost when the process exits.
1431+ To persist tokens across sessions, implement ` TokenStorage ` and pass it to ` OAuthAuthorizer ` :
1432+
1433+ ``` swift
1434+ final class KeychainTokenStorage : TokenStorage {
1435+ func save (_ token : OAuthAccessToken) {
1436+ // Encode and store token.value in the system Keychain
1437+ }
1438+
1439+ func load () -> OAuthAccessToken? {
1440+ // Load and decode token from the Keychain
1441+ return nil
1442+ }
1443+
1444+ func clear () {
1445+ // Delete from the Keychain
1446+ }
1447+ }
1448+
1449+ let authorizer = OAuthAuthorizer (
1450+ configuration : config,
1451+ tokenStorage : KeychainTokenStorage ()
1452+ )
1453+ ```
1454+
1455+ ### Client: ` private_key_jwt ` Authentication
1456+
1457+ Authenticate to the token endpoint using an asymmetric key (RFC 7523).
1458+ The SDK provides a built-in ES256 helper for P-256 keys:
1459+
1460+ ``` swift
1461+ let config = OAuthConfiguration (
1462+ grantType : .clientCredentials ,
1463+ authentication : .privateKeyJWT (
1464+ clientID : " my-app" ,
1465+ assertionFactory : { tokenEndpoint, clientID in
1466+ try OAuthConfiguration.makePrivateKeyJWTAssertion (
1467+ clientID : clientID,
1468+ tokenEndpoint : tokenEndpoint,
1469+ privateKeyPEM : myEC256PrivateKeyPEM // PEM-encoded P-256 private key
1470+ )
1471+ }
1472+ )
1473+ )
1474+ ```
1475+
1476+ ### Client: Endpoint Overrides
1477+
1478+ Skip automatic discovery by providing explicit endpoint URLs.
1479+ Useful when the server does not publish well-known metadata documents:
1480+
1481+ ``` swift
1482+ let config = OAuthConfiguration (
1483+ grantType : .clientCredentials ,
1484+ authentication : .clientSecretBasic (clientID : " app" , clientSecret : " secret" ),
1485+ endpointOverrides : OAuthConfiguration.EndpointOverrides (
1486+ tokenEndpoint : URL (string : " https://auth.example.com/oauth/token" )!
1487+ )
1488+ )
1489+ ```
1490+
1491+ ### Server: Serving Protected Resource Metadata
1492+
1493+ Per the MCP authorization specification, servers ** MUST** serve Protected Resource Metadata
1494+ at ` /.well-known/oauth-protected-resource ` so clients can discover authorization server endpoints.
1495+
1496+ Use ` ProtectedResourceMetadataValidator ` as the first validator in your pipeline so that
1497+ unauthenticated discovery requests are handled before the bearer token check:
1498+
1499+ ``` swift
1500+ let metadata = OAuthProtectedResourceServerMetadata (
1501+ resource : " https://api.example.com" ,
1502+ authorizationServers : [URL (string : " https://auth.example.com" )! ],
1503+ scopesSupported : [" read" , " write" ]
1504+ )
1505+ let metadataValidator = ProtectedResourceMetadataValidator (metadata : metadata)
1506+ ```
1507+
1508+ ### Server: Validating Bearer Tokens
1509+
1510+ Use ` BearerTokenValidator ` to authenticate incoming requests.
1511+ Your ` tokenValidator ` closure ** MUST** verify the token's ` aud ` claim to prevent
1512+ token substitution attacks where a token intended for another resource is replayed against your server:
1513+
1514+ ``` swift
1515+ let resourceIdentifier = URL (string : " https://api.example.com" )!
1516+
1517+ let bearerValidator = BearerTokenValidator (
1518+ resourceMetadataURL : URL (string : " https://api.example.com/.well-known/oauth-protected-resource" )! ,
1519+ resourceIdentifier : resourceIdentifier,
1520+ tokenValidator : { token, request, context in
1521+ guard let claims = try ? verifyAndDecodeJWT (token) else {
1522+ return .invalidToken (errorDescription : " Token verification failed" )
1523+ }
1524+ // Pass audience and expiry to BearerTokenInfo; the SDK validates the
1525+ // audience claim against resourceIdentifier automatically.
1526+ return .valid (BearerTokenInfo (
1527+ audience : claims.audience ,
1528+ expiresAt : claims.expiresAt
1529+ ))
1530+ }
1531+ )
1532+
1533+ let pipeline = StandardValidationPipeline (validators : [
1534+ metadataValidator, // serves /.well-known/oauth-protected-resource unauthenticated
1535+ bearerValidator, // validates Bearer tokens on all other requests
1536+ AcceptHeaderValidator (mode : .sseRequired ),
1537+ ContentTypeValidator (),
1538+ SessionValidator (),
1539+ ])
1540+ ```
1541+
13441542## Platform Availability
13451543
13461544The Swift SDK has the following platform requirements:
0 commit comments