Vapor Auth

authentication-librarySwiftVaporserver-sidemiddlewareJWTsessionsauthenticationauthorization

Authentication Library

Vapor Auth

Overview

Vapor Auth is the authentication and authorization system built into Swift's Vapor framework. It supports various authentication methods including Basic authentication, Bearer authentication, session authentication, and JWT authentication, providing flexible and extensible security features through middleware-based design. Integrated since Vapor 4, it's available without additional dependencies.

Details

Vapor Auth is designed around the Authenticator protocol, extracting authentication information from requests to identify users. Operating as middleware, it stores user information in req.auth upon successful authentication. Combined with Guard Middleware, it can protect routes that require authentication.

Key Components

  • Authenticator: Middleware protocol defining authentication methods
  • BasicAuthenticator: Helper for Basic authentication
  • BearerAuthenticator: Helper for Bearer authentication
  • CredentialsAuthenticator: Request body-based authentication
  • Guard Middleware: Enforced authentication checks

Authentication Methods

  • Basic Authentication: Username and password authentication
  • Bearer Authentication: Token-based authentication (JWT, API tokens)
  • Session Authentication: Integration with session middleware
  • Credentials Authentication: Custom credential authentication
  • JWT Authentication: JSON Web Token authentication

Authorization Features

  • Role-Based Access Control: Authorization by user roles
  • Custom Authorization Logic: Custom authorization rule implementation
  • Route-Level Protection: Authentication requirements for specific routes
  • Group-Level Protection: Bulk authentication for route groups

Pros and Cons

Pros

  • Built-in System: Integrated into Vapor 4, no additional dependencies
  • Type Safety: Safe implementation leveraging Swift's type system
  • Middleware Design: Flexible and configurable architecture
  • Multiple Authentication Methods: Rich authentication options
  • Async Support: Latest Swift features with async/await support
  • Extensibility: Easy implementation of custom authentication methods

Cons

  • Swift/Vapor Only: Limited to Vapor framework
  • Learning Curve: Requires knowledge of Server-side Swift
  • Ecosystem: Limited Server-side Swift ecosystem
  • Configuration Complexity: Advanced authentication setup requires expertise

Reference Links

Code Examples

Basic User Model

import Vapor

struct User: Authenticatable {
    var id: UUID?
    var name: String
    var email: String
    var passwordHash: String
}

// Database-enabled version
import Fluent

final class User: Model, Content, Authenticatable {
    static let schema = "users"
    
    @ID(key: .id)
    var id: UUID?
    
    @Field(key: "name")
    var name: String
    
    @Field(key: "email")
    var email: String
    
    @Field(key: "password_hash")
    var passwordHash: String
    
    init() { }
    
    init(id: UUID? = nil, name: String, email: String, passwordHash: String) {
        self.id = id
        self.name = name
        self.email = email
        self.passwordHash = passwordHash
    }
}

Basic Authentication Implementation

import Vapor

struct UserAuthenticator: BasicAuthenticator {
    typealias User = App.User

    func authenticate(
        basic: BasicAuthorization,
        for request: Request
    ) -> EventLoopFuture<Void> {
        // Verify username and password
        if basic.username == "admin" && basic.password == "secret" {
            let user = User(name: "Admin User", email: "[email protected]")
            request.auth.login(user)
        }
        return request.eventLoop.makeSucceededFuture(())
    }
}

// async/await version
struct AsyncUserAuthenticator: AsyncBasicAuthenticator {
    typealias User = App.User

    func authenticate(
        basic: BasicAuthorization,
        for request: Request
    ) async throws {
        if basic.username == "admin" && basic.password == "secret" {
            let user = User(name: "Admin User", email: "[email protected]")
            request.auth.login(user)
        }
    }
}

Bearer Authentication Implementation

import Vapor

struct TokenAuthenticator: BearerAuthenticator {
    typealias User = App.User

    func authenticate(
        bearer: BearerAuthorization,
        for request: Request
    ) -> EventLoopFuture<Void> {
        // Token verification
        if bearer.token == "valid-api-token" {
            let user = User(name: "API User", email: "[email protected]")
            request.auth.login(user)
        }
        return request.eventLoop.makeSucceededFuture(())
    }
}

// async/await version
struct AsyncTokenAuthenticator: AsyncBearerAuthenticator {
    func authenticate(
        bearer: BearerAuthorization,
        for request: Request
    ) async throws {
        if bearer.token == "valid-api-token" {
            let user = User(name: "API User", email: "[email protected]")
            request.auth.login(user)
        }
    }
}

Route Protection Setup

import Vapor

func routes(_ app: Application) throws {
    // Routes without authentication
    app.get("public") { req in
        return "This is public"
    }
    
    // Routes protected by Basic authentication
    let basicProtected = app.grouped(UserAuthenticator())
        .grouped(User.guardMiddleware())
    
    basicProtected.get("protected") { req -> String in
        let user = try req.auth.require(User.self)
        return "Hello, \(user.name)!"
    }
    
    // API protected by Bearer authentication
    let bearerProtected = app.grouped(TokenAuthenticator())
        .grouped(User.guardMiddleware())
    
    bearerProtected.get("api", "data") { req -> String in
        let user = try req.auth.require(User.self)
        return "API data for \(user.name)"
    }
}

Multiple Authentication Methods Combination

import Vapor

func routes(_ app: Application) throws {
    // Combine multiple authentication methods
    let multiAuth = app.grouped([
        UserAuthenticator(),      // Basic authentication
        TokenAuthenticator(),     // Bearer authentication
        User.guardMiddleware()    // Either authentication required
    ])
    
    multiAuth.get("secure") { req -> String in
        let user = try req.auth.require(User.self)
        return "Authenticated as \(user.name)"
    }
}

Session Authentication Implementation

import Vapor

// Session authentication configuration
func configure(_ app: Application) throws {
    // Session middleware setup
    app.middleware.use(app.sessions.middleware)
    
    // Register session authenticator
    app.middleware.use(User.sessionAuthenticator())
}

// User extension for session authentication
extension User: SessionAuthenticatable {
    var sessionID: UUID {
        return self.id!
    }
}

// Login handler
func loginHandler(_ req: Request) throws -> EventLoopFuture<Response> {
    let credentials = try req.content.decode(UserCredentials.self)
    
    return User.authenticate(credentials: credentials, on: req.db)
        .map { user in
            if let user = user {
                req.auth.login(user)
                return req.redirect(to: "/dashboard")
            } else {
                return req.redirect(to: "/login?error=invalid")
            }
        }
}

// Logout handler
func logoutHandler(_ req: Request) -> Response {
    req.auth.logout(User.self)
    return req.redirect(to: "/")
}

JWT Authentication Implementation

import Vapor
import JWT

// JWT payload
struct UserPayload: JWTPayload {
    let userID: UUID
    let exp: ExpirationClaim
    
    func verify(using signer: JWTSigner) throws {
        try self.exp.verifyNotExpired()
    }
}

// JWT authenticator
struct JWTAuthenticator: JWTAuthenticator {
    typealias Payload = UserPayload
    
    func authenticate(jwt: UserPayload, for request: Request) -> EventLoopFuture<Void> {
        return User.find(jwt.userID, on: request.db).map { user in
            if let user = user {
                request.auth.login(user)
            }
        }
    }
}

// JWT protected route setup
func routes(_ app: Application) throws {
    let jwtProtected = app.grouped(JWTAuthenticator())
        .grouped(User.guardMiddleware())
    
    jwtProtected.get("profile") { req -> EventLoopFuture<User> in
        let user = try req.auth.require(User.self)
        return req.eventLoop.makeSucceededFuture(user)
    }
}

Custom Authentication Middleware

import Vapor

struct AdminMiddleware: Middleware {
    func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
        guard let user = request.auth.get(User.self),
              user.isAdmin else {
            return request.eventLoop.makeFailedFuture(Abort(.unauthorized))
        }
        return next.respond(to: request)
    }
}

// async/await version
struct AsyncAdminMiddleware: AsyncMiddleware {
    func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
        guard let user = request.auth.get(User.self),
              user.isAdmin else {
            throw Abort(.unauthorized)
        }
        return try await next.respond(to: request)
    }
}

// Usage example
let adminRoutes = app.grouped(User.sessionAuthenticator())
    .grouped(User.guardMiddleware())
    .grouped(AdminMiddleware())

adminRoutes.get("admin", "dashboard") { req in
    return "Admin Dashboard"
}

Error Handling

import Vapor

// Authentication error handling
func protectedRoute(_ req: Request) throws -> String {
    do {
        let user = try req.auth.require(User.self)
        return "Hello, \(user.name)!"
    } catch {
        throw Abort(.unauthorized, reason: "Authentication required")
    }
}

// Custom error response
struct AuthenticationError: AbortError {
    let status: HTTPResponseStatus = .unauthorized
    let reason = "Invalid authentication credentials"
}