August 31, 2020
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.
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.
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!
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!
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!
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.
Want to play around a bit more with this? Why not try:
This is the example API and UI used for this blog:
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!
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.