NestJS: Third Party OAuth2 Authentication

Steve

August 31, 2020

NestJS: Third Party OAuth2 Authentication

Intro

NestJS is a powerful, Angular-like, application framework built in Node.js. We use it in a few of our apps at nerd.vision including our Discord bot: @nerd.bot. Recently I’ve been trying to set up authentication for an internal single page application in a NestJS project, but as good as their documentation is, I found it didn’t provide any direct docs on how to authenticate via a generic OAuth2 service. 


What we’re going to do

We’re going to add authentication via Discord using OAuth2 to an existing app. This will be done using NestJS purely as an API, so I’ll include a very basic front end to try it out with! If you just want to skip the tutorial and see the final code, scroll down to the bottom to see the example projects.


Prerequisites

  • Recent version of Node.js and NPM installed
  • Basic knowledge of NestJS & Typescript
  • Basic understanding of how the OAuth2 process works
  • An existing NestJS app with authentication setup (we’ll presume that you have a setup similar to the official tutorial)
  • A Discord account & a developer app with OAuth2 access (if you’re not using Discord, you can swap this out with and provider that gives you OAuth2 connection details)

         If you don’t have this but want to use Discord, you’ll need to go to the Discord developer area and create an application by clicking “New Application”. If you don’t already have a Discord account, just follow the instructions to create one, the process is pretty simple!


Implementing the authentication

1. First we need to install passport (and its types if you’re using TypeScript) as that is what will be powering our authentication

2. In the auth.module.ts, add the import for passport

3. Next let’s create a strategy for Discord. Strategies are effectively configurations that tell passport how to interact with whatever authentication methods you’re using

    * Inside your auth module directory, create a new file call discord.strategy.ts with the following content

    * This file contains three important things:

      * Extending the PassportStrategy and giving our strategy the name ‘discord’ so we can reference it easily later

      * Passing along the Discord OAuth configuration to passport in the constructor

      * Validating the response from Discords authentication, allowing us to verify the user ourselves before allowing them through

    * As you can see, we’re still missing the configuration and validation

    * Don’t forget to add this file to the providers in the auth.module.ts

4. For each property in the configuration, let’s add the required info

    * authorizationUrl: https://discordapp.com/api/oauth2/authorize?authParams

      * This tells passport where to go to validate the auth with Discord

      * authParams is a query string which looks like client_id=123&response_type=code&... containing the following attributes:

        * client_id (you can find how to get this in step 5)

        * redirect_url (the exact same one as in step 5)

        * response_type: code

        * scope: identify

    * tokenURL: https://discordapp.com/api/oauth2/token

      * This is the URL that passport uses to get our auth token

    * scope: identify

      * Scopes allow us to gather information from the initial auth request

5. The other properties depend on your application configuration. Go to the application we set up earlier and we’ll gather the relevant properties

    * clientID: This is on the General Information page of your Discord app

    * clientSecret: This is also on that page 

    * redirectUrl: This is on the OAuth2 page in the sidebar. This is the URL that Discord will redirect the user back to so that we can validate their auth. This will be http://localhost:8080/auth/discord for our example front end app

      * Make sure you add this URL in the Redirects on the OAuth2 page so that Discord recognises it as a valid URI

6. Now that passport knows how to interact with Discord, let’s add the logic to handle successful authentication on our side. To keep it simple we’ll just check if we have any users with a discord_id that matches the Discord user they authenticated as. If they do, we’ll log in. If they don’t we’ll throw an error saying that they need to authenticate

7. To do this let’s grab the logged in user from Discord

    * We’ll need to import the HttpModule into our auth module and then the HttpClient into our strategy to make http requests

8. Next let’s make a method in the auth service to get a user from a Discord id

9. What if no user is found? Let’s throw an authentication error to the user

10. We can now import the auth service into our strategy and call that method to grab our user

11. As the auth service already handles there being no user, we can just return the user from the validate method and we’re logged in!

12. But we haven’t made any routes that utilise this strategy yet, so let’s create an auth controller if you haven’t got one already

    * Don’t forget to add this to the app.module.ts as a controller

12. Now let’s add a method to validate our Discord auth. It'll be available on auth/discord. This route will be used after Discord has redirected back to our app so we can match the Discord user with our user

13. NestJS currently doesn't know that we want to authenticate with Discord, so let's use a passport guard to tell NestJS

    * Note that the Discord string passed here is the same one in the Discord strategy

14. As the strategy handles all the authentication logic for us, any requests getting into the method are already authenticated! Let’s prove this by just returning the logged in user via the user property that gets attached to the request

15. We can now use the front end app to log in with Discord via OAuth2 and get back our related user!

Proving the auth works with the front end app

1. Clone/download the front end app from https://gitlab.com/nerd-vision/blogs/nestjs-oauth-discord-ui

2. Install the dependencies with npm i

3. Edit the .env file and change VUE_APP_DISCORD_CLIENT_ID to be your Discord client ID

4. Run npm run serve to start the app, and then go to http://localhost:8080/ to see it

5. You should now be able to hit 'Sign in with Discord'

    * This should redirect you to Discord where it asks you to confirm signing in

    * After confirming, you should be redirected back to the app where after a few seconds, it should display the name of your attached user

    * If it cannot find a user, you should instead see the auth error

6. Success, we've successfully authenticated with Discord in our application!


How to switch out for other OAuth/strategies

There’s a good chance that you’re not using Discord for authentication with your application, so if you do need to swap out for a different provider, all you need to do is change the configuration settings and ensure that the validate still checks for the correct fields, and you’re good to go.


Next steps

Want to play around a bit more with this? Why not try:

  • Implementing registration via the auth
  • Generate and store a token (or use a JWT) on successful login, then use this for authenticating all future requests to your application
  • Connecting to a different OAuth2 provider, like Twitter

The code

This is the example API and UI used for this blog:

API: https://gitlab.com/nerd-vision/blogs/nestjs-oauth-discord

UI: https://gitlab.com/nerd-vision/blogs/nestjs-oauth-discord-ui

Conclusion

Now your application should be set up to authenticate with Discord (or whichever OAuth2 provider you’ve been using)! This tutorial does presume quite a lot about the set up of your application, so if you’ve got any questions or just want to chat, come join us on our public Discord server. We're always happy to see new faces!


Steve

Steve

I'm a Software Engineer on the nerd.vision team. My skills mostly lie in the UI/Node area, but I also work with the backend services.