Vapor Auth
認証ライブラリ
Vapor Auth
概要
Vapor Authは、SwiftのVaporフレームワークに内蔵された認証・認可システムです。Basic認証、Bearer認証、セッション認証、JWT認証など多様な認証方式をサポートし、ミドルウェアベースの設計により柔軟で拡張可能なセキュリティ機能を提供します。Vapor 4から統合されており、追加の依存関係なしで利用可能です。
詳細
Vapor Authは、Authenticatorプロトコルを中心とした設計で、リクエストから認証情報を抽出してユーザーを識別します。ミドルウェアとして動作し、認証の成功時にはreq.authにユーザー情報を格納します。Guard Middlewareと組み合わせることで、認証が必須のルートを保護できます。
主要コンポーネント
- Authenticator: 認証方式を定義するミドルウェアプロトコル
- BasicAuthenticator: Basic認証のヘルパー
- BearerAuthenticator: Bearer認証のヘルパー
- CredentialsAuthenticator: リクエストボディベース認証
- Guard Middleware: 認証チェックの強制実行
認証方式
- Basic Authentication: ユーザー名とパスワードによる認証
- Bearer Authentication: トークンベース認証(JWT、APIトークン)
- Session Authentication: セッションミドルウェアとの連携
- Credentials Authentication: カスタムクレデンシャル認証
- JWT Authentication: JSON Web Token認証
認可機能
- ロールベースアクセス制御: ユーザーロールによる認可
- カスタム認可ロジック: 独自の認可ルール実装
- ルートレベル保護: 特定ルートの認証要求
- グループレベル保護: ルートグループの一括認証
メリット・デメリット
メリット
- 内蔵システム: Vapor 4に統合、追加依存なし
- 型安全: Swiftの型システムを活用した安全な実装
- ミドルウェア設計: 柔軟で構成可能なアーキテクチャ
- 多様な認証方式: 豊富な認証オプション
- 非同期対応: async/awaitでの最新Swift機能サポート
- 拡張性: カスタム認証方式の容易な実装
デメリット
- Swift/Vapor専用: Vaporフレームワーク限定
- 学習コスト: Server-side Swiftの知識が必要
- エコシステム: Server-side Swiftエコシステムが限定的
- 設定複雑さ: 高度な認証設定には専門知識が必要
参考ページ
- Vapor Authentication Documentation - 公式ドキュメント
- Vapor Security Guide - セキュリティガイド
- Vapor Middleware Documentation - ミドルウェアガイド
書き方の例
基本的なユーザーモデル
import Vapor
struct User: Authenticatable {
var id: UUID?
var name: String
var email: String
var passwordHash: String
}
// データベース対応の場合
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認証の実装
import Vapor
struct UserAuthenticator: BasicAuthenticator {
typealias User = App.User
func authenticate(
basic: BasicAuthorization,
for request: Request
) -> EventLoopFuture<Void> {
// ユーザー名とパスワードの検証
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版
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認証の実装
import Vapor
struct TokenAuthenticator: BearerAuthenticator {
typealias User = App.User
func authenticate(
bearer: BearerAuthorization,
for request: Request
) -> EventLoopFuture<Void> {
// トークンの検証
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版
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)
}
}
}
ルート保護の設定
import Vapor
func routes(_ app: Application) throws {
// 認証不要のルート
app.get("public") { req in
return "This is public"
}
// Basic認証で保護されたルート
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)!"
}
// Bearer認証で保護されたAPI
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)"
}
}
複数認証方式の組み合わせ
import Vapor
func routes(_ app: Application) throws {
// 複数の認証方式を組み合わせ
let multiAuth = app.grouped([
UserAuthenticator(), // Basic認証
TokenAuthenticator(), // Bearer認証
User.guardMiddleware() // どちらかの認証が必須
])
multiAuth.get("secure") { req -> String in
let user = try req.auth.require(User.self)
return "Authenticated as \(user.name)"
}
}
セッション認証の実装
import Vapor
// セッション認証の設定
func configure(_ app: Application) throws {
// セッションミドルウェアの設定
app.middleware.use(app.sessions.middleware)
// セッション認証子の登録
app.middleware.use(User.sessionAuthenticator())
}
// セッション認証用のユーザー拡張
extension User: SessionAuthenticatable {
var sessionID: UUID {
return self.id!
}
}
// ログイン処理
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")
}
}
}
// ログアウト処理
func logoutHandler(_ req: Request) -> Response {
req.auth.logout(User.self)
return req.redirect(to: "/")
}
JWT認証の実装
import Vapor
import JWT
// JWTペイロード
struct UserPayload: JWTPayload {
let userID: UUID
let exp: ExpirationClaim
func verify(using signer: JWTSigner) throws {
try self.exp.verifyNotExpired()
}
}
// JWT認証器
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保護ルートの設定
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)
}
}
カスタム認証ミドルウェア
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版
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)
}
}
// 使用例
let adminRoutes = app.grouped(User.sessionAuthenticator())
.grouped(User.guardMiddleware())
.grouped(AdminMiddleware())
adminRoutes.get("admin", "dashboard") { req in
return "Admin Dashboard"
}
エラーハンドリング
import Vapor
// 認証エラーの処理
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")
}
}
// カスタムエラーレスポンス
struct AuthenticationError: AbortError {
let status: HTTPResponseStatus = .unauthorized
let reason = "Invalid authentication credentials"
}