Implemented the BearerAuthMiddleware middleware in the library target.

This commit is contained in:
2026-03-26 00:30:02 +01:00
parent 99b202a34e
commit d1761e8a83
5 changed files with 234 additions and 36 deletions
+5 -18
View File
@@ -61,26 +61,11 @@ The App Store Connect API requires authentication using API keys. You'll need to
1. Create an API key in App Store Connect 1. Create an API key in App Store Connect
2. Generate a signed JWT token using your key ID, issuer ID, and private key 2. Generate a signed JWT token using your key ID, issuer ID, and private key
3. Inject the token into each request via an OpenAPI middleware 3. Pass the token to the built-in `BearerAuthMiddleware` when creating the client
```swift ```swift
import OpenAPIRuntime import ASConnectService
import OpenAPIURLSession
struct BearerAuthMiddleware: ClientMiddleware {
let token: String
func intercept(
_ request: HTTPRequest,
body: HTTPBody?,
baseURL: URL,
operationID: String,
next: @Sendable (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?)
) async throws -> (HTTPResponse, HTTPBody?) {
var request = request
request.headerFields[.authorization] = "Bearer \(token)"
return try await next(request, body, baseURL)
}
}
let client = Client( let client = Client(
serverURL: try Servers.server1(), serverURL: try Servers.server1(),
@@ -89,6 +74,8 @@ let client = Client(
) )
``` ```
The `BearerAuthMiddleware` automatically injects the `Authorization` header with the Bearer token into every outgoing request.
## Supported Platforms ## Supported Platforms
- iOS 13.0+ - iOS 13.0+
@@ -59,26 +59,11 @@ The App Store Connect API requires authentication using API keys. You'll need to
1. Create an API key in App Store Connect 1. Create an API key in App Store Connect
2. Generate a signed JWT token using your key ID, issuer ID, and private key 2. Generate a signed JWT token using your key ID, issuer ID, and private key
3. Inject the token into each request via an OpenAPI middleware 3. Pass the token to the built-in ``BearerAuthMiddleware`` when creating the client
```swift ```swift
import OpenAPIRuntime import ASConnectService
import OpenAPIURLSession
struct BearerAuthMiddleware: ClientMiddleware {
let token: String
func intercept(
_ request: HTTPRequest,
body: HTTPBody?,
baseURL: URL,
operationID: String,
next: @Sendable (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?)
) async throws -> (HTTPResponse, HTTPBody?) {
var request = request
request.headerFields[.authorization] = "Bearer \(token)"
return try await next(request, body, baseURL)
}
}
let client = Client( let client = Client(
serverURL: try Servers.server1(), serverURL: try Servers.server1(),
@@ -87,6 +72,8 @@ let client = Client(
) )
``` ```
The ``BearerAuthMiddleware`` automatically injects the `Authorization` header with the Bearer token into every outgoing request.
## Supported Platforms ## Supported Platforms
- iOS 13.0+ - iOS 13.0+
@@ -108,6 +95,10 @@ let client = Client(
- ``Client``: The main API client for making requests - ``Client``: The main API client for making requests
### Authentication
- ``BearerAuthMiddleware``: A client middleware that injects a Bearer token into outgoing HTTP requests
### API Endpoints ### API Endpoints
The package provides access to all App Store Connect API endpoints including: The package provides access to all App Store Connect API endpoints including:
@@ -0,0 +1,80 @@
// ===----------------------------------------------------------------------===
//
// This source file is part of the ASConnectService open source project
//
// Copyright (c) 2026 Röck+Cöde VoF. and the ASConnectService project authors
// Licensed under Apache license v2.0
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of ASConnectService project authors
//
// SPDX-License-Identifier: Apache-2.0
//
// ===----------------------------------------------------------------------===
import struct Foundation.URL
import struct Foundation.URLComponents
import struct Foundation.URLQueryItem
import struct HTTPTypes.HTTPField
import struct HTTPTypes.HTTPFields
import struct HTTPTypes.HTTPRequest
import struct HTTPTypes.HTTPResponse
import protocol OpenAPIRuntime.ClientMiddleware
import class OpenAPIRuntime.HTTPBody
/// A client middleware that injects a Bearer authentication token into outgoing HTTP requests.
///
/// This middleware appends an `Authorization` header with a Bearer token to every request
/// before forwarding it to the next handler in the middleware chain. It is intended for use
/// with the App Store Connect API, which requires JSON Web Token (JWT) authentication.
///
/// ## Usage
///
/// ```swift
/// let middleware = BearerAuthMiddleware(token: "your-jwt-token")
/// ```
public struct BearerAuthMiddleware {
// MARK: Properties
/// The Bearer token to include in the `Authorization` header of each request.
private let token: String
// MARK: Initializers
/// Creates a new middleware instance with the given Bearer token.
/// - Parameter token: A JSON Web Token (JWT) string used to authenticate requests to the App Store Connect API.
init(token: String) {
self.token = token
}
}
// MARK: - ClientMiddleware
extension BearerAuthMiddleware: ClientMiddleware {
// MARK: Methods
/// Intercepts an outgoing HTTP request and adds a Bearer authentication token to its headers.
/// - Parameters:
/// - request: The original HTTP request.
/// - body: The optional body of the request.
/// - baseURL: The base URL for the request.
/// - operationID: The identifier of the API operation being performed.
/// - next: The next handler in the middleware chain.
/// - Returns: The HTTP response and optional body returned by the next handler.
public func intercept(
_ request: HTTPRequest,
body: HTTPBody?,
baseURL: URL,
operationID: String,
next: @Sendable (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?)
) async throws -> (HTTPResponse, HTTPBody?) {
var request = request
request.headerFields[.authorization] = "Bearer \(token)"
return try await next(
request,
body,
baseURL
)
}
}
@@ -0,0 +1,140 @@
import Foundation
import HTTPTypes
import OpenAPIRuntime
import Testing
@testable import ASConnectService
@Suite("BearerAuthMiddleware")
struct BearerAuthMiddlewareTests {
// MARK: Tests
@Test("Adds the Authorization header with the Bearer token to the request")
func addsAuthorizationHeader() async throws {
let middleware = BearerAuthMiddleware(token: .Token.jwt)
let (response, _) = try await middleware.intercept(
.get,
body: nil,
baseURL: .base,
operationID: "listApps",
next: { interceptedRequest, body, baseURL in
#expect(interceptedRequest.headerFields[.authorization] == "Bearer test-jwt-token")
return (HTTPResponse(status: .ok), nil)
}
)
#expect(response.status == .ok)
}
@Test("Forwards the request body to the next handler")
func forwardsRequestBody() async throws {
let middleware = BearerAuthMiddleware(token: .Token.jwt)
let requestBody: HTTPBody = HTTPBody("request-body")
var receivedBody: HTTPBody?
_ = try await middleware.intercept(
.get,
body: requestBody,
baseURL: .base,
operationID: "createApp",
next: { _, body, _ in
receivedBody = body
return (HTTPResponse(status: .created), nil)
}
)
let bodyData = try await Data(collecting: try #require(receivedBody), upTo: .max)
#expect(bodyData == Data("request-body".utf8))
}
@Test("Forwards the base URL to the next handler")
func forwardsBaseURL() async throws {
let middleware = BearerAuthMiddleware(token: .Token.jwt)
let expectedBaseURL = URL.base
_ = try await middleware.intercept(
.get,
body: nil,
baseURL: expectedBaseURL,
operationID: "listApps",
next: { _, _, baseURL in
#expect(baseURL == expectedBaseURL)
return (HTTPResponse(status: .ok), nil)
}
)
}
@Test("Returns the response from the next handler")
func returnsNextHandlerResponse() async throws {
let middleware = BearerAuthMiddleware(token: .Token.jwt)
let expectedBody: HTTPBody = HTTPBody("response-body")
let (response, body) = try await middleware.intercept(
.get,
body: nil,
baseURL: .base,
operationID: "listApps",
next: { _, _, _ in
(HTTPResponse(status: .notFound), expectedBody)
}
)
#expect(response.status == .notFound)
let bodyData = try await Data(collecting: try #require(body), upTo: .max)
#expect(bodyData == Data("response-body".utf8))
}
@Test("Preserves existing request headers")
func preservesExistingHeaders() async throws {
let middleware = BearerAuthMiddleware(token: .Token.jwt)
var request = HTTPRequest.get
request.headerFields[.contentType] = "application/json"
_ = try await middleware.intercept(
request,
body: nil,
baseURL: .base,
operationID: "listApps",
next: { interceptedRequest, _, _ in
#expect(interceptedRequest.headerFields[.contentType] == "application/json")
#expect(interceptedRequest.headerFields[.authorization] == "Bearer \(String.Token.jwt)")
return (HTTPResponse(status: .ok), nil)
}
)
}
}
// MARK: - HTTPRequest+Samples
private extension HTTPRequest {
static let get = HTTPRequest(
method: .get,
scheme: "https",
authority: "api.appstoreconnect.apple.com",
path: "/v1/apps"
)
}
// MARK: - String+Samples
private extension String {
enum Token {
static let jwt: String = "test-jwt-token"
}
}
// MARK: - URL+Samples
private extension URL {
static let base = URL(string: "https://api.appstoreconnect.apple.com")!
}