Skip to content

Commit

Permalink
[Examples] Auth middlewares: client and server (#440)
Browse files Browse the repository at this point in the history
[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
czechboy0 authored Dec 8, 2023
1 parent b82666d commit ca0e344
Show file tree
Hide file tree
Showing 32 changed files with 476 additions and 6 deletions.
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")]
),
]
)
27 changes: 27 additions & 0 deletions Examples/AuthenticationClientMiddleware/README.md
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
```
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)
}
}
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)")
}
}
}
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/
41 changes: 41 additions & 0 deletions Examples/AuthenticationServerMiddleware/Package.swift
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")]
),
]
)
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,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) }
}
}
Loading

0 comments on commit ca0e344

Please sign in to comment.