Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
/.build
/Packages
/*.xcodeproj
build/
.swiftpm/
82 changes: 50 additions & 32 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 7 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
// swift-tools-version:5.2
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "FirebaseJWTMiddleware",
platforms: [
.macOS(.v10_15)
.macOS(.v12)
],
products: [
.library(name: "FirebaseJWTMiddleware", targets: ["FirebaseJWTMiddleware"]),
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-rc"),
.package(url: "https://github.com/vapor/jwt.git", from: "4.0.0-rc"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/jwt.git", from: "4.0.0"),
],
targets: [
// .executableTarget(name: "Run", dependencies: [.target(name: "App")]),
.target(name: "FirebaseJWTMiddleware", dependencies: [
.product(name: "Vapor", package: "vapor"),
.product(name: "JWT", package: "jwt")
.product(name: "Vapor", package: "vapor"),
.product(name: "JWT", package: "jwt")
]),
.testTarget(name: "FirebaseJWTMiddlewareTests", dependencies: [
.target(name: "FirebaseJWTMiddleware"),
Expand Down
11 changes: 11 additions & 0 deletions Sources/FirebaseJWTMiddleware/Application+FirebaseJWT.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,22 @@ import Vapor
import JWT

extension Application {

public var firebaseJwt: FirebaseJWT {
.init(application: self)
}

public struct FirebaseJWT {

let application: Application

public func asyncSigners(on request: Request) async throws -> JWTSigners {
let requests = try await jwks.get(on: request).get()
let signers = JWTSigners()
try signers.use(jwks: requests)
return signers
}

public func signers(on request: Request) -> EventLoopFuture<JWTSigners> {
self.jwks.get(on: request).flatMapThrowing {
let signers = JWTSigners()
Expand All @@ -42,9 +51,11 @@ extension Application {
}

private final class Storage {

let jwks: EndpointCache<JWKS>
var applicationIdentifier: String?
var gSuiteDomainName: String?

init() {
self.jwks = .init(uri: "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com")
self.applicationIdentifier = nil
Expand Down
16 changes: 13 additions & 3 deletions Sources/FirebaseJWTMiddleware/FirebaseJWTMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@ import Vapor
import JWT

open class FirebaseJWTMiddleware: Middleware {
public init() {}

public init() { }

public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
request.firebaseJwt.verify().transform(to: next.respond(to: request))
}
}

open class AsyncFirebaseJWTMiddleware: AsyncMiddleware {

public init() { }

public func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
_ = try await request.firebaseJwt.asyncVerify()
return try await next.respond(to: request)
}
}
9 changes: 9 additions & 0 deletions Sources/FirebaseJWTMiddleware/FirebaseJWTPayload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import JWT

public struct FirebaseJWTPayload: JWTPayload {

enum CodingKeys: String, CodingKey {
case issuer = "iss"
case subject = "sub"
Expand All @@ -21,6 +22,7 @@ public struct FirebaseJWTPayload: JWTPayload {
case authTime = "auth_time"
case isEmailVerified = "email_verified"
case phoneNumber = "phone_number"
case firebase = "firebase"
}

/// Issuer. It must be "https://securetoken.google.com/<projectId>", where <projectId> is the same project ID used for aud
Expand All @@ -47,6 +49,7 @@ public struct FirebaseJWTPayload: JWTPayload {
public let name: String?
public let isEmailVerified: Bool?
public let phoneNumber: String?
public let firebase: Firebase?

public func verify(using signer: JWTSigner) throws {
guard self.issuer.value.contains("securetoken.google.com") else {
Expand All @@ -60,3 +63,9 @@ public struct FirebaseJWTPayload: JWTPayload {
try self.expirationAt.verifyNotExpired()
}
}

public struct Firebase: Codable {
public let identities: [String: [String]]
public let sign_in_provider: String
}

28 changes: 28 additions & 0 deletions Sources/FirebaseJWTMiddleware/Request+FirebaseJWT.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
import Vapor

extension Request {

public var firebaseJwt: FirebaseJWT {
.init(request: self)
}

public struct FirebaseJWT {

let request: Request

public func verify(
Expand Down Expand Up @@ -54,5 +56,31 @@ extension Request {
return token
}
}

public func asyncVerify(applicationIdentifier: String? = nil) async throws -> FirebaseJWTPayload {
guard let token = self.request.headers.bearerAuthorization?.token else {
self.request.logger.error("Request is missing JWT bearer header.")
throw Abort(.unauthorized)
}
return try await self.asyncVerify(token, applicationIdentifier: applicationIdentifier)
}

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

public func asyncVerify<Message>(_ message: Message, applicationIdentifier: String? = nil) async throws -> FirebaseJWTPayload where Message: DataProtocol {

let signers = try await request.application.firebaseJwt.asyncSigners(on: request)

let token = try signers.verify(message, as: FirebaseJWTPayload.self)

if let applicationIdentifier = applicationIdentifier ?? self.request.application.firebaseJwt.applicationIdentifier {
try token.audience.verifyIntendedAudience(includes: applicationIdentifier)
}
return token
}

}

}