Vapor Auth
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
- Vapor Authentication Documentation - Official documentation
- Vapor Security Guide - Security guide
- Vapor Middleware Documentation - Middleware guide
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"
}