Skip to content

Commit

Permalink
Update to JWTKit v5 (#149)
Browse files Browse the repository at this point in the history
* Switch to using JWTKit v5

* Add Sendable conformance

* [skip ci] Delete projectboard.yml

* Adapt to new errors

* Fix failing test

* Update package for new JWTKit API

* Update to use JWTKit 5 beta

* Bump Vapor dependency

* Fix stupid mistake

* Update CODEOWNERS

* Update README

* Remove codecov from README

---------

Co-authored-by: Gwynne Raskind <gwynne@vapor.codes>
  • Loading branch information
ptoffy and gwynne committed Feb 27, 2024
1 parent 209ae87 commit c56703f
Show file tree
Hide file tree
Showing 12 changed files with 309 additions and 422 deletions.
9 changes: 8 additions & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
@@ -1 +1,8 @@
* @0xTim @gwynne
* @ptoffy
/.github/CONTRIBUTING.md @ptoffy @0xTim @gwynne
/.github/workflows/*.yml @ptoffy @0xTim @gwynne
/.github/workflows/test.yml @ptoffy @gwynne
/.spi.yml @ptoffy @0xTim @gwynne
/.gitignore @ptoffy @0xTim @gwynne
/LICENSE @ptoffy @0xTim @gwynne
/README.md @ptoffy @0xTim @gwynne
38 changes: 23 additions & 15 deletions Package.swift
@@ -1,29 +1,37 @@
// swift-tools-version:5.4
// swift-tools-version:5.9
import PackageDescription

let package = Package(
name: "jwt",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6)
.macOS(.v13),
.iOS(.v16),
.tvOS(.v16),
.watchOS(.v9),
],
products: [
.library(name: "JWT", targets: ["JWT"]),
],
dependencies: [
.package(url: "https://github.com/vapor/jwt-kit.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.50.0"),
.package(url: "https://github.com/vapor/jwt-kit.git", from: "5.0.0-beta.1"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.92.0"),
],
targets: [
.target(name: "JWT", dependencies: [
.product(name: "JWTKit", package: "jwt-kit"),
.product(name: "Vapor", package: "vapor"),
]),
.testTarget(name: "JWTTests", dependencies: [
.target(name: "JWT"),
.product(name: "XCTVapor", package: "vapor"),
]),
.target(
name: "JWT",
dependencies: [
.product(name: "JWTKit", package: "jwt-kit"),
.product(name: "Vapor", package: "vapor"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
.testTarget(
name: "JWTTests",
dependencies: [
.target(name: "JWT"),
.product(name: "XCTVapor", package: "vapor"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
]
)
9 changes: 3 additions & 6 deletions README.md
Expand Up @@ -18,17 +18,14 @@
<a href="https://github.com/vapor/jwt/actions/workflows/test.yml">
<img src="https://img.shields.io/github/actions/workflow/status/vapor/jwt/test.yml?event=push&style=plastic&logo=github&label=tests&logoColor=%23ccc" alt="Continuous Integration">
</a>
<a href="https://swift.org">
<img src="https://design.vapor.codes/images/swift59up.svg" alt="Swift 5.9+">
</a>
</p>
<br>

Support for JWT (JSON Web Tokens) in Vapor.

Supported versions:

|Version|Swift|SPM|
|---|---|---|
|4.0|5.4+|`from: "4.0.0"`|

**Original author**

- Siemen Sikkema, [@siemensikkema](http://github.com/siemensikkema)
40 changes: 30 additions & 10 deletions Sources/JWT/Application+JWT.swift
@@ -1,16 +1,36 @@
import Vapor
import JWTKit
import Vapor
import NIOConcurrencyHelpers

extension Application {
public var jwt: JWT {
public extension Application {
var jwt: JWT {
.init(_application: self)
}

public struct JWT {
private final class Storage {
var signers: JWTSigners
struct JWT: Sendable {
private final class Storage: Sendable {
private struct SendableBox: Sendable {
var keys: JWTKeyCollection
}

private let sendableBox: NIOLockedValueBox<SendableBox>

var keys: JWTKeyCollection {
get {
self.sendableBox.withLockedValue { box in
box.keys
}
}
set {
self.sendableBox.withLockedValue { box in
box.keys = newValue
}
}
}

init() {
self.signers = .init()
let box = SendableBox(keys: .init())
self.sendableBox = .init(box)
}
}

Expand All @@ -20,9 +40,9 @@ extension Application {

public let _application: Application

public var signers: JWTSigners {
get { self.storage.signers }
set { self.storage.signers = newValue }
public var keys: JWTKeyCollection {
get { self.storage.keys }
set { self.storage.keys = newValue }
}

private var storage: Storage {
Expand Down
38 changes: 0 additions & 38 deletions Sources/JWT/AsyncJWTAuthenticator.swift

This file was deleted.

81 changes: 50 additions & 31 deletions Sources/JWT/JWT+Apple.swift
@@ -1,55 +1,55 @@
import NIOConcurrencyHelpers
import Vapor

extension Request.JWT {
public var apple: Apple {
public extension Request.JWT {
var apple: Apple {
.init(_jwt: self)
}

public struct Apple {
struct Apple: Sendable {
public let _jwt: Request.JWT

public func verify(applicationIdentifier: String? = nil) -> EventLoopFuture<AppleIdentityToken> {
public func verify(
applicationIdentifier: String? = nil
) async throws -> AppleIdentityToken {
guard let token = self._jwt._request.headers.bearerAuthorization?.token else {
self._jwt._request.logger.error("Request is missing JWT bearer header.")
return self._jwt._request.eventLoop.makeFailedFuture(Abort(.unauthorized))
throw Abort(.unauthorized)
}
return self.verify(token, applicationIdentifier: applicationIdentifier)
return try await self.verify(token, applicationIdentifier: applicationIdentifier)
}

public func verify(_ message: String, applicationIdentifier: String? = nil) -> EventLoopFuture<AppleIdentityToken> {
self.verify([UInt8](message.utf8), applicationIdentifier: applicationIdentifier)
public func verify(
_ message: String,
applicationIdentifier: String? = nil
) async throws -> AppleIdentityToken {
try await self.verify([UInt8](message.utf8), applicationIdentifier: applicationIdentifier)
}

public func verify<Message>(_ message: Message, applicationIdentifier: String? = nil) -> EventLoopFuture<AppleIdentityToken>
where Message: DataProtocol
{
self._jwt._request.application.jwt.apple.signers(
on: self._jwt._request
).flatMapThrowing { signers in
let token = try signers.verify(message, as: AppleIdentityToken.self)
if let applicationIdentifier = applicationIdentifier ?? self._jwt._request.application.jwt.apple.applicationIdentifier {
try token.audience.verifyIntendedAudience(includes: applicationIdentifier)
}
return token
public func verify(
_ message: some DataProtocol & Sendable,
applicationIdentifier: String? = nil
) async throws -> AppleIdentityToken {
let keys = try await self._jwt._request.application.jwt.apple.keys(on: self._jwt._request)
let token = try await keys.verify(message, as: AppleIdentityToken.self)
if let applicationIdentifier = applicationIdentifier ?? self._jwt._request.application.jwt.apple.applicationIdentifier {
try token.audience.verifyIntendedAudience(includes: applicationIdentifier)
}
return token
}
}
}

extension Application.JWT {
public var apple: Apple {
public extension Application.JWT {
var apple: Apple {
.init(_jwt: self)
}

public struct Apple {
struct Apple: Sendable {
public let _jwt: Application.JWT

public func signers(on request: Request) -> EventLoopFuture<JWTSigners> {
self.jwks.get(on: request).flatMapThrowing {
let signers = JWTSigners()
try signers.use(jwks: $0)
return signers
}
public func keys(on request: Request) async throws -> JWTKeyCollection {
try await JWTKeyCollection().add(jwks: jwks.get(on: request).get())
}

public var jwks: EndpointCache<JWKS> {
Expand All @@ -69,12 +69,31 @@ extension Application.JWT {
typealias Value = Storage
}

private final class Storage {
private final class Storage: Sendable {
private struct SendableBox: Sendable {
var applicationIdentifier: String?
}

let jwks: EndpointCache<JWKS>
var applicationIdentifier: String?
private let sendableBox: NIOLockedValueBox<SendableBox>

var applicationIdentifier: String? {
get {
self.sendableBox.withLockedValue { box in
box.applicationIdentifier
}
}
set {
self.sendableBox.withLockedValue { box in
box.applicationIdentifier = newValue
}
}
}

init() {
self.jwks = .init(uri: "https://appleid.apple.com/auth/keys")
self.applicationIdentifier = nil
let box = SendableBox(applicationIdentifier: nil)
self.sendableBox = .init(box)
}
}

Expand Down

0 comments on commit c56703f

Please sign in to comment.