Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Examples] Auth middlewares: client and server #440

Merged
merged 15 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Examples/AuthenticationClientMiddleware/.gitignore
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/
43 changes: 43 additions & 0 deletions Examples/AuthenticationClientMiddleware/Package.swift
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")]
),
]
)
24 changes: 24 additions & 0 deletions Examples/AuthenticationClientMiddleware/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 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
czechboy0 marked this conversation as resolved.
Show resolved Hide resolved
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//===----------------------------------------------------------------------===//
//
// 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)
}
}
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 client = Client(
serverURL: URL(string: "http://localhost:8080/api")!,
transport: URLSessionTransport(),
middlewares: [
AuthenticationMiddleware(authorizationHeaderFieldValue: "token_for_Frank")
]
)
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)")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
generate:
- types
- client
accessModifier: internal
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
11 changes: 11 additions & 0 deletions Examples/AuthenticationServerMiddleware/.gitignore
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/
44 changes: 44 additions & 0 deletions Examples/AuthenticationServerMiddleware/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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")]
),
]
)
30 changes: 30 additions & 0 deletions Examples/AuthenticationServerMiddleware/README.md
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
...
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//===----------------------------------------------------------------------===//
//
// 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)
}
}
}
Loading