How to use Twitter OAuth 2.0 and Passport.js for user login
Tags: showdev, tutorial, node, twitter
Published: 2022-11-24 13:26:43


**tl;dr** Use the Node.js package [@superfaceai/passport-twitter-oauth2](https://github.com/superfaceai/passport-twitter-oauth2) to handle user authentication with Twitter's v2 API and Passport.js.

{% embed https://github.com/superfaceai/passport-twitter-oauth2 %}

Read on for the background, [how to obtain Twitter credentials](#getting-oauth-20-client-id-and-secret-from-twitter), [example Express app](#authenticating-with-passportjs-and-twitter-in-expressjs), and [code explanation](#breaking-down-the-example).

## Why use OAuth 2.0 for Twitter API

When you want to interact with Twitter's API or just provide a “social login” through Twitter, until last year you had to use [legacy OAuth 1.0a authentication](https://developer.twitter.com/en/docs/authentication/oauth-1-0a). While it still has its uses, Twitter is transitioning to the new version of their API (v2) and [OAuth 2.0](https://developer.twitter.com/en/docs/authentication/oauth-2-0) protocol, which is also used by other providers, like Facebook, Google, and LinkedIn.

Besides being more widespread, Twitter's OAuth 2.0 has other advantages compared to OAuth 1.0a:

- It gives you full access to Twitter's v2 API (including the endpoints for [Twitter Spaces](https://developer.twitter.com/en/docs/twitter-api/spaces/overview) and [editing tweets](https://developer.twitter.com/en/docs/twitter-api/edit-tweets))
- It lets you specify exactly what your application needs from the API through [scopes](https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code), so users have a clear idea what your app can and cannot do with their account; OAuth 1.0a has only three levels of access: “read”, “read + write”, and “read + write + access DMs”
- It is future-proof, as the development effort is focused on v2 API, for which OAuth 2.0 is the default authentication protocol.

When we wanted to build [social media integrations](https://superface.ai/catalog#social-media), we couldn't find any up-to-date Node.js library to handle the authentication flow. So, we built one as a strategy for the popular [Passport.js](https://www.passportjs.org/) authentication middleware.

The strategy is available in `@superfaceai/passport-twitter-oauth2` package. Recently we released a new minor version 1.2, which conducts a full rewrite to TypeScript.

## Getting OAuth 2.0 Client ID and Secret from Twitter

To start using Twitter API, you need to [register for a developer account](https://developer.twitter.com/) (including phone number verification). Once you register, you will be prompted to create the first application. You will immediately receive **API Key** and **API Key Secret** – but **ignore them**, since these are **only for OAuth 1.0**. Instead, go to your project's dashboard, there go to the App Settings of the only application you created.
In application settings, find **User authentication settings** and click Set up.

![Twitter developer portal with application settings. In the User authentication settings section, there is a text 'User authentication not set up' and a 'Set up' button.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ma1vc8a22zn8omvl8nm.png)

In _User authentication settings_ make sure to select **Type of App** as **Web App, Automated App or Bot**.

In **App info** enter the following URL under **Callback URI / Redirect URL**:

```
http://localhost:3000/auth/twitter/callback
```

This is a URL where the user can be redirected after approving the application. The callback will be handled by the example server we will build in the next section.

Fill in also the **Website URL**, since it is a required value. Feel free to ignore other fields.

![A page with User authentication set up opened. 'Type of app' is either 'Native app' or 'Web app, automated app, or bot' with the second option selected. In 'App info' the 'Callback URI / Redirect URL' is set to the localhost address.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/85qhzsfc1umbq83ppuid.png)

Once you save the form, you will get **OAuth 2.0 Client ID and Client Secret**. Make sure to **write these values down**, since you can't reveal the secret later, only regenerate it.

![Prompt with OAuth 2.0 Client ID and Client Secret and a disclaimer that "this will be the last time we'll display the Secret".](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mxv8h5domzm64qtfxzlf.png)

But in case you forget to save the secret, you can always regenerate it under Keys and Tokens section of your app.

![Detail of Keys and Tokens section in application settings, which includes OAuth 2.0 Clients and Secrets section. The Client Secret section allows displaying a hint or regenerate the secret.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1iq785grur5yny5vibck.png)

## Authenticating with Passport.js and Twitter in Express.js

Let's create a simple Express server with Passport.js to show the authentication flow in action. Passport requires some session handling mechanism, usually through [express-session](https://www.npmjs.com/package/express-session). I will also use the [dotenv](https://www.npmjs.com/package/dotenv) package to load the credentials from `.env` file to `process.environment` object.

First, let's create a project and install dependencies:

```shell
mkdir twitter-oauth2-passport-demo
cd twitter-oauth2-passport-demo
npm install express express-session passport @superfaceai/passport-twitter-oauth2 dotenv
```

Now put your Client ID and Client Secret into `.env` file in the root of your project. Use the following template and make sure to paste in the correct values from the developer portal:

```
BASE_URL=http://localhost:3000
TWITTER_CLIENT_ID="OAuth 2.0 Client ID from Twitter Developer portal"
TWITTER_CLIENT_SECRET="OAuth 2.0 Client Secret from Twitter Developer portal"
```

I have also prepared the `BASE_URL` variable, since we will be passing the callback URL during the authentication, and keeping this value configurable makes it easier to take your app to production.
And now, let's create `server.js` file, and put in the following contents:

```js
const express = require('express');
const passport = require('passport');
const { Strategy } = require('@superfaceai/passport-twitter-oauth2');
const session = require('express-session');
require('dotenv').config();

// <1> Serialization and deserialization
passport.serializeUser(function (user, done) {
  done(null, user);
});
passport.deserializeUser(function (obj, done) {
  done(null, obj);
});

// Use the Twitter OAuth2 strategy within Passport
passport.use(
  // <2> Strategy initialization
  new Strategy(
    {
      clientID: process.env.TWITTER_CLIENT_ID,
      clientSecret: process.env.TWITTER_CLIENT_SECRET,
      clientType: 'confidential',
      callbackURL: `${process.env.BASE_URL}/auth/twitter/callback`,
    },
    // <3> Verify callback
    (accessToken, refreshToken, profile, done) => {
      console.log('Success!', { accessToken, refreshToken });
      return done(null, profile);
    }
  )
);

const app = express();

// <4> Passport and session middleware initialization
app.use(passport.initialize());
app.use(
  session({ secret: 'keyboard cat', resave: false, saveUninitialized: true })
);

// <5> Start authentication flow
app.get(
  '/auth/twitter',
  passport.authenticate('twitter', {
    // <6> Scopes
    scope: ['tweet.read', 'users.read', 'offline.access'],
  })
);

// <7> Callback handler
app.get(
  '/auth/twitter/callback',
  passport.authenticate('twitter'),
  function (req, res) {
    const userData = JSON.stringify(req.user, undefined, 2);
    res.end(
      `<h1>Authentication succeeded</h1> User data: <pre>${userData}</pre>`
    );
  }
);

app.listen(3000, () => {
  console.log(`Listening on ${process.env.BASE_URL}`);
});
```

Now run your server with `npm start` and visit `http://localhost:3000/auth/twitter`. You will be redirected to Twitter authorization page:

![Twitter prompt: "Passport OAuth 2.0 Tutorial wants to access your Twitter account." with buttons to "Authorize app" and "Cancel". Below, the following permissions are listed: App can view "All the Tweets you can view, including Tweets from protected accounts" and "Any account you can view, including protected accounts". The app can also "Stay connected to your account until you revoke access".](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1fywglcnqlg95idaq06.png)

And once you authorize the app, you should see the user data from your profile and, in addition, `accessToken` and `refreshToken` will be logged into console.

![Page from the application with the title "Authentication succeeded" and a dump of user data loaded from authorized user's profile.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/19f8ko3zwza210pp58jm.png)

## Breaking down the example

Let's break down the example code above a bit.

### User serialization and deserialization

```js
// <1> Serialization and deserialization
passport.serializeUser(function (user, done) {
  done(null, user);
});
passport.deserializeUser(function (obj, done) {
  done(null, obj);
});
```

These functions serialize and deserialize user to and from session. In our example application, we keep all sessions in the memory with no permanent storage, so we just pass the whole user object.

Typically, you will persist data in a database. In that case, you will store the user ID in the session, and upon deserialization find the user in your database using the serialized ID, for example:

```js
passport.serializeUser(function (user, done) {
  done(null, user.id);
});
passport.deserializeUser(function (id, done) {
  User.findOrCreate(id).then(user => done(null, user));
});
```

The deserialized user object is then accessible through `req.user` property in middleware functions.

### Strategy initialization

```js
// Use the Twitter OAuth2 strategy within Passport
passport.use(
  // <2> Strategy initialization
  new Strategy(
    {
      clientID: process.env.TWITTER_CLIENT_ID,
      clientSecret: process.env.TWITTER_CLIENT_SECRET,
      clientType: 'confidential',
      callbackURL: `${process.env.BASE_URL}/auth/twitter/callback`,
    }
    // ...
  )
);
```

To use an authentication strategy, it must be registered with Passport through `passport.use`. Here, the Twitter OAuth 2.0 strategy is initialized with credentials from Twitter developer portal. The callback URL must be absolute and registered with Twitter – it's where the user is redirected after authorizing the application.

`clientType` can be either `confidential` or `public`, but in case of server-side applications you will usually use `confidential`. As [explained in OAuth specification](https://oauth.net/2/client-types/), `confidential` clients can keep the secret safe, while `public` clients, like mobile applications, can't.

### Success callback

```js
passport.use(
  new Strategy(
    //...
    ,
    // <3> Verify callback
    (accessToken, refreshToken, profile, done) => {
      console.log('Success!', { accessToken, refreshToken });
      return done(null, profile);
    }
  )
);
```

The second argument to the strategy constructor is a [verify function](https://www.passportjs.org/concepts/authentication/strategies/#verify-function). In case of OAuth-based strategies, it is called at the end of _successful_ authorization flow. The user has authorized your application, and you will receive their access token and (optionally) refresh token and user's profile (username, display name, profile image etc.).

Now it's your turn: typically you will want to update or create the user in your database and store the tokens, so you can call the API with them. The `done` callback should receive a user object, which is passed in `req.user` property.

### Passport and Session middlewares initialization

```js
// <4> Passport and session middleware initialization
app.use(passport.initialize());
app.use(
  session({ secret: 'keyboard cat', resave: false, saveUninitialized: true })
);
```

Passport needs to be initialized as middleware as well. And it requires a session middleware for storing state and user data. The most common session middleware is [express-session](https://github.com/expressjs/session).

By default, express-session stores all data in memory, which is good for testing, but not intended for production: if your server gets restarted, all users will be logged out. There is a wide selection of [compatible session stores](https://github.com/expressjs/session#compatible-session-stores) – pick one which fits with the rest of your stack.

### Start the authentication flow

Now we get to the juicy part, routes where the authentication happens. The first route is `/auth/twitter`:

```js
// <5> Start authentication flow
app.get(
  '/auth/twitter',
  passport.authenticate('twitter', {
    //...
  })
);
```

`passport.authenticate` creates a [middleware](https://www.passportjs.org/concepts/authentication/middleware/) for the given strategy. It redirects the user to Twitter with URL parameters, so Twitter knows what application the user is authorizing and where the user should be then redirected back. `authenticate` function accepts a second parameter with additional options, where the most important is `scopes`.

### Authorization scopes

```js
passport.authenticate('twitter', {
  // <6> Scopes
  scope: ['tweet.read', 'users.read', 'offline.access'],
});
```

[OAuth scopes](https://oauth.net/2/scope/) define what the application is allowed to do on behalf of the user. The user can then review and approve these permissions.

In this case, the following scopes are requested:

- `tweet.read` – allows reading of user's and others' tweets (including tweets from private accounts the user follows)
- `users.read` – allows reading information about users profiles
- `offline.access` – allows access even when the user isn't signed in; in practice, the application receives the refresh token

Both `tweet.read` and `users.read` are necessary for accessing information about the signed-in user. `offline.access` is useful when you want to do something when the user isn't directly interacting with your application, for example if you post scheduled tweets, build a bot, or monitor mentions on Twitter.

For a detailed overview of what scopes are used in Twitter's API, check [Twitter's authentication mapping](https://developer.twitter.com/en/docs/authentication/guides/v2-authentication-mapping).

### Callback handler

```js
// <7> Callback handler
app.get(
  '/auth/twitter/callback',
  passport.authenticate('twitter'),
  function (req, res) {
    const userData = JSON.stringify(req.user, undefined, 2);
    res.end(
      `<h1>Authentication succeeded</h1> User data: <pre>${userData}</pre>`
    );
  }
);
```

This is the final step in the authentication flow. After the user authorized your application, they are redirected to `/auth/twitter/callback` route. The `passport.authenticate` middleware is here again, but this time it checks query parameters Twitter provided on redirect, checks if everything is correct, and obtains access and refresh tokens.

If the authentication succeeds, the next middleware function is called – typically you will display some success message to the user or redirect them back to your application. Since the authentication passed, you can now find the user data in `req.user` property.

## Next steps

Passport.js isn't limited to just Express, you can use our strategy with other frameworks with Passport compatibility like [NestJS](https://docs.nestjs.com/security/authentication), [Fastify](https://github.com/fastify/fastify-passport), or [Koa](https://github.com/rkusa/koa-passport).

With the obtained access token, you can start integrating Twitter's API. Check out our [twitter-demo](https://github.com/superfaceai/twitter-demo) repository with CLI scripts showing how to list followers, lookup posts by hashtag, or publish a tweet.

We also have a bit more complex [social-media-demo](https://github.com/superfaceai/social-media-demo) which demonstrates authentication and integrations with Facebook, Instagram, LinkedIn, and Twitter.

I will be diving into more hands-on examples with Twitter, Instagram, and other social media in the future posts. Subscribe to our blog or [sign up for our monthly newsletter](https://share-eu1.hsforms.com/17JDgYID1Qk2qBzYmZAW0WQfn34z), so you don't miss it.

{% embed https://dev.to/superface %}

[Try our strategy](https://github.com/superfaceai/passport-twitter-oauth2) and let me know what you are building with it!

## Resources

- [Announcement of OAuth 2.0 General Availability](https://twittercommunity.com/t/announcing-oauth-2-0-general-availability/163555) by Twitter
- Twitter's guide [Getting Access to the Twitter API](https://developer.twitter.com/en/docs/twitter-api/getting-started/getting-access-to-the-twitter-api) is useful for the general overview of required steps and use of various credentials.
- [Twitter API v2 authentication mapping](https://developer.twitter.com/en/docs/authentication/guides/v2-authentication-mapping) for overview of what scopes are needed where.
- [OAuth 2.0 Authorization Code Flow with PKCE](https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code) explains details of Twitter's OAuth 2.0 implementation, like lifetime of access and refresh tokens, glossary, and overview of all scopes.