Unauthorized Response

I followed the code of the book, but unfortunately I get an 401 unauthorized error, when I try to test to enter an acronym through Rested.app.
XCode gives me this error:
Abort.401: User not authenticated. (GuardMiddleware.swift:43)

I know its not an error, but why does not authenticate. I am on page 305.

As an attachment I uploaded a screenshot of my request with Rested. Perhaps you can take a look and give me a hint where can be my error.
Arnold

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

Have you implemented the bearer token stuff yet? Because if so, you need to send the Basic Auth login stuff to /api/users/login (IIRC) then send the token with the request in the headers to create an acronym

No, not yet. I have not implemented the bearer stuff. I am testing the api with the simple basic authorization header using rested. But without success and I am not able to find my mistake. It seems that the route is ok but it seems that the user will not be authenticated. It is GuardMiddleware which checks the password or where will be checked the user and the password?
Here the routes:
let basicAuthMiddleware = User.basicAuthMiddleware(using: BCryptDigest())
let guardAuthMiddleware = User.guardAuthMiddleware()
let protected = acronymsRoutes.grouped(basicAuthMiddleware,guardAuthMiddleware)
protected.post(Acronym.self, use:createHandler)
Thanks Arnold

Also if I try to use a Token its impossible for me to login. It gives me the error:
ERROR ] AuthenticationError.notAuthenticated: User has not been authenticated. (AuthenticationCache.swift:68)
I try to login using the route described in the book, but its impossible to get authenticated. In the database the password is stored as a hash and I can query the user from the database. The thing that not works it the authentication process. Perhaps you have any idea why?
Arnold

This line of code gives me the error:
let user = try req.requireAuthenticated(User.self)
It seems that the AuthenticationCache is empty. I am unable to find the error.

I am still working on my error in authentication without success. I am debugging the authentication. The requireauthenticated method in the Authentication library has no user. Can it be an bug in the rested.app. Here a screenshot of the debugging window.

Ok cool, so you don’t need the bearer stuff if you haven’t done it yet then. If you step into the authenticate call can you see why it isn’t returning a user?

Possibilities are:

  • wrong password
  • not passing it correctly (the screenshot looks correct)
  • password is hashed differently in the DB or not at all

What does your code look like where you create the user? You could upload the code if you want and I’ll take a look.

First of all, thank you for all your effort. I am using a mysql server on my private nas for testing. The password is stored as a hash in the database. Here the code for the user modell and user controller. Should be the same as in the book or similar.
Here first the code for the user model:
// model code`
import Vapor
import FluentMySQL
import Authentication

final class User: Codable {
    var id : Int?
    var name: String
    var username: String
    var password: String

init(name: String, username: String, password: String){
    self.name = name
    self.username = username
    self.password = password
}
final class Public: Codable {
    var id: Int?
    var name: String
    var username: String
    init(id: Int?, name: String, username: String){
        self.id = id
        self.name = name
        self.username = username
    }
}

}
extension User: MySQLModel{}
extension User: Parameter{}
extension User.Public: Content{}
extension User: Migration{}
extension User: Content{}
extension User {
var acronyms: Children<User, Acronym>{
return children(.userID)
}
func convertToPublic() -> User.Public {
return User.Public(id: id, name: name, username: username)
}
}
extension Future where T: User {
func convertToPublic()->Future<User.Public> {
return self.map(to: User.Public.self) { user in
return user.convertToPublic()
}
}
}

extension User: BasicAuthenticatable {
static let usernameKey: UsernameKey = \User.username
static let passwordKey: PasswordKey = \User.password

}
Here the code for the controller:

import Vapor
import Crypto

struct UsersController: RouteCollection {
func boot(router: Router) throws {
let usersRoute = router.grouped(“api”,“users”)

    //usersRoute.post(User.self, use: createHandler)
    usersRoute.get(use:getAllHandler)
    usersRoute.get(User.parameter, use: getHandler)
    usersRoute.delete(User.parameter, use:deleteHandler)
    usersRoute.get(User.parameter, "acronyms", use: getAcronymsHandler)
    usersRoute.post(User.self, use: createHandler)
    usersRoute.put(User.parameter, use: updateHandler)
    let basicAuthMiddleware = User.basicAuthMiddleware(using: BCryptDigest())
    let basicAuthGroup = usersRoute.grouped(basicAuthMiddleware)
    basicAuthGroup.post("login", use: loginHandler)
    
}
func createHandler(_ req: Request, user: User) throws -> Future<User.Public> {
    user.password = try BCrypt.hash(user.password)
    return user.save(on: req).convertToPublic()
}

func getAllHandler(_ req: Request) throws -> Future<[User.Public]>{
    return User.query(on: req).decode(data:User.Public.self).all()
}
func getHandler (_ req: Request) throws -> Future<User.Public> {
    return try req.parameters.next(User.self).convertToPublic()
}
func deleteHandler (_ req: Request) throws -> Future<HTTPStatus> {
    return try req
        .parameters
        .next(User.self)
        .delete(on: req)
        .transform(to: HTTPStatus.noContent)
}
func getAcronymsHandler(_ req: Request) throws -> Future <[Acronym]> {
    return try req
        .parameters.next (User.self)
        .flatMap(to: [Acronym].self){ user in
            try user.acronyms.query(on: req).all()

    }
}
func updateHandler(_ req: Request) throws -> Future<User.Public> {
    return try flatMap(
        to: User.Public.self,
        req.parameters.next(User.self),
        req.content.decode(User.self)
    ){ user, updatedUser in
        user.name = updatedUser.name
        user.username = updatedUser.username
        user.password = try BCrypt.hash(updatedUser.password)
        return user.save(on:req).convertToPublic()
    }
}
func loginHandler(_ req: Request) throws -> Future<Token> {
    let user = try req.requireAuthenticated(User.self)
    let token = try Token.generate(for: user)
    return token.save (on: req)
}

}

And here finally a screenshot of the table of my db:


thanks a lot
Arnold

Ok that all looks valid (though I can’t see a salt for your schmida user, but that may just be the encoding.

Does the bill user work? Also the NULL columns shouldn’t be possible. I guess have you tried dropping the database and starting again (realise that that isn’t exactly possible all the time)

Thanks, I will try it. Which salt do you mean?? BCrypt should hash the salt automatically, or not? Yes the null columns are only old entries. I tried with bill too.
Thank you
Arnold

The saved hash in the database normally contains the salt, it’s in the form . and I can’t see a . in the table

Good Morning Tim,
unfortunately I get stuck always on the same problem. The fact that seems strange to me is that why the the loginHandler the user object is empty:

func loginHandler(_ req: Request) throws -> Future<Token> {
    print("User: \(User.self)" )
    let user = try req.requireAuthenticated(User.self)
    let token = try Token.generate(for: user)
    return token.save (on: req)
}

I am testing the route http://localhost:8080/api/users/login with rested.app as described in your book and obviously I give them the Authorization data in basic form, but the User seems to be set. I send you a screenhot of my debug window, perhaps you can see if the user object is ok in this form.


Can it be that i have included the wrong version of a library, because after adding the Token.swift file and regenerating the project with vapor xcode -y, I reget the segmentation error mentioned above and I had to remove all the authentication stuff and rebuild it from new and then adding it in xcode too. This last lines are only a observation of myself, I am not sure that the problems arise from it. Finally here my package.swift:

import PackageDescription

let package = Package(
name: “TestApi”,
dependencies: [
// :droplet: A server-side Swift web framework.
.package(url: “https://github.com/vapor/vapor.git”, from: “3.0.0”),

    // 🔵 Swift ORM (queries, models, relations, etc) built on SQLite 3.
    .package(url: "https://github.com/vapor/fluent-mysql.git", from: "3.0.0"),
    .package(url: "https://github.com/vapor/auth.git", from: "2.0.0")
    
],
targets: [
    .target(name: "App", dependencies: ["FluentMySQL", "Vapor", "Authentication"]),
    .target(name: "Run", dependencies: ["App"]),
    .testTarget(name: "AppTests", dependencies: ["App"])
]

)

Thank you,
Arnold

So the user object is nil because authentication failed (which usually indicates an incorrect username and password for HTTP basic auth). User.self is a type, which is why it prints out in the console. What have you set as the username and password key for BasicAuthenticatable? And are you sending the right things through (as opposed to a name instead of username)?

If you’re still having issues, just zip up the source and send it to me and I’ll take a look, or post a GitHub link.

Thank you Tim,
here my github link:


Greetings
Arnold

Few questions:

  • Any particular reason you removed the AppTests directory in Tests? It means you can’t generate an Xcode project without fixing that first.
  • I’m assuming you’re connecting to an external database with existing tables? Is there a reason for this and not using Vapor to run migrations?

Fixing those two issues (i.e. using Vapor to run the migrations) made it work for me

Thank you Tim:
Regarding AppTests directory, I think it was an accidental moving out from the test folder using xcode. The AppTest directory exists, but outside the tests folder. Thanks. I will move it in the test folder.
Yes, the reason why I do not use migration is becaus I am using an older version of mysql where migration gives me always an error. I will try to build a container with docker using a newer mysql version and then I will test it again.
You helped me a lot, thank you Tim.

Solved. You were right. I used the migration stuff using a docker image and now I get a token.

1 Like