Search code examples
iosswiftpostgresqlvaporvapor-fluent

Vapor 4 authentication


Hey I'm having some problems with the login controllers.My code is:

func login(_ req: Request) throws -> EventLoopFuture<UserToken>{
        let user = try req.auth.require(User.self)
        let token = try user.generateToken()
        return token.save(on: req.db).map { token }
    }

But I don't really know that how the function work in postman.This is my usermodel :

import Foundation
import Fluent
import Vapor
import FluentPostgresDriver


final class User:Model,Content{
    static let schema = "user"
    
    @ID(key: .id)
    var id:UUID?
    
    @Field(key:"帳號")
    var account:String
    
    
    @Field(key: "密碼")
    var password:String
    
    
    init() {}
    
    init(id: UUID?=nil, account:String, password:String){
        self.id=id
        self.account=account
        self.password=password
    }    
}

extension User: ModelAuthenticatable {
    // 要取帳號的欄位
    static var usernameKey: KeyPath<User, Field<String>> = \User.$account
    
    // 要取雜湊密碼的欄位
    static var passwordHashKey: KeyPath<User, Field<String>> = \User.$password

    // 驗證
    func verify(password: String) throws -> Bool {
        try Bcrypt.verify(password, created: self.password)
    }
}

extension User {
    struct Create: Content {
        var account: String
        var password: String
        var confirmPassword: String // 確認密碼
    }
    
    
}

extension User.Create: Validatable {
    
    static func validations(_ validations: inout Validations) {
        
        validations.add("account", as: String.self, is: .count(10...10))
        // password需為8~16碼
        validations.add("password", as: String.self, is: .count(8...16))
    }
}



extension User {
    func generateToken() throws -> UserToken {
        // 產生一組新Token, 有效期限為一天
        let calendar = Calendar(identifier: .gregorian)
        let expiryDate = calendar.date(byAdding: .day, value: 1, to: Date())

        return try UserToken(value: [UInt8].random(count: 16).base64, expireTime: expiryDate, userID: self.requireID())
    }
}

And this is my usertoken:

import Foundation
import Vapor
import Fluent

final class UserToken: Content, Model {
    static let schema: String = "user_tokens"

    @ID(key: .id)
    var id: UUID?

    @Field(key: "value")
    var value: String

    // oken過期時間
    @Field(key: "expireTime")
    var expireTime: Date?

    // 關聯到User
    @Parent(key: "user_id")
    var user: User

    init() { }

    init(id: UUID? = nil, value: String, expireTime: Date?, userID: User.IDValue) {
        self.id = id
        self.value = value
        self.expireTime = expireTime
        self.$user.id = userID
    }
}

extension UserToken: ModelTokenAuthenticatable {
    //Token的欄位
    static var valueKey = \UserToken.$value
    
    //要取對應的User欄位
    static var userKey = \UserToken.$user

    // 驗證,這裡只檢查是否過期
    var isValid: Bool {
        guard let expireTime = expireTime else { return false }
        return expireTime > Date()
    }
}

While I'm typing the value of "account","password" and "confirmPassword", but it kept telling me that "User not authenticated." ,which I've already have the value in my database. enter image description here

And I'm sure that the password was right. Is there anything that I missed? I'm pretty new in vapor. And I followed the article below: https://ken-60401.medium.com/vapor-4-authentication-server-side-swift-1f96b035a117


Solution

  • I think the tutorial linked uses HTTP Basic authentication for the login route and I'm guessing that's the case judging by the code shown (it would be good to show how you're registering the login route).

    If that's the case then you need to send the username and password in the request as basic authentication credentials in the Authorization header. The value should be Basic <Credentials> where Credentials is username:password Base 64 encoded. However you can get Postman to do it for you