Keychain adds a complete and customizable user authentication system to your API project.
Update your Package.swift
file.
.package(url: "https://github.com/nodes-vapor/keychain.git", from: "2.0.0")
targets: [
.target(
name: "App",
dependencies: [
...
.product(name: "Keychain", package: "keychain"),
]
),
...
]
These are the steps required to use Keychain in your project.
- Define a Payload that conforms to the
KeychainPayload
protocol - Create
KeychainConfig
objects for the key types you would like to use - Configure your
Keychain
using aSigner
and theKeychainConfig
objects defined in step 2 - Actually start using your
Keychain
Time to look at each step in detail.
Your payload must conform to the KeychainPayload
protocol, meaning that it must contain:
init(expirationDate: Date, user: User) throws
func findUser(request: Request) -> EventLoopFuture<User>
which is where you do a search for the user you were presented in theinit
methodfunc verify(using signer: JWTSigner) throws
which will verify that your token is still valid
Furthermore you need to tell your KeychainPayload
what its associatedtype
for User
translates to.
Here is an example that uses elements from a JWT token and verifies that the expiration (exp
) claim is not expired. Note that findUser
in this case only returns a test user. In real life you probably want to do a lookup somewhere where users are stored.
import JWT
import Keychain
import Vapor
struct UserJWTPayload: KeychainPayload {
let exp: ExpirationClaim
let sub: SubjectClaim
init(expirationDate: Date, user: User) {
self.exp = .init(value: expirationDate)
self.sub = .init(value: user.id)
}
func findUser(request: Request) -> EventLoopFuture<User> {
request.eventLoop.future(request.testUser).unwrap(or: TestError.userNotFound)
}
func verify(using signer: JWTSigner) throws {
try exp.verifyNotExpired()
}
}
Your KeychainConfig
objects must contain:
- an identifier (eg: access, refresh or reset):
jwkIdentifier
- an
expirationTimeInterval
And you need to connect your KeychainConfig
with the KeychainPayload
you defined in step 1 (the KeychainConfig
has a typealias
for a KeychainPayload
).
Here is an example creating three KeychainConfig
objects:
- A
UserAccessKeychainConfig
with the identifier "access" and anexpirationTimeInterval
of 300 seconds - A
UserRefreshKeychainConfig
with the identifier "refresh" and anexpirationTimeInterval
of 600 seconds - A
UserResetKeychainConfig
with the identifier "reset" and anexpirationTimeInterval
of 400 seconds
import JWT
import Keychain
struct UserAccessKeychainConfig: KeychainConfig, Equatable {
typealias JWTPayload = UserJWTPayload
static var jwkIdentifier: JWKIdentifier = "access"
let expirationTimeInterval: TimeInterval = 300
}
struct UserRefreshKeychainConfig: KeychainConfig, Equatable {
typealias JWTPayload = UserJWTPayload
static var jwkIdentifier: JWKIdentifier = "refresh"
let expirationTimeInterval: TimeInterval = 600
}
struct UserResetKeychainConfig: KeychainConfig, Equatable {
typealias JWTPayload = UserJWTPayload
static var jwkIdentifier: JWKIdentifier = "reset"
let expirationTimeInterval: TimeInterval = 400
}
Time to tie it all together! In your configure.swift
you can add multiple KeychainConfig
objects as seen here:
app.keychain.configure(
signer: .hs256(key: YourKeyGoesHere...ProbablyReadFromSomeEnvironment),
config: UserAccessKeychainConfig()
)
app.keychain.configure(
signer: JWTSigner(
algorithm: TestJWTAlgorithm(name: UserRefreshKeychainConfig.jwkIdentifier.string)
),
config: UserRefreshKeychainConfig()
)
app.keychain.configure(
signer: JWTSigner(
algorithm: TestJWTAlgorithm(name: UserResetKeychainConfig.jwkIdentifier.string)
),
config: UserResetKeychainConfig()
)
Note the signer
parameter. You can use one of the built-in signers as in the first example where we use the .hs256
signer with a key. Alternatively, you can provide your own signer as it is done in the last two examples.
With all the setup out of the way, it is time to kick back and take advantage of Keychain
. You can now use the UserAccessKeychainConfig
, UserRefreshKeychainConfig
and UserResetKeychainConfig
objects that you created previously to generate JWT tokens by calling the makeToken(on:, currentDate:)
Here is an example on how to generate a new refreshToken
.
import Keychain
struct UserController {
let currentDate: () -> Date
...
func refreshToken(request: Request) throws -> Response {
let token = try UserRefreshKeychainConfig.makeToken(on: request, currentDate: currentDate())
// here we encode the token string as JSON but you might include your token in a struct
// conforming to `Content`
let response = Response()
try response.content.encode(token, as: .json)
return response
}
}
This package is developed and maintained by the Vapor team at Monstarlab.
This package is open-sourced software licensed under the MIT license