I would like to know if I am using the best practices here as I send the user credentials in a Content-Type: application/json
way, without protecting the password. I would like to know if I can protect my route with a middleware and make the user signing up in the safest way possible on my Vapor 4 application.
Can I use Authorization: Basic
to sign up a user on my Vapor app ?
Is it the normal flow that I am doing here?
This is the way I sign up a user. There is my table with the UserModel, and this is the extension to handle the sign up flow.
extension UserModel {
// MARK: UserSignUp
/// Values needed for a user to sign up.
///
struct SignUp {
let email: String
let password: String
}
}
extension UserModel.SignUp: Codable {}
extension UserModel.SignUp: Validatable {
/// Setting up sign up rules for email and password to validate
/// new user.
///
static func validations(_ validations: inout Validations) {
validations.add("email", as: String.self, is: .email)
validations.add("password", as: String.self, is: .count(1...))
}
}
The route where I would like to protect it to safely pass user credentials.
struct UsersAuthController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let unprotectedRoute = routes.grouped("users")
unprotectedRoute.post("signup", use: signUp)
}
}
And here is the way I sign up the user in the Vapor app. Is this the safest way?
extension UsersAuthController {
func signUp(_ req: Request) throws -> EventLoopFuture<UserModel.NewSession> {
try UserModel.SignUp.validate(content: req)
let signUpUser = try req.content.decode(UserModel.SignUp.self)
let user = try create(from: signUpUser)
var token: UserTokenModel!
return checkIfUserExists(signUpUser.email, req: req)
.flatMapThrowing { exists -> UserModel in
guard !exists else {
throw Abort(.conflict)
}
return user }
.flatMap { $0.save(on: req.db) }
.flatMapThrowing { _ -> UserTokenModel in
guard let newToken = try? user.createUserToken(source: .signUp) else {
throw Abort(.internalServerError)
}
token = newToken
return token }
.flatMap { $0.save(on: req.db) }
.flatMapThrowing {
UserModel.NewSession(token: token.value,
user: try user.asPublic()) // Just returning a token and use id.
}
}
func create(from user: UserModel.SignUp) throws -> UserModel {
UserModel(email: user.email,
password: try Bcrypt.hash(user.password))
}
}
What are you trying to protect from? There's no real difference between sending the username and password in the JSON body or as a HTTP Basic credentials header. Otherwise things look OK, though I'd nest some of the futures to avoid having var token: UserTokenModel!