Vapor 4 Authentication: Getting Started | raywenderlich.com

In this Vapor 4 tutorial, you’ll learn how to implement user authentication for your app using both bearer tokens and basic authentication headers.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/9191035-vapor-4-authentication-getting-started

Just finished going through your tutorial. Found it super easy to follow until:

let passwordProtected = 
  usersRoute.grouped(User.authenticator().middleware())
passwordProtected.post("login", use: login)

Having the .middleware() gives:

Value of type 'Authenticator' has no member 'middleware'

Removing the .middleware() the code then works as expected.

@warplydesigned thanks a lot for flagging this. There was an API change in the final release of Vapor from the time the article was written, and we fixed it in the sample projects, but missed in the article.

The article has been updated. Thank you!

1 Like

No problem glad to help.

This is a great tutorial! It was very easy to follow and understand what/how to do things. Now… working through the stretch items.

I’m confused by the logic on “Adding the Login Endpoint”.

Typically a fresh login will require a username (email) password. The server side will find a user object for that username or email address, validate the passwords and return a token + user of some kind.

In case I’m missing something big, I fail to see how the current implementation would work.

@drunneriv Really glad you liked it! Cheers! :]

@natanrolnik Can you please help with this when you get a chance? Thank you - much appreciated! :]

Hi @kdeda, and I’m sorry for the delay here, I missed your comment.

You’re correct. But in this case, instead of sending the login and password in the HTTP Body, Vapor’s User.authenticator() middleware decodes them from the Authentication header, and provide the user object in case they are valid.

You can use this website to understand how this header is generated based on the username and password:

Hi @natanrolnik , I have followed the tutorial carefully but I have problem I can’t login the by runing unit test it’s throw always failure unauthorised.
this the scenario i followed in my unit test I create user, logout, login.

  1. this the router.
   let passwordProtector = userRoutes.grouped(User.authenticator())
   passwordProtector.post("login", use: loginUser)
  1. this the login handler

fileprivate func loginUser(_ req: Request) throws → EventLoopFuture {
let user = try req.auth.require(User.self)
let token = try user.createToken(source: .login)
return token.create(on: req.db).flatMapThrowing{
NewSession(token: token.value, user: try user.asPublic())
}
}

  1. this my unit test call.

let login = userURI + “login”
try app.test(.POST, login, beforeRequest: { (req) in
let loginuser = [“username”: userName,
“password”: password
]
try req.content.encode(loginuser)
}, afterResponse: { (response) in
let recivedSession = try response.content.decode(NewSession.self)
XCTAssertNotNil(recivedSession.token)
})

Hi there, if I want to make sure both the email and username is unique, what’s the best way to chain the futures together? I have this, which works, but I find it kind of ugly! Also I wonder if it’s more efficient to just do one query rather than two? But then I need the future to respond with an error code instead of a Bool?

    /// Creates a user
func create(req: Request) throws -> EventLoopFuture<NewSession> {
    
    try UserSignup.validate(content: req)
    let userSignup = try req.content.decode(UserSignup.self)
    let user = try User.create(from: userSignup)
    var token: Token!
    
    return checkIfEmaiInUse(userSignup.email, req: req)
        .flatMap { exists in
            guard !exists else {
                return req.eventLoop.future(error: UserError.emailTaken)
            }
            
            return checkIfUsernameInUse(userSignup.username, req: req).flatMap { usernameExists in
                guard !usernameExists else {
                    return req.eventLoop.future(error: UserError.usernameTaken)
                }
                
                return user.save(on: req.db)
            }
        }.flatMap {
            guard let newToken = try? user.createToken(source: .signup) else {
                return req.eventLoop.future(error: Abort(.internalServerError))
            }
            token = newToken
            return token.save(on: req.db)
        }.flatMapThrowing {
            NewSession(token: token.value, user: try user.asPublic())
        }
    
}


private func checkIfEmaiInUse(_ email: String,
                              req: Request) -> EventLoopFuture<Bool> {
    User.query(on: req.db)
        .filter(\.$email == email)
        .first()
        .map { $0 != nil }
}

private func checkIfUsernameInUse(_ username: String,
                                  req: Request) -> EventLoopFuture<Bool> {
    User.query(on: req.db)
        .filter(\.$username == username)
        .first()
        .map { $0 != nil }
}