Search code examples
swiftvaporvapor-fluent

Vapor - Route with Basic Auth always returning 401


I'm new to Vapor and I've implemented register and login routes. Register works fine. Every time I call the login route it seems to have a problem. Every time, I try to login with a registered user using Basic Auth it just returns 401. Attaching my code below.

App user model:

extension AppUser: ModelAuthenticatable {
    static let usernameKey = \AppUser.$email
    static let passwordHashKey = \AppUser.$passwordHash
    func verify(password: String) throws -> Bool {
        try Bcrypt.verify(password, created: self.passwordHash)
    } 
}
extension AppUser: JWTPayload {
    func verify(using signer: JWTSigner) throws {
    } 
}

Routes configuration:

   //MARK: Unprotected API
    let unprotectedApi = app.routes
    try unprotectedApi.register(collection: AppUserController.Unprotected())
    //MARK: Password Protected API
    let passwordProtectedApi = unprotectedApi.grouped(AppUser.authenticator())
    try passwordProtectedApi.register(collection: AppUserController.PasswordProtected())

login logic:

    extension AppUserController.PasswordProtected: RouteCollection {
    func login(req: Request) throws -> EventLoopFuture<Response> {
        let user = try req.auth.require(AppUser.self)
        let token = try req.jwt.sign(user)
        let loginResponse = AppUserLoginResponse(user: user.response, accessToken: token)
        return DataWrapper.encodeResponse(data: loginResponse, for: req)
    }
    func boot(routes: RoutesBuilder) throws {
        routes.post(Endpoint.API.Users.login, use: login)
    }
}

Solution

  • Your login route as it stands is returning 401 because you have included it in the protected group, which requires the user to be logged in already. It would normally be unprotected. You need some code to do the logging in. This function assumes the user is identified by an email address and has supplied a password somehow:

    private func loginExample( email: String, password: String, on: req Request) -> EventLoopFuture<Bool> {
        return AppUser.query(on: req).filter(\.$email == email).first().flatMap { user in
            // user will be nil if not found, following line test for this
            if let user = user {
                // user was identified by email
                if try! user.verify(password: password) {
                    // password matches what is stored
                    request.auth.login(user)
                    // login has succeeded
                    return true
                }
            }
            // login has failed - because either email did not match a user or the password was incorrect
            return false
        }
    }
    

    I've kept it simple by forcing the try in the call to verify (avoiding do-catch, etc.). You need to use something like this code in your login route, perhaps decoding the email and password from an HTML form.