vapor / auth Goto Github PK
View Code? Open in Web Editor NEWπ€ Authentication and Authorization framework for Fluent.
π€ Authentication and Authorization framework for Fluent.
Here is the Package.swift
file
let package = Package(
name: "Foo",
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.3"),
.package(url: "https://github.com/vapor/fluent.git",from: "3.0.0-rc.2.4.1"),
.package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0-rc.2.3"),
.package(url: "https://github.com/vapor/auth.git", from: "2.0.0-rc.4.1"),
.package(url: "https://github.com/vapor/leaf.git", from: "3.0.0-rc.2.2"),
],
targets: [
.target(name: "App", dependencies: [
"Vapor",
"Fluent",
"FluentPostgreSQL",
"Auth",
"Leaf",
]),
.target(name: "Run", dependencies: ["App"]),
.testTarget(name: "AppTests", dependencies: ["App"])
]
)
and the eror log
Generating Xcode Project [Failed]
'Auth' /Users/h172462/Desktop/Foo: error: product dependency 'Auth' not found
warning: dependency 'Auth' is not used by any target
Error: Could not generate Xcode project: 'Foo' /Users/h172462/Desktop/Foo: error: product dependency 'Auth' not found
warning: dependency 'Auth' is not used by any target
In 2.0.0-RC.4, work was done to allow multiple authentication middlewares to exist for a route optionally. However, for TokenAuthenticationMiddleware the following line still throws an Abort:
This should be try?
so TokenAuthenticationMiddleware works optionally as intended.
Protected routes can still be hit after calling unauthenticateSession(_:)
.
It do set cache to nil
, but only for current request, not globally.
Configurate:
// ...
var middlewares = MiddlewareConfig()
middlewares.use(SessionsMiddleware.self)
services.register(middlewares)
// ...
var migrations = MigrationConfig()
migrations.add(model: User.self, database: .mysql)
services.register(migrations)
// ...
Model:
final class User: MySQLModel, Migration, PasswordAuthenticatable, SessionAuthenticatable {
var email: String
var password: String
// ...
static var usernameKey: UsernameKey = \User.email
static var passwordKey: PasswordKey = \User.password
}
Controller:
let authSessionRoutes = router.grouped(User.authSessionsMiddleware())
authSessionRoutes.get("login", use: makeLoginView)
authSessionRoutes.post("login", use: login)
let protectedRoutes = authSessionRoutes.grouped(RedirectMiddleware<User>(path: "/login"))
protectedRoutes.get("logout", use: logout)
protectedRoutes.get("/", use: makeHomeView)
// ...
Route:
func login(_ req: Request) throws -> Future<Response> {
// ...
try req.authenticateSession(authed)
}
func logout(_ req: Request) throws -> Response {
try req.unauthenticateSession(User.self)
return req.redirect(to: "/login")
}
/
route, and redirected to /login
./logout
route./
route.Redirected to /login
.
Hit protected /
and not redirecting.
To prevent a database dump from revealing access to user accounts.
I'm getting a crash when I use the middleware in the beta branch of Auth. Specifically when requesting a route which has been protected with basicAuthMiddleware
or tokenAuthMiddleware
.
Given a User
model which is BasicAuthenticatable
:
extension User: BasicAuthenticatable {
static let usernameKey = \User.email
static let passwordKey = \User.password
}
β¦And a route which uses the middleware:
let authMiddleware = try User.basicAuthMiddleware(using: PlaintextVerifier(), database: .psql)
let group = router.grouped(authMiddleware)
group.get("test"){ req -> String in
return "OK"
}
β¦Requesting /test
results in a crash when DatabaseKit tries to use a nil
connection.
DatabaseKit throws an exception in line 21 of /Connection/DatabaseConnection.swift
:
extension DatabaseConnection {
/// See `DatabaseConnectable.connect(to:)`
public func connect<D>(to database: DatabaseIdentifier<D>?) -> Future<D.Connection> {
assert(database == nil, "Unexpected \(#function): nil database identifier") // π¨π¨π¨
assert(self is D.Connection, "Unexpected \(#function): \(self) not \(D.Connection.self)")
return Future(self as! D.Connection)
}
}
π¨: codes.vapor.application (3): Assertion failed: Unexpected connect(to:): nil database identifier
/App/Setup/Configure.swift
/api/v1/register
with correct details (or use the Paw file, api-template.paw
)/api/v1/me
with basic auth details (Get me with basic Auth
in Paw)
Consider the scenario where not all users have username/passwords and thus those properties are optional.
Currently it's not possible to conform the below model to the BasicAuthenticable protocol because you need to supply WritableKeyPath<User, String> keys, not WritableKeyPath<User, String?>.
final class User : MySQLModel {
var id: Int?
var username: String?
var passwordHash: String?
}
What do you think about allowing username /password fields of type WritableKeyPath<User, String?> to also conform to BasicAuthenticable?
I think this can be pulled off like so:
public protocol BasicAuthenticatableKeyType { }
extension String : BasicAuthenticatableKeyType {}
extension Optional : BasicAuthenticatableKeyType where Wrapped == String {}
/// Authenticatable by `Basic username:password` auth.
public protocol BasicAuthenticatable: Authenticatable {
associatedtype UsernameKeyType: BasicAuthenticatableKeyType
associatedtype PasswordKeyType: BasicAuthenticatableKeyType
/// Key path to the username
typealias UsernameKey = WritableKeyPath<Self, UsernameKeyType>
/// The key under which the user's username,
/// email, or other identifing value is stored.
static var usernameKey: UsernameKey { get }
/// Key path to the password
typealias PasswordKey = WritableKeyPath<Self, PasswordKeyType>
/// The key under which the user's password
/// is stored.
static var passwordKey: PasswordKey { get }
/// Authenticates using the supplied credentials, connection, and verifier.
static func authenticate(using basic: BasicAuthorization, verifier: PasswordVerifier, on connection: DatabaseConnectable) -> Future<Self?>
var basicPassword: String? { get }
}
extension BasicAuthenticatable where PasswordKeyType == String {
public var basicPassword: String? { return self[keyPath: Self.passwordKey] }
}
extension BasicAuthenticatable where PasswordKeyType == String? {
public var basicPassword: String? { return self[keyPath: Self.passwordKey] }
}
extension BasicAuthenticatable where Self: Model, Self.Database: QuerySupporting {
public static func authenticate(using basic: BasicAuthorization, verifier: PasswordVerifier, on conn: DatabaseConnectable) -> Future<Self?> {
do {
let filter = ModelFilter<Self>(filter: try QueryFilter(field: usernameKey.makeQueryField(), type: QueryFilterType<Self.Database>.equals, value: .data(basic.username)))
return Self.query(on: conn).filter(filter).first().map(to: Self?.self) { user in
guard let user = user, let basicPassword = user.basicPassword, try verifier.verify(basic.password, created: basicPassword) else {
return nil
}
return user
}
} catch {
return conn.eventLoop.newFailedFuture(error: error)
}
}
}
Providing a JWT auth middleware by default could be a nice addition to this package. Vapor's JWT package would be a lightweight dep since Auth already relies on Crypto.
final class JWTAuthenticationMiddleware<U>: Middleware where U: Authenticatable & JWTPayload {
let signer: JWTSigner
init(_ type: U.Type, signer: JWTSigner) {
self.signer = signer
}
/// See `Middleware`.
func respond(to req: Request, chainingTo next: Responder) throws -> EventLoopFuture<Response> {
// fetches the token from `Authorization: Bearer <token>` header
guard let bearer = req.http.headers.bearerAuthorization else {
// no authorization header, pass along un-authenticated request
return try next.respond(to: req)
}
// parse JWT from token string, using configured signer
let jwt = try JWT<U>(from: bearer.token, verifiedUsing: signer)
try req.authenticate(jwt.payload)
// pass along authenticated request
return try next.respond(to: req)
}
}
When trying to compile, using the vapor build command I got this error.
Sources/AuthProvider/RedirectMiddleware.swift:10:30: error: use of undeclared type 'RedirectType'
public let redirectType: RedirectType
it's not easy to reproduce because works fine on my macOS and on my docker image running Linux but doesn't work at AWS ECS FARGATE.
also, my implementation is made following this article
https://medium.com/@martinlasek/tutorial-how-to-build-web-auth-with-session-f9f64ba49830
and again, it works fine locally and on my docker image running ubuntu:16.04 and swift:4.1.3
Description
When deploying project on ubuntu on a VPS, I ran into timeouts on a route that uses a Fluent SQLite Model.
The project is running fine when ran on Mac.
Environment
Conditions (based on my observations)
Here is a zip containing a sample project (reduced from my project), in which the login post route is affected by the issue :
Workshop copie.zip
Protected routes can still be hit after calling unauthenticate(_:)
Configurate:
// ...
try services.register(AuthenticationProvider())
// ...
var migrations = MigrationConfig()
migrations.add(model: User.self, database: .sqlite)
migrations.add(model: Token.self, database: .sqlite)
// ...
Models:
final class User: SQLiteUUIDModel, Migration, Content, Parameter, BasicAuthenticatable, TokenAuthenticatable {
var id: UUID?
var username: String
var password: String
init(username: String, password: String) {
self.username = username
self.password = password
}
static let usernameKey: UsernameKey = \.username
static let passwordKey: PasswordKey = \.password
}
final class Token: SQLiteUUIDModel, Migration, Content, Authentication.Token, BearerAuthenticatable {
var id: UUID?
var token: String
var userID: User.ID
init(token: String, userID: User.ID) {
self.token = token
self.userID = userID
}
typealias UserType = User
static let userIDKey: UserIDKey = \.userID
static let tokenKey: TokenKey = \.token
static func prepare(on connection: SQLiteConnection) -> Future<Void> {
return Database.create(self, on: connection) { builder in
try addProperties(to: builder)
try builder.addReference(from: \.userID, to: \User.id)
}
}
static func generate(for user: User) throws -> Token {
let random = try CryptoRandom().generateData(count: 16)
return try Token(token: random.base64EncodedString(), userID: user.requireID())
}
}
Controller:
func boot(router: Router) throws {
let usersRoutes = router.grouped("users")
usersRoutes.post(User.self, use: createHandler)
let basicAuthMiddleware = User.basicAuthMiddleware(using: BCryptDigest())
let basicAuthGroup = usersRoutes.grouped(basicAuthMiddleware)
basicAuthGroup.post("login", use: loginHandler)
let tokenAuthMiddleware = User.tokenAuthMiddleware()
let tokenAuthGroup = usersRoutes.grouped(tokenAuthMiddleware)
tokenAuthGroup.delete(use: logoutHandler)
}
func createHandler(_ req: Request, user: User) throws -> Future<User> {
let hasher = try req.make(BCryptDigest.self)
user.password = try hasher.hash(user.password)
return user.save(on: req)
}
func loginHandler(_ request: Request) throws -> Future<Token> {
let user = try request.requireAuthenticated(User.self)
let token = try Token.generate(for: user)
return token.save(on: request)
}
func logoutHandler(_ request: Request) throws -> HTTPStatus {
guard try request.isAuthenticated(User.self) else { throw Abort(.unauthorized) }
try request.unauthenticate(User.self)
return .ok
}
Steps:
POST /users
POST /users/login
DELETE /users
DELETE /users
401 Unauthorized
200 OK
Error appeared in
auth-provider.git-2099911502650186246/Sources/AuthProvider/InverseRedirectMiddleware.swift:10:30
Hi!
I'm trying to use TokenAuthenticatable
for a user model, however, when running the app I'm getting this log:
[ ERROR ] ServiceError.make: No services are available for 'AuthenticationCache'. (Container.swift:112)
[ DEBUG ] Suggested fixes for ServiceError.make: Register a service for 'AuthenticationCache'. `services.register(AuthenticationCache.self) { ... }`. (Logger+LogError.swift:20)
After trying to register the AuthenticationCache
service in my configuration I'm getting a Use of unresolver identifier
error, because AuthenticationCache
is not public:
/// Stores authenticated objects. This should be created
/// using the request container as a singleton. Authenticated
/// objects can then be stored here by middleware and fetched
/// later in route closures.
final class AuthenticationCache: Service { ... }
Should this class be public? or maybe I'm doing this wrong, I'm pretty new with vapor and I can't find many guides or references about this.
Thank you.
I have the following testing Vapor App:
import PackageDescription
let package = Package(
name: "VaporKirchenmusikServer",
platforms: [
.macOS(.v10_14)
],
dependencies: [
// π§ A server-side Swift web framework.
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-beta"),
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-beta"),
.package(url: "https://github.com/vapor/auth.git", .branch("master")),
//.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0-beta")
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0-beta")
],
targets: [
.target(name: "App", dependencies: ["Fluent", "FluentSQLiteDriver", "Vapor", "OpenCrypto", "Auth"]),
.target(name: "Run", dependencies: ["App"]),
.testTarget(name: "AppTests", dependencies: ["App"])
]
)
vapor-beta build
is working eternal on this.
At the moment, AuthenticationSessionsMiddleware
calls A.authenticate(sessionID: aID, on: req)
, which reserves a database connection for the lifetime of the request. For long-running requests that only use pooled database connections elsewhere, this can cause DB connection count bottlenecks with e.g. Postgres. This problem is exacerbated by the fact that AuthenticationSessionsMiddleware
might run on every request. (Postgres has very strict connection limits by default.)
In fact, the following has happened to me in production:
My database connection pool size is 3. I send three concurrent requests to the server. The middleware flow is like this:
SessionsMiddleware
is invoked and in turn calls DatabaseKeyedCache.get
, which uses a connection pool.AuthenticationSessionsMiddleware
authenticates the user and reserves a database connection for the lifetime of the request.SessionsMiddleware
calls DatabaseKeyedCache.set
in addCookies
, again using a connection pool.Now, if all three database connections are taken by AuthenticationSessionsMiddleware
after step 2, requesting another connection from the pool will block (because the pool has been exhausted by the cached connections), resulting in a deadlock on all three requests. Especially if DB calls have non-negligible latency (e.g. 50-100ms) due to the DB and the Vapor server not running on the same machine, this can be problematic, even with slightly higher connection pool sizes.
To solve this, I would suggest using a pooled connection for authenticating the user. This might make changes to SessionAuthenticatable
necessary, though.
When using AuthenticationSessionsMiddleware
it is not possible to delete sessions because of the way #52 has been fixed. I think I have found the problem, but I am not entirely sure. I don't think I am the first since this could be the same: vapor/vapor#1667 and vapor/vapor#1661.
I am also using Redis as sessions storage.
When calling req.destroySession()
and req.unauthenticate(User.self)
the session and authenticated user in the cache are set to nil. This works as expected, however, when the AuthenticationSessionsMiddleware
responds, it sees that the user is no longer authenticated, and calls req.unauthenticateSession(A.self)
as implemented by #52. This in turns calls session()
that sees the cache is set to nil and creates a new session.
For this reason, the SessionMiddleware
(which responds after the auth session middleware) thinks we have created a new session, and stores that in the session storage. Not only does this mean that the previous session is not deleted, a new one is created, without any user context.
For the time being, I have been using a custom middleware that works like AuthenticationSessionsMiddleware
but does not do any responding. Thus, the middleware never calls session()
and I am able to correctly delete the session without a new one being created.
I have not created a PR, because I don't know how this should be handled appropriately. Because the middleware does not respond, you will have to call try req.authenticateSession(user)
manually, and not req.authenticate(user)
in your signin method.
Our temporary middleware looks something like:
final class UserSessionsMiddleware: Middleware {
init() {}
public func respond(to req: Request, chainingTo next: Responder) throws -> Future<Response> {
if let id = try req.authenticatedSession(User.self) {
// try to find user with id from session
return User.authenticate(sessionID: id, on: req).flatMap { user in
// if the user was found, auth it
guard let user = user else {
throw SomeError
}
try req.authenticate(user)
// return done
return try next.respond(to: req)
}
} else {
// no need to authenticate
return try next.respond(to: req)
}
}
}
I'm using the authentication middleware to check if a bearer token is valid and everything is working as expected, but I can't find a way to check for its "expires_in" parameter (that is not mandatory but recommended from the standard). Is it possible to add a customizable variable that will check against expiration_date and return a 401 if the token is not valid anymore? Or should I do this check somewhere else?
Weird bug that has been reported and is reproducible in Vapor Cloud easily. When using the sessions middleware after authenticating a User the session is not persisted and the user does not appear to be logged in. This has been reported on the RW forums and on Slack. I haven't been able to reproduce locally at all, but have managed to reproduce in Vapor Cloud. If I had to guess it would be that the Session Cache isn't being shared across threads
I noticed that my own code is actually only depending on several auxiliary modules of the Vapor family, but not the main Vapor
module itself. In the spirit of having minimal dependencies, I'm now trying to avoid depending on the Vapor
module altogether, but I am using the Auth
module.
As far as I can tell, the only import Vapor
statements are in Exports.swift
and a test. Would it therefore be possible to avoid having to include the whole Vapor
repo in this project's dependencies?
I've been wanting to add some permissions around my routes and I thought I'd write my thoughts down as as suggestions or ideas for future development. I'm new to Vapor and backend development, so please take this with a grain of salt.
First, I thought it would be great to be able to add some ability to define permissions levels or roles in conjunction with the User
model. It seems like the most flexible system would allow the author to define these levels on their own (These could be defined as either one-to-one or one-to-many). Alternatively, Vapor could provide preset permissions levels, but that seems like possible overkill. Presumably this could be built as a Protocol any model could conform to, but concrete implementations could be provided as well.
Authorizable
Could be designed like Timestampable
:
extension User: Authorizable {
// Adds mapping to Authorizable key path properties
static var permissionsKey: WritableKeyPath<User, [PermissionType]> { return \.roleLevel }
}
Now, to add Authorization
the routes you could add an extra function to the route chain like .authorize(using: .FooPolicy)
. This could be done with a closure or function that accepts arguments that could be used to define a true/false or pass/fail test. Laravel does something similar with their Authorization system.
If you start with a collection of routes like this:
let athleteRoutes = router.grouped("api", "athletes")
athleteRoutes.get(use: index)
athleteRoutes.post(use: create)
athleteRoutes.delete(Athlete.parameter, use: delete)
With Authorization
the route group could end up looking like the following:
let athleteRoutes = router.authorize(using: athletePolicy).grouped("api", "athletes")
athleteRoutes.get(use: index)
athleteRoutes.post(use: create)
athleteRoutes.delete(Athlete.parameter, use: delete)
Or:
let athleteRoutes = router.grouped("api", "athletes")
athleteRoutes.get(use: index)
athleteRoutes.authorize(using: athleteEditPolicy).post(use: create)
athleteRoutes.authorize(using: athleteEditPolicy).delete(Athlete.parameter, use: delete)
Ideally, you could add this authorization component at both the individual route or route group level.
The authorization function could look something like this:
func athletePolicy(_ req: Request) throws -> Future<Bool> {
return user.permissions.contains(.athleteEditor) // pseudocode for getting user permissions levels
}
Or:
func athleteEditPolicy(_ req: Request) throws -> Future<Bool> {
return try req.content.decode(Athlete.self).map(to: Bool.self) { athlete in
return athlete.ownerID == user.ID //pseudocode for getting user.ID
}
}
In the event that the Authorization
process fails, I'd be great if Vapor sent the appropriate HTTP error code (I think this would be a 403, but I could be wrong).
When using TokenAuthenticatable
and SessionAuthenticatable
both seem to be run on each request which just does more queries and so is not ideal.
If middleware is set as User.tokenAuthMiddleware(), User.authSessionsMiddleware(), User.guardAuthMiddleware()
and on a request, I only provide a bearer token and no cookies, I would expect only the tokenAuthMiddleware
to run. Or if authSessionsMiddleware
runs, that it exits early without doing any queries.
Using the Vapor 3 auth-template
, then enabled sessions (and setup cache etc.), once user is logged in and a request is run, these are the logs:
SELECT * FROM "UserToken" WHERE ("UserToken"."expiresAt" IS NULL OR "UserToken"."expiresAt" > (?)) AND "UserToken"."string" = (?) LIMIT 1 OFFSET 0 [1559561285.008395, "bzmCb/FWPE2l/4ItxasXbw=="]
SELECT * FROM "User" WHERE "User"."id" = (?) LIMIT 1 OFFSET 0 [1]
SELECT * FROM "fluentcache" WHERE "fluentcache"."key" = (?) LIMIT 1 OFFSET 0 ["19OwkNmfnogOE6gwiUUqSA=="]
INSERT INTO "fluentcache" ("key", "data") VALUES (?, ?) ["19OwkNmfnogOE6gwiUUqSA==", 0x7b2264617461223a7b225f5573657253657373696f6e223a2231227d7d]
I wouldn't expect the 2 fluentcache
queries since only a bearer token was given in the request. Also should be same for the opposite, if there is a vapor-session
cookie, the token middleware should exit early and not perform any queries.
Would really appreciate a fix for Vapor 3 since Vapor 4 seems far off still (for production). Or some pointers on how I could contribute on this.
How would we conform the class User
to BasicAuthenticatable
in the following case:
import Vapor
import FluentPostgreSQL
import Authentication
class User: Codable {
var id: Int?
var username: String
var email: String
var passwordHash: String
init(username: String, email: String, passwordHash: String) {
self.username = username
self.email = email
self.passwordHash = passwordHash
}
}
final class Student: User {
struct Public {
let id: Int
let email: String
let username: String
let nationality: String?
}
let nationality: String?
enum CodingKeys: CodingKey {
case nationality
}
init(username: String, email: String, passwordHash: String, nationality: String?) {
self.nationality = nationality
super.init(username: username, email: email, passwordHash: passwordHash)
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Student.CodingKeys.self)
self.nationality = try container.decode(String.self, forKey: .nationality)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: Student.CodingKeys.self)
try container.encode(nationality, forKey: .nationality)
}
}
extension Student.Public: Content {}
extension Student: PostgreSQLModel {}
extension Student: Content {}
extension Student: Parameter {}
extension Student: Migration {}
If I do the following:
extension User: BasicAuthenticatable {
static let usernameKey: UsernameKey = \User.username
static let passwordKey: PasswordKey = \User.passwordHash
}
I get the error, that BasicAuthenticatable can only be implemented by final-classes. How should I work around this? Thx in advance :)
Is stable release coming any time soon?
It is limiting us to Vapor2 for now.
With the current beta, it is just missing an unauthenticate
function to remove the authenticated user from the session to log the user out. Adding here as a reminder so I don't forget!
#39 introduced a bug where the typealias for UserIDType
needs to be specified, despite the fact the UserType
is specified. This should be able to be inferred
It would be great to be able to use the auth package when implementing something like the repository pattern. In SteamPress for Vapor 3, there is no knowledge of Fluent (at least the inner package). All the models are held behind repositories accessible from services. This would be fine, except for the fact things like BasicAuthenticatable
that all the web sessions stuff relies on use DatabaseConnection
to get the user for verifying. Because it's done in the protocol there's no way to implement the extensions yourself but still conform, meaning you have to reimplement most of the auth package π
Would be great to be fixed with Vapor 4!
Currently, if we want to secure routes with a TokenAuthenticationMiddleware
we can only specify a single TokenAuthenticatable
type, which we require to be authenticated in order to execute the route.
In multi-role systems (ie. blog), we usually have multiple different user types (which do not necessarily share a common superclass). Therefore, we often want to secure routes with a middleware that would allow us to specify multiple different types where one of them has to be authenticated for the route to be executed.
After some discussion with @tanner0101, a possible solution could be a new "non-throwing" TokenAuthenticationMiddleware
or BearerAuthenticationMiddleware
, which would authenticate a specified user type if possible, otherwise just continue, instead of throwing. Having this "non-throwing" middleware, it would allow us to compose aka chain middlewares in order to achieve the "multitype authentication" capability. At the end of the chain, we would of course still need a throwing middleware that would check if one of the types has been authenticated otherwise finally throw with unauthorized
.
I've been playing around with this a bit and here is the rough version of how I managed to achieve this.
public final class NonThrowingBearerAuthenticationMiddleware<A>: Middleware where A: BearerAuthenticatable {
public func respond(to req: Request, chainingTo next: Responder) throws -> Future<Response> {
if try req.isAuthenticated(A.self) {
return try next.respond(to: req)
}
guard let token = req.http.headers.bearerAuthorization else {
// TODO: Throw AuthenticationError when initializer is exposed publicly
// @tanner0101 is AuthenticationError intentionally hidden?
return try next.respond(to: req)
}
// auth token on connection
return A.authenticate(using: token, on: req).flatMap(to: Response.self) { a in
guard let a = a else {
return try next.respond(to: req)
}
// set authed on request
try req.authenticate(a)
return try next.respond(to: req)
}
}
}
extension BearerAuthenticatable {
public static func nonThrowingBearerAuthenticationMiddleware(
database: DatabaseIdentifier<Database>? = nil
) -> NonThrowingBearerAuthenticationMiddleware<Self> {
return .init()
}
}
public final class ThrowIfNoneAuthenticatedMiddleware: Middleware {
public enum UserType {
case admin
case user
case moderator
}
private let allowedUserTypes: [UserType]
/// Create a new `ThrowIfNoneAuthenticatedMiddleware`
public init(allowedUserTypes: [UserType]) {
self.allowedUserTypes = allowedUserTypes
}
/// See Middleware.respond
public func respond(to req: Request, chainingTo next: Responder) throws -> Future<Response> {
for userType in allowedUserTypes {
switch userType {
case .admin:
if try req.isAuthenticated(Admin.TokenType.self) {
return try next.respond(to: req)
}
case .user:
if try req.isAuthenticated(User.TokenType.self) {
return try next.respond(to: req)
}
case .moderator:
if try req.isAuthenticated(Moderator.TokenType.self) {
return try next.respond(to: req)
}
}
}
throw Abort(.unauthorized, reason: "Invalid credentials")
}
}
let routes = router.grouped("api", "posts")
.grouped(Admin.TokenType.nonThrowingBearerAuthenticationMiddleware())
.grouped(User.TokenType.nonThrowingBearerAuthenticationMiddleware())
.grouped(ThrowIfNoneAuthenticatedMiddleware(allowedUserTypes: [.admin, .user]))
Above routes will be protected with bearer authentication, allowing only Admin
and User
types to access them and throwing unauthorized
for everyone else.
With Auth 2.0.2 included a clean project will not compile due to this error. Once I rolled back to 2.0.1 everything worked fine.
Currently, unauthenticating a session requires unauthenticateSession(), which doesn't make much sense when using the session authentication middleware. He should catch the unauthentication and remove session authentication data if it exists.
STP Release 50 - session cookies are not working correctly, after authentication if I try and access a protected route it redirects me back using the RedirectMiddleware. Works fine in Firefox, Chrome and normal Safari. Adding here to remind me to investigate further, either Vapor (unlikely since it works fine in others) or a STP bug that needs filing in a radar
did "swift package update"
swift 5, vapor toolbox 3.1.10
getting:
/private/tmp/.../.build/checkouts/auth/Sources/Authentication/Persist/SessionAuthenticatable.swift:44:19: error: value of type 'Request' has no member 'hasSession'; did you mean 'session'? guard try self.hasSession() else { ^~~~ ~~~~~~~~~~ session Vapor.Request:2:17: note: 'session' declared here public func session() throws -> Vapor.Session
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.