API Authentication¶
This guide will introduce you to stateless authentication—a method of authentication commonly used for protecting API endpoints.
Concept¶
In Computer Science (especially web frameworks), the concept of Authentication means verifying the identity of a user. This is not to be confused with Authorization which verifies privileges to a given resource
This package allows you to implement stateless authorization using the following tools:
"Authorization"
header: Used to send credentials in an HTTP request.- Middleware: Detects credentials in request and fetches authenticated user.
- Model: Represents an authenticated user and its identifying information.
Authorization Header¶
This packages makes use of two common authorization header formats: basic and bearer.
Basic¶
Basic authorization contains a username and password. They are joined together by a :
and then base64 encoded.
A basic authorization header containing the username Alladin
and password OpenSesame
would look like this:
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
Although basic authorization can be used to authenticate each request to your server, most web applications usually create an ephemeral token for this purpose instead.
Bearer¶
Bearer authorization simply contains a token. A bearer authorization header containing the token cn389ncoiwuencr
would look like this:
Authorization: Bearer cn389ncoiwuencr
The bearer authorization header is very common in APIs since it can be sent easily with each request and contain an ephemeral token.
Middleware¶
The usage of Middleware is critical to this package. If you are not familiar with how Middleware works in Vapor, feel free to brush up by reading Vapor → Middleware.
Authentication middleware is responsible for reading the credentials from the request and fetching the identifier user. This usually means checking the "Authorization"
header, parsing the credentials, and doing a database lookup.
For each model / authentication method you use, you will add one middleware to your application. All of this package's middlewares are composable, meaning you can add multiple middlewares to one route and they will work together. If one middleware fails to authorize a user, it will simply forward the request for the next middleware to try.
If you would like to ensure that a certain model's authentication has succeeded before running your route, you must add an instance of GuardAuthenticationMiddleware
.
Model¶
Fluent models are what the middlewares authenticate. Learn more about models by reading Fluent → Models. If authentication is successful, the middleware will have fetched your model from the database and stored it on the request. This means you can access an authenticated model synchronously in your route.
In your route closure, you use the following methods to check for authentication:
authenticated(_:)
: Returns type if authenticated,nil
if not.isAuthenticated(_:)
: Returnstrue
if supplied type is authenticated.requireAuthenticated(_:)
: Returns type if authenticated,throws
if not.
Typical usage looks like the following:
// use middleware to protect a group let protectedGroup = router.group(...) // add a protected route protectedGroup.get("test") { req in // require that a User has been authed by middleware or throw let user = try req.requireAuthenticated(User.self) // say hello to the user return "Hello, \(user.name)." }
Methods¶
This package supports two basic types of stateless authentication.
- Token: Uses the bearer authorization header.
- Password: Uses the basic authorization header.
For each authentication type, there is a separate middleware and model protocol.
Password Authentication¶
Password authentication uses the basic authorization header (username and password) to verify a user. With this method, the username and password must be sent with each request to a protected endpoint.
To use password authentication, you will first need to conform your Fluent model to PasswordAuthenticatable
.
extension User: PasswordAuthenticatable { /// See `PasswordAuthenticatable`. static var usernameKey: WritableKeyPath<User, String> { return \.email } /// See `PasswordAuthenticatable`. static var passwordKey: WritableKeyPath<User, String> { return \.passwordHash } }
Note that the passwordKey
should point to the hashed password. Never store passwords in plaintext.
Once you have created an authenticatable model, the next step is to add middleware to your protected route.
// Use user model to create an authentication middleware let password = User.basicAuthMiddleware(using: BCryptDigest()) // Create a route closure wrapped by this middleware router.grouped(password).get("hello") { req in /// }
Here we are using BCryptDigest
as the PasswordVerifier
since we are assuming the user's password is stored as a BCrypt hash.
Now, to fetch the authenticated user in the route closure, you can use requireAuthenticated(_:)
.
let user = try req.requireAuthenticated(User.self) return "Hello, \(user.name)."
The requireAuthenticated
method will automatically throw an appropriate unauthorized error if the valid credentials were not supplied. Because of this, using GuardAuthenticationMiddleware
to protect the route from unauthenticated access is not required.
Token Authentication¶
Token authentication uses the bearer authorization header (token) to lookup a token and its related user. With this method, the token must be sent with each request to a protected endpoint.
Unlike password authentication, token authentication relies on two Fluent models. One for the token and one for the user. The token model should be a child of the user model.
Here is an example of a very basic User
and associated UserToken
.
struct User: Model { var id: Int? var name: String var email: String var passwordHash: String var tokens: Children<User, UserToken> { return children(\.userID) } } struct UserToken: Model { var id: Int? var string: String var userID: User.ID var user: Parent<UserToken, User> { return parent(\.userID) } }
The first step to using token authentication is to conform your user and token models to their respective Authenticatable
protocols.
extension UserToken: Token { /// See `Token`. typealias UserType = User /// See `Token`. static var tokenKey: WritableKeyPath<UserToken, String> { return \.string } /// See `Token`. static var userIDKey: WritableKeyPath<UserToken, User.ID> { return \.userID } }
Once the token is conformed to Token
, setting up the user model is easy.
extension User: TokenAuthenticatable { /// See `TokenAuthenticatable`. typealias TokenType = UserToken }
Once you have conformed your models, the next step is to add middleware to your protected route.
// Use user model to create an authentication middleware let token = User.tokenAuthMiddleware() // Create a route closure wrapped by this middleware router.grouped(token).get("hello") { // }
Now, to fetch the authenticated user in the route closure, you can use requireAuthenticated(_:)
.
let user = try req.requireAuthenticated(User.self) return "Hello, \(user.name)."
The requireAuthenticated
method will automatically throw an appropriate unauthorized error if the valid credentials were not supplied. Because of this, using GuardAuthenticationMiddleware
to protect the route from unauthenticated access is not required.