-
Notifications
You must be signed in to change notification settings - Fork 125
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Examples] Auth middlewares: client and server (#440)
[Examples] Auth middlewares: client and server ### Motivation Show auth client and server middlewares. ### Modifications Added those two examples. ### Result Auth examples in place. ### Test Plan Tested manually together. Reviewed by: simonjbeaumont Builds: ✔︎ pull request validation (5.10) - Build finished. ✔︎ pull request validation (5.9) - Build finished. ✔︎ pull request validation (5.9.0) - Build finished. ✔︎ pull request validation (compatibility test) - Build finished. ✔︎ pull request validation (docc test) - Build finished. ✔︎ pull request validation (examples) - Build finished. ✔︎ pull request validation (integration test) - Build finished. ✔︎ pull request validation (nightly) - Build finished. ✔︎ pull request validation (soundness) - Build finished. #440
- Loading branch information
Showing
32 changed files
with
476 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
.DS_Store | ||
.build | ||
/Packages | ||
/*.xcodeproj | ||
xcuserdata/ | ||
DerivedData/ | ||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata | ||
.vscode | ||
/Package.resolved | ||
.ci/ | ||
.docc-build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// swift-tools-version:5.9 | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftOpenAPIGenerator open source project | ||
// | ||
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "AuthenticationClientMiddleware", | ||
platforms: [.macOS(.v11), .iOS(.v14), .tvOS(.v14), .watchOS(.v7), .visionOS(.v1)], | ||
dependencies: [ | ||
.package(url: "https://github.com/apple/swift-openapi-generator", exact: "1.0.0-alpha.1"), | ||
.package(url: "https://github.com/apple/swift-openapi-runtime", exact: "1.0.0-alpha.1"), | ||
.package(url: "https://github.com/apple/swift-openapi-urlsession", exact: "1.0.0-alpha.1"), | ||
.package(url: "https://github.com/apple/swift-http-types", from: "1.0.0"), | ||
], | ||
targets: [ | ||
.target( | ||
name: "AuthenticationClientMiddleware", | ||
dependencies: [ | ||
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"), | ||
.product(name: "HTTPTypes", package: "swift-http-types"), | ||
] | ||
), | ||
.executableTarget( | ||
name: "HelloWorldURLSessionClient", | ||
dependencies: [ | ||
"AuthenticationClientMiddleware", .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"), | ||
.product(name: "OpenAPIURLSession", package: "swift-openapi-urlsession"), | ||
], | ||
plugins: [.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")] | ||
), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Client Authentication Middleware | ||
|
||
In this example we'll implement a `ClientMiddleware` that injects an authentication header into the request. | ||
|
||
## Overview | ||
|
||
This example extends the [HelloWorldURLSessionClient](../HelloWorldURLSessionClient) | ||
with a new target, `AuthenticationClientMiddleware`, which is then used when creating | ||
the `Client`. | ||
|
||
NOTE: This example shows just one way of injecting authentication information in a middleware | ||
and is purely for illustrative purposes. | ||
|
||
The tool uses the [URLSession](https://developer.apple.com/documentation/foundation/urlsession) API to perform the HTTP call, wrapped in the [Swift OpenAPI URLSession Transport](https://github.com/apple/swift-openapi-urlsession). | ||
|
||
The server can be started by running the `AuthenticationServerMiddleware` example locally. | ||
|
||
## Usage | ||
|
||
Build and run the client CLI using: | ||
|
||
``` | ||
$ swift run HelloWorldURLSessionClient token_for_Fr | ||
Hello, Stranger! (Requested by: Frank) | ||
$ swift run HelloWorldURLSessionClient invalid_token | ||
Unauthorized | ||
``` |
42 changes: 42 additions & 0 deletions
42
...entMiddleware/Sources/AuthenticationClientMiddleware/AuthenticationClientMiddleware.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftOpenAPIGenerator open source project | ||
// | ||
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
import OpenAPIRuntime | ||
import Foundation | ||
import HTTPTypes | ||
|
||
/// A client middleware that injects a value into the `Authorization` header field of the request. | ||
package struct AuthenticationMiddleware { | ||
|
||
/// The value for the `Authorization` header field. | ||
private let value: String | ||
|
||
/// Creates a new middleware. | ||
/// - Parameter value: The value for the `Authorization` header field. | ||
package init(authorizationHeaderFieldValue value: String) { self.value = value } | ||
} | ||
|
||
extension AuthenticationMiddleware: ClientMiddleware { | ||
package func intercept( | ||
_ request: HTTPRequest, | ||
body: HTTPBody?, | ||
baseURL: URL, | ||
operationID: String, | ||
next: (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?) | ||
) async throws -> (HTTPResponse, HTTPBody?) { | ||
var request = request | ||
// Adds the `Authorization` header field with the provided value. | ||
request.headerFields[.authorization] = value | ||
return try await next(request, body, baseURL) | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
...ationClientMiddleware/Sources/HelloWorldURLSessionClient/HelloWorldURLSessionClient.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftOpenAPIGenerator open source project | ||
// | ||
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
import OpenAPIURLSession | ||
import Foundation | ||
import AuthenticationClientMiddleware | ||
|
||
@main struct HelloWorldURLSessionClient { | ||
static func main() async throws { | ||
let args = CommandLine.arguments | ||
guard args.count == 2 else { | ||
print("Requires a token") | ||
exit(1) | ||
} | ||
let client = Client( | ||
serverURL: URL(string: "http://localhost:8080/api")!, | ||
transport: URLSessionTransport(), | ||
middlewares: [AuthenticationMiddleware(authorizationHeaderFieldValue: args[1])] | ||
) | ||
let response = try await client.getGreeting() | ||
switch response { | ||
case .ok(let okResponse): print(try okResponse.body.json.message) | ||
case .unauthorized: print("Unauthorized") | ||
case .undocumented(statusCode: let statusCode, _): print("Undocumented status code: \(statusCode)") | ||
} | ||
} | ||
} |
4 changes: 4 additions & 0 deletions
4
...ticationClientMiddleware/Sources/HelloWorldURLSessionClient/openapi-generator-config.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
generate: | ||
- types | ||
- client | ||
accessModifier: internal |
38 changes: 38 additions & 0 deletions
38
Examples/AuthenticationClientMiddleware/Sources/HelloWorldURLSessionClient/openapi.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
openapi: '3.1.0' | ||
info: | ||
title: GreetingService | ||
version: 1.0.0 | ||
servers: | ||
- url: https://example.com/api | ||
description: Example service deployment. | ||
paths: | ||
/greet: | ||
get: | ||
operationId: getGreeting | ||
parameters: | ||
- name: name | ||
required: false | ||
in: query | ||
description: The name used in the returned greeting. | ||
schema: | ||
type: string | ||
responses: | ||
'200': | ||
description: A success response with a greeting. | ||
content: | ||
application/json: | ||
schema: | ||
$ref: '#/components/schemas/Greeting' | ||
'401': | ||
description: Authentication required. | ||
components: | ||
schemas: | ||
Greeting: | ||
type: object | ||
description: A value with the greeting contents. | ||
properties: | ||
message: | ||
type: string | ||
description: The string representation of the greeting. | ||
required: | ||
- message |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
.DS_Store | ||
.build | ||
/Packages | ||
/*.xcodeproj | ||
xcuserdata/ | ||
DerivedData/ | ||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata | ||
.vscode | ||
/Package.resolved | ||
.ci/ | ||
.docc-build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// swift-tools-version:5.9 | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftOpenAPIGenerator open source project | ||
// | ||
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "AuthenticationServerMiddleware", | ||
platforms: [.macOS(.v10_15)], | ||
dependencies: [ | ||
.package(url: "https://github.com/apple/swift-openapi-generator", exact: "1.0.0-alpha.1"), | ||
.package(url: "https://github.com/apple/swift-openapi-runtime", exact: "1.0.0-alpha.1"), | ||
.package(url: "https://github.com/swift-server/swift-openapi-vapor", exact: "1.0.0-alpha.1"), | ||
.package(url: "https://github.com/vapor/vapor", from: "4.87.1"), | ||
], | ||
targets: [ | ||
.target( | ||
name: "AuthenticationServerMiddleware", | ||
dependencies: [.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime")] | ||
), | ||
.executableTarget( | ||
name: "HelloWorldVaporServer", | ||
dependencies: [ | ||
"AuthenticationServerMiddleware", .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"), | ||
.product(name: "OpenAPIVapor", package: "swift-openapi-vapor"), | ||
.product(name: "Vapor", package: "vapor"), | ||
], | ||
plugins: [.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")] | ||
), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Server Authentication Server | ||
|
||
In this example we'll implement a `ServerMiddleware` that verifies an authentication header in the request. | ||
|
||
## Overview | ||
|
||
This example extends the [HelloWorldVaporServer](../HelloWorldVaporServer) with a new target, `AuthenticationServerMiddleware`, which is then used when registering the server handler with the transport. | ||
|
||
NOTE: This example shows just one way of varifying authentication information in a middleware and is purely for illustrative purposes. | ||
|
||
The tool uses the [Vapor](https://github.com/vapor/vapor) server framework to handle HTTP requests, wrapped in the [Swift OpenAPI Vapor Transport](https://github.com/swift-server/swift-openapi-vapor). | ||
|
||
The CLI starts the server on `http://localhost:8080` and can be invoked by running the `AuthenticationClientMiddleware` example client or on the command line using: | ||
|
||
``` | ||
$ curl -H 'Authorization: token_for_Frank' 'http://localhost:8080/api/greet?name=Jane' | ||
{ | ||
"message" : "Hello, Jane! (Requested by: Frank)" | ||
} | ||
``` | ||
|
||
## Usage | ||
|
||
Build and run the server CLI using: | ||
|
||
``` | ||
$ swift run | ||
2023-12-01T14:14:35+0100 notice codes.vapor.application : [Vapor] Server starting on http://127.0.0.1:8080 | ||
... | ||
``` |
65 changes: 65 additions & 0 deletions
65
...verMiddleware/Sources/AuthenticationServerMiddleware/AuthenticationServerMiddleware.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftOpenAPIGenerator open source project | ||
// | ||
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
import OpenAPIRuntime | ||
import HTTPTypes | ||
|
||
/// A server middleware that authenticates the incoming user based on the value of | ||
/// the `Authorization` header field and injects the identifier `User` information | ||
/// into a task local value, allowing the request handler to use it. | ||
package struct AuthenticationServerMiddleware: Sendable { | ||
|
||
/// Information about an authenticated user. | ||
package struct User: Hashable { | ||
|
||
/// The name of the authenticated user. | ||
package var name: String | ||
|
||
/// Creates a new user. | ||
/// - Parameter name: The name of the authenticated user. | ||
package init(name: String) { self.name = name } | ||
|
||
/// The task local value of the currently authenticated user. | ||
@TaskLocal package static var current: User? | ||
} | ||
|
||
/// The closure that authenticates the user based on the value of the `Authorization` | ||
/// header field. | ||
private let authenticate: @Sendable (String) -> User? | ||
|
||
/// Creates a new middleware. | ||
/// - Parameter authenticate: The closure that authenticates the user based on the value | ||
/// of the `Authorization` header field. | ||
package init(authenticate: @Sendable @escaping (String) -> User?) { self.authenticate = authenticate } | ||
} | ||
|
||
extension AuthenticationServerMiddleware: ServerMiddleware { | ||
package func intercept( | ||
_ request: HTTPRequest, | ||
body: HTTPBody?, | ||
metadata: ServerRequestMetadata, | ||
operationID: String, | ||
next: @Sendable (HTTPRequest, HTTPBody?, ServerRequestMetadata) async throws -> (HTTPResponse, HTTPBody?) | ||
) async throws -> (HTTPResponse, HTTPBody?) { | ||
// Extracts the `Authorization` value, if present. | ||
// If no `Authorization` header field value was provided, no User is injected into | ||
// the task local. | ||
guard let authorizationHeaderFieldValue = request.headerFields[.authorization] else { | ||
return try await next(request, body, metadata) | ||
} | ||
// Delegate the authentication logic to the closure. | ||
let user = authenticate(authorizationHeaderFieldValue) | ||
// Inject the authenticated user into the task local and call the next middleware. | ||
return try await User.$current.withValue(user) { try await next(request, body, metadata) } | ||
} | ||
} |
Oops, something went wrong.