Adding Twitter sign in to your Node.js + Express web application using OAuth

In this tutorial, we'll build a basic Express web application using FusionAuth to handle login via Twitter.

Authors

Published: November 15, 2022


In this tutorial, we’ll build a basic Node.js + Express web application which does user registration and authentication via FusionAuth. We’ll also hook FusionAuth into Twitter’s authentication system, to allow users to easily log in to your app via Twitter.

The application itself is very simple: it will let users sign up via Twitter, and give them access to a “secret” area where their FusionAuth profile is displayed to them. With these basics in place, you’ll see how FusionAuth works and how it can extend the application to do whatever you need. You can, as always, skip ahead and view the code.

Prerequisites

We’ll explain nearly everything that we use, but we expect you to have:

  • Basic Node.js knowledge and a Node.js environment set up.
  • Preferably basic Express knowledge (or knowledge of a similar web framework, or of the middleware concept).
  • Docker and Docker Compose set up as we’ll set up FusionAuth using these.

It’ll also help if you know the basics of OAuth or authentication in general.

Why FusionAuth instead of plain Passport?

Passport is a one of the commonly used authentication systems in Express apps. It is very powerful, and allows you to hook into social providers, OpenID and OAuth providers, or use a local authentication strategy. This sounds like everything you’ll ever need, but there are still a few missing pieces. For example, you still need to construct your own login page and other account functionality such as resetting passwords, forgotten password resets, 2FA, email verification, account protection and more. Setting up custom web app authentication is always more complicated than it seems.

The great news is that combining Passport with FusionAuth makes a complete system, which takes care of all aspects of authentication. It also means that much of your app’s authentication capability can be configured through FusionAuth, rather than writing code and modifying your app. For example, you can easily add social login providers whenever you need to, without changing code or redeploying your app.

With this setup, authentication concerns are taken care of entirely by FusionAuth.

The image below shows how this works.

Important private data goes in FusionAuth. Everything else in Node-Express. FusionAuth coordinates with other identity providers

Your application logic and all public information can be handled by Node.js + Express. Anything sensitive, such as personally identifiable information (PII), is handled by FusionAuth.

This allows you to focus a majority of your security efforts on the FusionAuth installation. It also means that if you create more applications, they can piggyback on your centralised authentication instead of having to re-implement authentication for every application that you build. You can also create a multi-tenant configuration allowing you to easily have logically separate environments for different clients.

Also, any integrations that you set up with other providers (e.g. Twitter sign-in) can be done once, instead of per application.

Installing and configuring FusionAuth with Docker Compose

There are various ways to install FusionAuth depending on your system, but the easiest way is to use Docker and Docker Compose. Instructions are here. Currently, to install and run FusionAuth you would run (again, assuming you have Docker installed) the following commands:

curl -o docker-compose.yml https://raw.githubusercontent.com/FusionAuth/fusionauth-containers/main/docker/fusionauth/docker-compose.yml
https://raw.githubusercontent.com/FusionAuth/fusionauth-containers/main/docker/fusionauth/docker-compose.override.yml
curl -o .env https://raw.githubusercontent.com/FusionAuth/fusionauth-containers/main/docker/fusionauth/.env
docker compose up

Note that this uses a public .env file containing hard-coded database passwords and is not suitable for production use.

For Twitter integration, we recommend setting up FusionAuth on a publicly available URL. This is because Twitter does not redirect to localhost addresses for OAuth callbacks. Some developers have luck setting a local hosts file entry, or using 127.0.0.1 instead of localhost, but the most reliable option is to host FusionAuth on a publicly accessible URL. Bear in mind the extra security considerations of this option.

Configuring FusionAuth

FusionAuth should now be running and reachable on your chosen URL, or http://localhost:9011 if you’ve installed it locally. The first time you visit, you’ll be prompted to set up an admin user and password. Once you’ve done this, you’ll be prompted to complete three more setup steps, as shown below.

FusionAuth prompts us with the setup steps that we need to complete.

We’ll skip step #3 in this tutorial, but sending emails (to verify email addresses and do password resets) is a vital part of FusionAuth running in production, so you’ll want to do that.

Creating an application

Click “Setup” under “Missing Application” and call your new app “Twitter Express”, or another name of your choice. It’ll get a Client Id and Client Secret automatically - save these, as we’ll use them in the code. Later, we’ll set up a Node.js + Express application which will run on http://localhost:3000, so configure the Authorized URLs accordingly. You should add:

  • http://localhost:3000/auth/callback to the Authorized redirect URLs.
  • http://localhost:3000/ to the Authorized request origin URL.
  • http://localhost:3000/logout to the Logout URL.

Configuring the application URLs in FusionAuth.

Click the Save button at the top right for your changes to take effect.

Setting up a FusionAuth API key

Once the user has logged in via the FusionAuth application, we can retrieve their FusionAuth profile using the FusionAuth Typescript module, provided with an API key.

Navigate to Settings and then API Keys, then add a key. Add a name for the key and take note of the generated key value.

Getting the API key from FusionAuth.

For extra security, you can restrict the permissions for the key. For our app, we only need to enable the actions for /api/user/, which will let the key carry out basic actions on users. If you leave the key with no explicitly assigned permissions, it will be an all-powerful key that can control all aspects of your FusionAuth app.

Limiting the scope of the created API key.

Creating an application on Twitter

In order to allow our users to sign into our app using their Twitter account, you’ll need to sign up for a developer account on Twitter. The full instructions for doing this are available here.

Use https://<YOUR_FUSIONAUTH_URL>/oauth2/callback (where https://<YOUR_FUSIONAUTH_URL> is your FusionAuth installation address) for the Callback URI / Redirect URL. This will allow our FusionAuth app to talk to Twitter servers and have them authenticate users on our behalf, as shown below.

Adding authorized URLs to Google.

Once you’ve set up everything on Twitter, you’ll need to complete your setup by adding Twitter as an identity provider to FusionAuth and copying in the API key and secret that you received from Twitter. These are different values from the FusionAuth application’s Client Id and Client Secret. You can find the detailed steps for setting up Twitter as a third-party login via FusionAuth here.

In the next step, make sure that you have enabled Twitter integration and turn on “Create registration” for your FusionAuth + Express app. Also don’t forget to hit the save button.

Enabling Twitter registration in our application.

Now we’ve done most of the admin. Let’s build a Node.js + Express app!

Setting up Express

To get started, you should:

  • Scaffold a new Express application.
  • Install the scaffolded dependencies.
  • Install Passport and helper libraries, and the FusionAuth Typescript client.
  • Start the server to ensure everything is installed and working.

Here are the commands to do it:

npx express-generator --view=hbs fusion-twitter
cd fusion-twitter 
npm install
npm install passport passport-oauth2 connect-ensure-login express-session @fusionauth/typescript-client
npm start

If all went well, the server should start successfully and you can visit http://localhost:3000.

Express app default home page

Building the application

Our application will only have three pages, including the FusionAuth login page.

  1. A home page - a public page showing how many users our app has and inviting users to log in.
  2. The login/sign-up page (redirected to FusionAuth) with options to use a username/password or to sign in with Twitter.
  3. A logged in private “Member’s Only” page. This will display the user’s profile retrieved from FusionAuth.

Adding and initializing dependencies

To start, we’ll add the references needed for Passport, the passport-oauth2 strategy, and enabling sessions using Express Sessions. We’ll also add a reference to the FusionAuth typescript client.

Open the app.js file. Below the line var logger = require('morgan'); add the following:

var passport = require("passport");
var OAuth2Strategy = require("passport-oauth2").Strategy;
var passOAuth = require("passport-oauth2");
var { FusionAuthClient } = require("@fusionauth/typescript-client");
var session = require("express-session");
const { ensureLoggedIn } = require('connect-ensure-login');

Now, we can initialize and add Passport and the session handler to the Express pipeline. Add the following just below the app.use(express.static(path.join(__dirname, 'public'))); line:

app.use(session({ secret: "TOPSECRET" }));
app.use(passport.initialize());
app.use(passport.session());

Replace the TOPSECRET string with a string of your choosing. This secret is used to sign the session information in the cookie. Normally, this is kept secret, as anyone who has access to the secret could construct a session cookie that looks legitimate to the server and gives them access to any account on the server. You can also add an environment variable to store this secret, rather than store it in the code repo.

We’ll also need to initialize the FusionAuth client with the API key created earlier. This will allow us to retrieve the user profile from FusionAuth after a successful login. Add the following code just below the previous code added:

const fusionClient = new FusionAuthClient(
  "<YOUR_FUSION_API_KEY>",
  "https://<YOUR_FUSIONAUTH_URL>"
);

Replace the parameter <YOUR_FUSIONAUTH_URL> with the URL your FusionAuth instance is located at. Replace <YOUR_FUSION_API_KEY> with the API key created earlier.

Now we can initialize the Passport strategy. We’ll be connecting to FusionAuth using OAuth2, so we’ll use the passport-oauth2 strategy. Add the following code directly below the code you’ve just added:

passport.use(
  new OAuth2Strategy(
    {
      authorizationURL: "https://<YOUR_FUSIONAUTH_URL>/oauth2/authorize",
      tokenURL: "https://<YOUR_FUSIONAUTH_URL>/oauth2/token",
      clientID: "<YOUR_FUSIONAUTH_APP_CLIENTID>",
      clientSecret: "<YOUR_FUSIONAUTH_APP_CLIENT_SECRET>",
      callbackURL: "http://localhost:3000/auth/callback",
    },
    function (accessToken, refreshToken, profile, cb) {
      // Get the user profile from Fusion:
      fusionClient
        .retrieveUserUsingJWT(accessToken)
        .then((clientResponse) => {
          return cb(null, clientResponse.response.user);
        })
        .catch((err) => {
          console.error(err);
        });
    }
  )
);

Replace the parameter <YOUR_FUSIONAUTH_URL> with the URL your FusionAuth instance is located at. Replace <YOUR_FUSIONAUTH_APP_CLIENTID>, and <YOUR_FUSIONAUTH_APP_CLIENT_SECRET> with the values you saved during the FusionAuth application setup earlier.

This snippet of code sets up the OAuth parameters for the Passport strategy. The strategy has a callback which is invoked when a successful authorization and token call has been completed to FusionAuth. The FusionAuth client has a handy method to retrieve a user by the JWT returned from the authorization process. We can use this to get the user, and return it to the Passport strategy callback. This user will then also be passed to our session handler to save, and added to the req parameter in subsequent middleware handlers as req.user. To enable this, add the following code, below the code you just added:

passport.serializeUser(function (user, done) {
  process.nextTick(function () {
    done(null, user);
  });
});

passport.deserializeUser(function (user, done) {
  process.nextTick(function () {
    done(null, user);
  });
});

Adding Express routes

We’ve got the basic framework and authorization code set up. Now we can add some routes. We’ll start with the login route to handle the redirect to FusionAuth, which will in turn handle the calls to Twitter.

Add this code under the app.use("/", indexRouter); line:

app.get("/login", passport.authenticate("oauth2"));

Note that we don’t need to add a router or view for the login redirect to FusionAuth to work. Passport will check whether the user needs to be logged in, and if so will send them to FusionAuth for authentication.

After authentication, FusionAuth will redirect to the callback route we provided in the Passport OAuth setup, as well as in the authorized callback route earlier. We can add this route now. Add the following code under the login route:

app.get(
  "/auth/callback",
  passport.authenticate("oauth2", { failureRedirect: "/" }),
  function (req, res) {
    // Successful authentication, redirect home.
    res.redirect("/");
  }
);

On successful authentication or failure, we’ll redirect to the homepage. Let’s update that now to show the login status, and provide a link to the “Members Only” area. Open the index.js file in the routes folder, and update the get route to the following:

router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express', "authenticated": req.isAuthenticated() });
});

Passport adds a function isAuthenticated() to the req object. Querying this function tells us whether the user is logged in. We add this to the keys and values passed to the index view, so that we can show a different message based on the user’s authentication status.

Now open the index.hbs file in the views folder, and update the code to the following:

<h1>{{title}}</h1>
<p>Welcome to {{title}}</p>

{{#if authenticated}}
  <p>You are logged in!</p>
  <p>
    You can now visit the super secure <a href="/users/me">members only area</a>
  </p> 
  <p>
    Or, you can <a href="<<YOUR_FUSIONAUTH_URL>/oauth2/logout?client_id=<YOUR_FUSIONAUTH_APP_CLIENTID>">log out here</a>
  </p>
{{else}}
  <p>
    <a href="/login">Login Here</a>
  </p>
{{/if}}

This will notify the user if they are logged in or not, and point them to the relevant page.

If they are logged in, besides a link to the member’s only area, we provide a link to FusionAuth to start the log out process. Replace <YOUR_FUSIONAUTH_URL> and <YOUR_FUSIONAUTH_APP_CLIENTID> with your FusionAuth installation address, and application Client Id. Note that log out won’t fully work until we add the necessary routes on Express to destroy the local session. We’ll do that in a bit.

Adding a members only area

Now that we have the basic login and authentication mechanics set up, we can add a restricted route that is only available to users that are logged in.

In the users.js file in the routes folder, modify the get route to the following:

router.get('/me', function(req, res, next) {
  const user = req.user; 
  res.send('You have reached the super secret members only area! Authenticated as : ' + JSON.stringify(user, null, '\t'));
});

Now, we need to secure the route to this page to users that are authenticated. To help with that, we’ll use the connect-ensure-login middleware we installed earlier. Update the users route in the app.js file from:

app.use('/users', usersRouter);

to:

app.use('/users', ensureLoggedIn('/login'), usersRouter);

The ensureLoggedIn middleware checks if the user is authenticated before proceeding to the router (or following middleware). It redirects to the login page if the user is not logged in.

Logging out

When we set up the FusionAuth application, we provided the Logout URL http://localhost:3000/logout. To complete the logout process, we’ll need to add this logout URL on our express app. FusionAuth calls this URL once it has logged the user out of the FusionAuth application, which gives us the opportunity to destroy the local Express session. Add the following code under the app.use('/users', ensureLoggedIn('/login'), usersRouter); line:

app.get('/logout', function (req, res, next) {
  req.session.destroy();
  res.redirect(302, '/');
});

Now, when the user clicks the log out link we added on the home page, they will be redirected to FusionAuth which will destroy the FusionAuth session and cookie. Then FusionAuth will redirect back to out app’s logout route, which will destroy our local session and redirect the user back to the home page. Therefore, the user will be logged out of both the FusionAuth application and the local app.

Testing

We are done with the demo. Type npm start at the console to start up the server. Then navigate to localhost:3000, preferably in a private tab. This ensures that your main admin login to FusionAuth is not a confounding factor while logging in.

You should see the main page looking something like this:

The main page when logged out

Clicking on “Login Here” should redirect you to your FusionAuth installation, with a Twitter button as a login option.

The FusionAuth login page, with Twitter as an option

Clicking the “Login with Twitter” button should redirect you to Twitter to complete the authentication.

The Twitter page asking permission to authenticate you to your FusionAuth instance

Entering your Twitter credentials should now redirect you back to the app’s root page, where you should see a logged in message.

The root page message for logged in users

Clicking on the “Members Only area” link should take you to users/me, showing a JSON object representing your profile on FusionAuth (with the username from Twitter).

The users/me page showing the user's FusionAuth profile

Where to next with Express and FusionAuth?

That’s the basics of our Express + Twitter + FusionAuth app done. The app has a fully featured authentication system, without the hassle and possible risks of implementing all of that code ourselves. The complete code is hosted on GitHub here.

Of course, you would need to add more interesting features to this app for it to be useful. But being able to take care of the authentication, social sign-in, and general security with just a small amount of configuration code leaves a lot more time for your application’s more useful and critical features.

For a production environment, you would also need to do a bit more work in making sure FusionAuth was really safe. In our example, we used the default password provided with Docker for our database, left debug mode on, and ran FusionAuth locally, co-hosted with our Express application. For a safer setup, you would run FusionAuth on its own infrastructure, physically separate from the Express app, and take more care around production configuration and deployment. FusionAuth gives you all of the tools to do this easily.

Subscribe to The FusionAuth Newsletter

A newsletter for developers covering techniques, technical guides, and the latest product innovations coming from FusionAuth.

Just dev stuff. No junk.