Understanding Authentication in Vapor: Part II
Yuriy Gudimov
iOS Developer | 3+ years | SwiftUI | UIKit | Combine | REST APIs | Vapor
In the first part of this series, we explored how to create a user model, implement basic authentication, and establish secure endpoints using Vapor’s ModelAuthenticatable. Now, let’s dive into the next steps: generating tokens for more secure user authentication, protecting routes, and using Vapor’s ModelTokenAuthenticatable.
Why Use Token-Based Authentication?
While Basic Authentication is straightforward, it’s not ideal for protecting all endpoints because it requires sending sensitive credentials (username and password) with every request. Token-based authentication resolves this by issuing a token during login that can be used for subsequent requests, improving both security and efficiency.
Setting Up the User Token Model
To enable token-based authentication, we first need a model to represent user tokens. Here’s an example:
final class UserToken: Model, Content {
static let schema = "user_tokens"
@ID(key: .id)
var id: UUID?
@Field(key: "value")
var value: String
@Parent(key: "user_id")
var user: User
init() { }
init(id: UUID? = nil, value: String, userID: User.IDValue) {
self.id = id
self.value = value
self.$user.id = userID
}
}
Each token is tied to a specific user and contains a value field, which holds the unique token string.
Migrating the User Token Model
To store user tokens in the database, create a migration:
extension UserToken {
struct Migration: AsyncMigration {
var name: String { "CreateUserToken" }
func prepare(on database: Database) async throws {
try await database.schema("user_tokens")
.id()
.field("value", .string, .required)
.field("user_id", .uuid, .required, .references("users", "id"))
.unique(on: "value")
.create()
}
func revert(on database: Database) async throws {
try await database.schema("user_tokens").delete()
}
}
}
Remember to add this migration to app.migrations:
app.migrations.add(UserToken.Migration())
Generating a Token
Now, let’s extend the User model to include a method for generating tokens:
extension User {
func generateToken() throws -> UserToken {
try .init(
value: [UInt8].random(count: 16).base64,
userID: self.requireID()
)
}
}
This generates a secure, random token that can be returned to the client upon login.
Implementing Login with Tokens
Update the login route to create and return a token upon successful authentication:
let passwordProtected = app.grouped(User.authenticator())
passwordProtected.post("login") { req async throws -> UserToken in
let user = try req.auth.require(User.self)
let token = try user.generateToken()
try await token.save(on: req.db)
return token
}
When a user logs in, they receive a token that can be used for further requests.
Protecting Routes with Tokens
To protect endpoints using the generated token, make the UserToken model conform to ModelTokenAuthenticatable:
extension UserToken: ModelTokenAuthenticatable {
static let valueKey = \UserToken.$value
static let userKey = \UserToken.$user
var isValid: Bool {
true
}
}
Now, you can use the UserToken authenticator to protect sensitive routes. For example:
let tokenProtected = app.grouped(UserToken.authenticator())
tokenProtected.get("me") { req -> User in
try req.auth.require(User.self)
}
This ensures that only authenticated users with a valid token can access the GET /me endpoint.
Testing
1. Login:
Send a POST request to /login with the user’s credentials. You’ll receive a token in response.
2. Access Protected Route:
Include the token in the Authorization header as a Bearer token to access protected routes:
Authorization: Bearer <token>
Conclusion
By introducing token-based authentication, you’ve taken a significant step towards building a secure and scalable API. This approach minimizes the need to repeatedly send sensitive credentials and provides a more efficient way to manage user sessions.
Feel free to leave your questions and thoughts in the comments, or share your experiences with authentication in Vapor!
iOS Developer | Avito | Experience 5+ years
1 个月Wow, this is so well explained! Now it’s clear why tokens are a must-have for APIs. Thanks for the simple and clear breakdown!
Data analyst
1 个月Insightful!
Tech Entrepreneur | Team Lead & Software Engineer | Author & Speaker | Follow for daily posts about Mindset, Personal Growth, and Leadership
1 个月Great continuation of the series! ?? Generating tokens for user authentication is crucial for enhancing security. I'm looking forward to seeing how you implement route protection and utilize Vapor's ModelTokenAuthenticatable. These steps are vital for building robust applications. Keep up the great work!
Frontend Developer @TechWings | React, TypeScript, JavaScript | Improving UX Through Scalable Solutions
1 个月Great series so far! Diving into token-based authentication with Vapor sounds exciting, secure endpoints and protected routes are crucial for robust apps. Looking forward to seeing how it all comes together!