Server Side Swift with Vapor - Part 29: Web | Ray Wenderlich

In this video, you'll learn how to protect web pages with authentication using sessions and cookies.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/4493-server-side-swift-with-vapor/lessons/29

@0xtim How do you capture the path that the user was attempting to access before being re-directed by the RedirectMiddleware?

@0xtim One more question, how would you recommend organizing this type of project? As I have added more features to the web controller, it has become a very large and unwieldy file. Could these parts be broken up into separate files?

Thanks for all the work on these videos!

Could you briefly explain what the authSessionRoutes does and how it differs from a regular router.get call?

The protectedRoutes requires the user to be authenticated (logged in) but I am unsure what the authSessionRoutes does.

For the first question - is this so you can redirect back to the right page after they have logged in? You have a few options, you can either save it to a cookie, save it to the session or add it as a query on the URL. These will all require a custom RedirectMiddleware however, but you can just copy it, it’s pretty simple.

For organising your code you can have as many files as you want! You may want to split it into different sections, such as an admin section where the user must be logged in, or split it up into AcronymWebsite, UserWebsite etc.

Another thing to consider is that if you are building a big site you probably want to split out the web front-end and the API into separate Vapor applications and have the website call the API instead of interacting directly with the database.

Hope that helps!

@simonqq the authSessionRoutes have the authSessionsMiddleware applied so every request will run through this middleware before hitting your route handlers. The protectedRoutes require a user to be logged in as you said, but the auth sessions middleware will convert a session object into a logged in user. Essentially it receives the sessions cookie in the request and then looks this up to see if this have an authenticated user ID associated with it, if it does it will get the user from the database using the ID, so you have a real user object to interact with when calling auth.requireAuthenticated(User.self)

Yes, the redirect is to send them back to the right page after logging in. So would I just copy RedirectMiddleware.swift from Authentication/Persist, rename it, and add the needed functionality? It seems like adding it as a query on the URL would be the “safest” approach.

Yeah basically create your own RedirectMiddleware. You can keep the basic functionality, just in your case add the current URL as a query to the redirect URL and check to see if it exists in the login POST handler

@0xtim. First of all, thanks for the awesome videos.

You mentioned that we can split the web front end and the apis into different applications.

How would the the authentication work between these applications?

Also, how can we run both applications at the same time from Xcode ? (i’m guessing that they will be using different ports)

Thanks

@ramelan to run on different ports you can do:

services.register { _ in 
  EngineServerConfig.detect(hostname: "127.0.0.1", port: 8080) 
}

In one of the apps in configure.swift and change the port number.

Authentication in split apps is a fun one! It all depends on how you Authenticate the API. Usually what you would do is in the website have a login form that forwards the login request to the API, receives a token and then saves that token in the session. Then when the cookie is provided in requests to the front-end you convert the session cookie into an API token and use that to query the API. This will require a custom authentication middleware though, but in principle is pretty similar to how the standard one works.

How would this relate to JWT? Are they just completely different things?

JWT is related but different. You can provide a JWT token to do authentication and it can be good for doing server-to-server authentication. But JWT is ‘self-authenticating’ so you can authenticate a user and provide them a JWT token which they can use to access other servers. The other servers don’t need to talk to the original server to verify the token which is where the benefit comes in. But you still need to issue the JWT and all servers need to know how to handle it

In the login post handler, after calling req.authenticateSession, would it then make sense to call the API’s login method there? I’m trying to resolve how the web authentication works with the Token based authentication for the API. Seems like I have to just call the API and then somehow store that token inside the web app.

Oh, I just saw your response to ramelan about this. Middleware…how do we convince you to show an example of doing that? :slight_smile:

So web auth and token auth are completely separate. In the TIL app, it’s small enough that it makes sense to allow two different methods of authenticated - one through the API for clients and one through the web.

When you call req.authenticateSession it saves the user provided to the request’s sessions. The web browser is then provided a cookie with the session ID. In subsequent requests the browser provides this cookie with requests, and then Vapor can take the session ID, see if there is an authenticated user saved in the sessions and then use that when you call req.requireAuthenticated(User.self). Does that make sense?

In terms of splitting out the API - for large applications you generally want to split them out into separate apps, so the web app just becomes another client just like the iOS app and does all interactions via the API. The web app still needs to manage saving API tokens etc, just like the iOS app does.

Does that explain everything?

Not really, no. Even if I split them into two completely separate apps, the web app (or the iOS project) still has to call to the API, and that requires sending a username and password to then get that Token object stored and returned. So I’m feeling like my web app’s login page does the normal web authentication, and then it also has to call to the API to get a token that’s used the rest of the time they’re in the web app. I’d store that token in the session of the web app and then use it for future calls.

So it seems like my web app’s login handler would do the authenticateSession call and then do something like this:

var headers = HTTPHeaders()
headers.basicAuthorization = BasicAuthorization(username: creds.username, password: creds.password)

return try req.client().get("/producer/login", headers: headers).map { response in
    let token = response.content.decode(Token.self)

    let session = try req.session()
    session["token"] = token.token

    return req.redirect(to: "/")
}

Almost. What would usually happen is that the web app doesn’t do the normal web authentication, since that would involve interacting with the app database. Instead it would only call the API login route and then handle the scenarios where that fails etc.

Once you have a token you can then save that in the session, and that is your ‘logged in’ state. You’ll probably need some custom authentication middleware to be able to covert that to a user, or you may want to get the user from an API using the token (something like /api/me) and save that in the session as well as the authenticated user.