DEV Community

Cover image for Authentication and Sessions for MVC Apps with NestJS

Authentication and Sessions for MVC Apps with NestJS

John Biundo on July 22, 2019

John is a member of the NestJS core team, primarily contributing to the documentation. Note: You can find all of the source code from this article...
Collapse
 
kresdjan profile image
christian

Yes thank you @johnbiundo
Please share more of your knowledge, best practices and guides on NestJs and authentication. Nothing is more important, all other features of the framework isn't useful before you have authentication going. I know I struggle with it a lot, I and I think others might too. So long knowledgeable articles are most welcome.

Collapse
 
johnbiundo profile image
John Biundo

Thanks Christian. I do plan more articles on this topic. Let me know if there are particular areas you'd like to see covered!

Collapse
 
umasankarswain profile image
umasankar-swain
Collapse
 
forsetius profile image
Marcin Paździora

That's really helpful article, thanks for it! It would be great if at least parts of it got integrated into Nest's docs.

Following all the steps described I managed to get auth+sessions work. But one thing bugs me: database security. In src/app.controller.ts we have LoginGuard on login route that after some hops takes us to auth.service.ts. There we pass username and password:

  async validateUser(username, password): Promise<any> {
    const user = await this.usersService.findOne(username);
Enter fullscreen mode Exit fullscreen mode

Let's say we pull the user from the database so we certainly want to have username validated first. But Guards are executed before Pipes so LoginGuard will run before ValidationPipe could return "400 Bad request" on some malicious payload.

So, how to make LoginGuard use ValidationPipe to check the input before proceeding with using it for its auth job?

Collapse
 
dddsuzuki profile image
dddsuzuki

Same opinion as you.

Should login logic with passport be implemented in Service instead of Guard?
process order: ValidationPipe -> Controller@Post -> LoginService -> DB

Collapse
 
umasankarswain profile image
umasankar-swain
Collapse
 
sboisse profile image
Sylvain Boissé • Edited

Hey John, thanks for the article. Not much documentation is available for sessions using NestJS so this is article is pretty helpful. That said I did waste a little bit of time because executing app.use(passport.initialize()) would crash using your code sample. After some struggling, I figured out passport is imported incorrectly in your sample code. When importing passport in main.ts, the import should be import passport from 'passport'; instead import * as passport from 'passport';. If you import using the later statement, the initialize function is undefined. You may want to correct this to avoid headaches to developers using code samples from your article to get sessions to work with NestJS :)

Cheers

Collapse
 
lampsbr profile image
lampsbr

I had the same problem using import * as passport from 'passport';

But if replace it with import passport from 'passport';, my app doesn't even boots. It says:

node:25616) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'initialize' of undefined
at bootstrap (/home/lamps/dev/mvc-sessions/dist/main.js:21:32)
(Use node --trace-warnings ... to show where the warning was created)
(node:25616) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag --unhandled-rejections=strict (see nodejs.org/api/cli.html#cli_unhand...). (rejection id: 1)
(node:25616) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Collapse
 
umasankarswain profile image
umasankar-swain
Collapse
 
lampsbr profile image
lampsbr

Hi!
I tried
import passport = require('passport');
and it seems to work. I'll keep this way for now. Does anyone knows the correct import syntax ?

Collapse
 
umasankarswain profile image
umasankar-swain
Collapse
 
joaquimley profile image
Joaquim Ley

Thank you so much for the detailed article. I have a couple of questions now though:

  1. Why isn't this simply in the official documentation? Feels a bit strange as I went with the official website's documentation to set up (a working) JWT interface and now I read this and I'm super confused to which I should follow, as there are different implementation(s) for the LocalStrategy here.

  2. Are there any plans to implement a simple(r) session related documentation to the official website?

Thank you once again to you and the rest of the team for the framework and time of this write-up.

Collapse
 
rafaelsofizadeh profile image
Rafael Sofi-zada • Edited

I'm in the same situation right now. I followed the documentation, built the JWT auth system, knowing nothing about authorization, but having heard JWT used everywhere. Started reading about authorization, sessions, realized that stateless authorization isn't applicable to my use case at all. I wish there was more information on stateful authorization with Nest.js.

I suggest you to read the blog post and linked, and its follow-up.

Collapse
 
ozzythegiant profile image
Oziel Perez

@joaquimley and @rafaelsofizadeh The JWT implementation from the nest documentation is actually more of an example of how to authenticate using JSON Web Tokens as opposed to sessions. There's been a lot of debate over the years as to whether JWT is appropriate, let alone secure for Single Page Apps. To cut through the chase, if you're building a traditional site (a web app with multiple pages which cause the browser to refresh when navigating) or a Single Page App, just stick to using Sessions. If you're building a public REST API or a mobile app with a backend service, then JWT might be more useful, and even then, according to some experts, it's better to have one app handle authentication only while keeping other apps separate.

I'm assuming you're both building traditional or single-page apps, so in your case, the documentation doesn't explicitly tell you how to set up authentication along with sessions. Here's what I recommend: follow the docs on Authentication all the way down until you finish setting up your Local Guard based on AuthGuard from @nestjs/passport. The next section is JWT, so ignore all of that. Instead, follow the instructions on how to set up sessions on express, so you will need this article. This means attaching and configuring session and passport on the bootstrap function, then setting up your serializer, like the article mentions, and finding a session Store to replace the development store that express-session comes with. In my case, I'm building a custom one that implements the Store interface but you may want to look for one in the express-session docs. Hope this helps!

Collapse
 
ozzythegiant profile image
Oziel Perez • Edited

I would like to post a minor correction/suggestion. If this is written for a REST API, request.logout() will only remove the user from the session, but the session itself won't be removed from the session store. Instead, in the logout route you should write something like this:

@Get("logout") 
public async logout(@Request() request: any, @Response() response: ExpressResponse): Promise<void> {
    request.session.destroy(() => {
        response.cookie(this.config.get("SESSION_NAME"), "", {
            domain: this.config.get("SESSION_DOMAIN"),
            path: "/",
            httpOnly: true,
            maxAge: 0,
            expires: new Date(0)
        })
        response.end()
    })
}
Enter fullscreen mode Exit fullscreen mode

where ExpressResponse is the Response type from express, so as not to clash with Response type from @nestjs/common

request.session.destroy() will remove the session from the store, and on the callback, the session cookie must be deleted since neither Nest nor Passport seem to do this for you. this.config.get("SESSION_NAME") will retrieve the cookie name from a .env file, assuming you set up @nestjs/config library; otherwise just type in the name of the session cookie which by default is connect.sid. Lastly, response.end() will finish the request manually since it the response object was injected.

EDIT: the previous response.clearCookie() call will not work if you had set httpOnly to true, so you must specify the whole cookie's parameters as shown above. Apparently all the previous parameters have to match except max age and expires.

Collapse
 
mendelb profile image
Mendel Blesofsky

Thanks for an awesome tutorial!

One note for anyone who encounters the same issue I did for the logout functionality.

I receieved the following error log when implementing per the article:
Error: req#logout requires a callback function

Passing the res.redirect('/) as a callback to the req.logout method call fixed this.

  @Get('/logout')
  logout(@Request() req, @Res() res: Response) {
    req.logout(() => {
      res.redirect('/');
    });
  }
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ketibansapi profile image
Deary

Thank you @johnbiundo it's a great article. By the way, can you continue this project with mongoose instead of hard-coded memory?

Collapse
 
johnbiundo profile image
John Biundo

Thanks for the feedback @Deary. Yes, at some point I would like to provide a fuller example, including a User store, but at the moment I'm focused a little bit more on the aspects of the solution that are relatively "unique" to NestJS.

Collapse
 
forsetius profile image
Marcin Paździora

I'd love some elaboration on that serialize/deserialize part. How the data is stored between requests? How to plug my solution (some custom memory store, some database etc.) into this framework?

Collapse
 
stephen_scholtz profile image
Stephen Scholtz

Excellent article, John! It's very carefully written and covers a lot of stuff. I have a question about the input Passport's local strategy accepts. It seems the JSON from a user has to be with keys "username" and "password". Is there a way to accept an "email" field instead of a "username" field?

Collapse
 
noo504 profile image
Omar M. Abdelhady

Thanks a lot,
I tried to make authentication with JWT but I cannot perform logout,
I want to be able to actually control the token, delete it, upgrade it if the user used it in the expiration time again,

here's a stackoverflow question I have posted

stackoverflow.com/questions/595473...

Collapse
 
ffirsching profile image
Felix Firsching

Hey John, awesome article! I hope you keep them coming! I struggle specifically with setting a default strategy with passport? I tried setting it in the module import via "PassportModule.register({ defaultStrategy: 'jwt' })," in both, once the AppModule and once my AuthModule, but it doesn't seem to work?

When setting the guard via decorator it works no problem? Hope you can point me in the right direction, cheers!

Collapse
 
johnbiundo profile image
John Biundo

Hi Felix, thanks for the feedback!

So, have you looked at the code sample here: docs.nestjs.com/techniques/authent... and does your code follow that pattern? You should only need to do this in the AuthModule if this is where you are registering Passport. If so, and this still isn't working, and you have a public repo, I'd be happy to take a look. Good luck!

Collapse
 
vzelenko profile image
Vlad

Thank you! Thank you! Thank you! @johnbiundo

This is perhaps the first tutorial on NestJS that has a login flow appropriate to bootstrap an application with view components.

All the code presented is clear, well documented, and works out of the box!

Would love to see more of your content.

- vlad

Collapse
 
markpieszak profile image
Mark Pieszak

Excellent write-up John!! 👏👏

Collapse
 
johnbiundo profile image
John Biundo

Thanks Mark! 😃

Collapse
 
mkubdev profile image
Maxime Kubik

Hey! Awesome ressource, thank you so much.
Can i continue this article with the implementation of mongo-connect ?

Collapse
 
ruslangonzalez profile image
Ruslan Gonzalez

Great post @johnbiundo thank you for sharing your knowledge with us.
Hope you get deeper details into authentication with NestJs.

Collapse
 
johnbiundo profile image
John Biundo

Thanks for the feedback @ruslangonzalez ! I do plan to in the near future.

Collapse
 
dwoodwardgb profile image
David Woodward • Edited

Thanks for filling a much needed niche, this is exactly what I was looking for!

Collapse
 
nomiracle profile image
Nomiracle

Thanks Christian, This article very useful. I had problem with “request.flash('loginError', 'Please try again!'); ” I cannot call flash method in request , Does anyone has same problem?

Collapse
 
mendelb profile image
Mendel Blesofsky

I experienced a similar issue. In my case I was getting the following error log:

property 'flash' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
Enter fullscreen mode Exit fullscreen mode

I realized I'd neglected to install the corresponding types package for connect-flash (@types/connect-flash) which resolved the issue for me.

Collapse
 
brian611 profile image
Brian Pooe

Thanks for this. Lately, I have been struggling to get this to work with OpenID connect hey, have you done it before? would really appreciate your help.

Collapse
 
yuezk profile image
Kevin Yue

This article is very helpful to me, thank you @johnbiundo .

But I have a question that the user info is not the must-have for some routes, so how can I implement this?

Collapse
 
mememe profile image
mememe

This is one of the best and helpful tutorials I have read on NestJS. I have comeback to this post a lot of times reading up on it from time to time.

Thank you John

Collapse
 
tomaszjankowski profile image
tomasz-jankowski

Great job!
Could you update your solution to implement redirection from login page to a profile page when user is already logged-in?

Collapse
 
tomaszjankowski profile image
tomasz-jankowski • Edited

I tried doing something like this image and it works, but I end up with error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

Any ideas? Couldn't resolve it with solutions from many Stack Overflow topics.

Collapse
 
tomekomel profile image
tomekomel

Great article! Simple written, everything clear. Thanks John!

Collapse
 
adrianfa25 profile image
Adrian F

Thank you very much! You helped me a lot!

Collapse
 
sundaycrafts profile image
Hiroto • Edited

Thanks for such a great tutorial! This might be not very new but still completely available.
It's a missing part of the official docs!

Collapse
 
manuelf0710 profile image
manuelf0710

Excellent article, I am currently implementing authentication with saml passport-saml, I have been looking for the solution for a few days. I'm going to apply the techniques you mention.