Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NathanFlurry/0d3b6e8bf9b83ef4724794da52329fb0 to your computer and use it in GitHub Desktop.
Save NathanFlurry/0d3b6e8bf9b83ef4724794da52329fb0 to your computer and use it in GitHub Desktop.
Like `TokenAuthenticationMiddleware`, but can declare a private and public token for a single object.
import Fluent
import Authentication
/// Copy of `TokenAuthenticationMiddleware` middleware that uses classified tokens. This way, objects can use
/// private and public tokens.
public final class ClassifiedTokenAuthenticationMiddleware<U: TokenAuthenticatable>: Middleware {
public let isPrivate: Bool
public init(_ userType: U.Type = U.self, isPrivate: Bool) {
self.isPrivate = isPrivate
}
public func respond(to req: Request, chainingTo next: Responder) throws -> Response {
// Handle already authenticated
if req.auth.isAuthenticated(U.self) {
return try next.respond(to: req)
}
// Get the token
guard let token = req.auth.header?.bearer else {
throw AuthenticationError.invalidCredentials
}
// Classify token so authenticator knows where to look
let classifiedToken = token.classified(isPrivate: isPrivate)
// Do authentication
let u = try U.authenticate(classifiedToken)
req.auth.authenticate(u)
return try next.respond(to: req)
}
}
/// Extension of token in order to make it be able to depict whether it's public or private.
extension Token {
static let publicPrefix = "[public]"
static let privatePrefix = "[private]"
public func classified(isPrivate: Bool) -> Token {
return Token(string: "\(isPrivate ? Token.privatePrefix : Token.publicPrefix)\(string)")
}
}
/// Add protocol to make it simple to implement classified token authentication.
protocol ClassifiedTokenAuthenticatable: TokenAuthenticatable {
/// Key for the public token value.
static var publicTokenKey: String { get }
/// Key for the private token value.
static var privateTokenKey: String { get }
}
/// Add shared code to parse tokens.
extension ClassifiedTokenAuthenticatable {
static func parseToken(_ token: Token) throws -> (tokenKey: String, originalToken: String) {
// Find the token key for the classification
let tokenKey: String
let tokenPrefix: String
if token.string.hasPrefix(Token.publicPrefix) {
tokenKey = publicTokenKey
tokenPrefix = Token.publicPrefix
} else if token.string.hasPrefix(Token.privatePrefix) {
tokenKey = privateTokenKey
tokenPrefix = Token.privatePrefix
} else {
throw AuthenticationError.invalidBearerAuthorization
}
// Get the original token by slicing off the prefix
let fromIndex = token.string.index(token.string.startIndex, offsetBy: tokenPrefix.characters.count)
let originalToken = token.string.substring(from: fromIndex)
return (tokenKey, originalToken)
}
}
extension ClassifiedTokenAuthenticatable where Self: Entity, Self.TokenType: Entity {
static func authenticate(_ token: Token) throws -> Self {
// Parse the token
let (tokenKey, originalToken) = try parseToken(token)
// Find the user
guard let user = try Self.makeQuery()
.join(Self.TokenType.self)
.filter(Self.TokenType.self, tokenKey, originalToken)
.first()
else {
throw AuthenticationError.invalidCredentials
}
return user
}
}
extension ClassifiedTokenAuthenticatable where Self: Entity, Self.TokenType: Entity, Self.TokenType == Self {
static func authenticate(_ token: Token) throws -> Self {
// Parse the token
let (tokenKey, originalToken) = try parseToken(token)
// Find the user
guard let user = try Self.makeQuery()
.filter(tokenKey, originalToken)
.first()
else {
throw AuthenticationError.invalidCredentials
}
return user
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment