Form return either View or Response

Hi :wave:

I'm working on my very first Vapor app and I am trying to figure out how to make a route return either a View or a Response. The best solution I have been able to come up with so far is to make the routes return a Response and then call .encodeResponse(status: HTTPStatus, headers: HTTPHeaders, for: Request) on req.view.render to turn it into a Response.

The reason I want to do this is to be able to display form errors in case an invalid username or password has been entered (or invalid form input in general) and also to be able to redirect the user in case they provide valid form input.

The following code snippet works but I am not sure if this is an acceptable way of doing it? or if there is a better way of doing it?

import Vapor

struct LoginContext: Encodable {
    var error: String
}

struct AuthenticationController: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
        let auth = routes.grouped("auth")
        let sessionAuth = auth.grouped(AdministratorCredentialsAuthenticator())
        
        auth.get("login", use: login)
        sessionAuth.post("login", use: loginPostHandler)
    }
    
    func login(req: Request) async throws -> Response {
        if let _ = req.auth.get(Administrator.self) {
            return req.redirect(to: "/")
        }
        return try await req.view.render("auth/login")
            .encodeResponse(status: .ok, for: req)
    }
    
    func loginPostHandler(req: Request) async throws -> Response {
        if let _ = req.auth.get(Administrator.self) {
            return req.redirect(to: "/")
        }
        return try await req.view.render("auth/login", LoginContext(error: "Invalid username or password"))
            .encodeResponse(status: .unauthorized, for: req)
    }
}
1 Like

Interesting. approach to the problem. Personally, I have not yet reached that stage in my playing. But I am wondering if the use of middleware would be a better strategy. This way whether you are using username/password, MFA, TOTP (Time-based One Time Password), JWT (Java Web Token) or something more insane like introducing IODC (Open ID Connect) or SAML. your app would not need to care. The middleware would have already looked at the user and decided to trust or not and add the appropriate items to the session to let your page code handle it.

I have not reached that section of Server Side Swift yet to see how he likes to do it. But it's the only book that I have found that was updated when Vapor went Async Await aware. In the book we have already addressed using middleware to fix minor routing errors.

I am not sure if this what you're talking about but I have already added the session middleware and implemented the required protocols as described in Authentication | Session :slight_smile:

Hey Henrik,
I assume you want to have a Response or View based on the condition? And I assume you want to display the error message next to the field?

Mattes

Correct :slight_smile:

Vapor comes with a Validator (Vapor: Basics → Validation), so you can add validations to your model and return the error messages depending on the requirement.

Then you can use validate(content: req)or validate(query: req) to catch the message and return it with your view and your DTO.

Keep in mind that's untested, but that's how I would try to achieve it.

1 Like

I feel like if it works, it's acceptable :rofl:

Thanks for posting this, was dealing with a similar situation and using some ridiculous redirect to query string parameters thingie, this looks to be a better solution :+1:

1 Like

Hi. Just want to share that I've implemented a POC for HTMX that handles this 'gracefully'. HTMX is a JS library that adds interactivity to traditional MPAs.

For a login form, instead of a standard browser POST, HTMX does an AJAX POST. If login fails, my handler returns an HTML fragment (containing the error message) that HTMX inserts into the DOM right below the form. The user stays on the same login page, without the browser fetching the login page again. If login is successful, my handler sends back a redirect via HTMX to bring the user past login to whichever destination page.