diff --git a/examples/state-usage/.env.example b/examples/state-usage/.env.example new file mode 100644 index 0000000..695ef0b --- /dev/null +++ b/examples/state-usage/.env.example @@ -0,0 +1,3 @@ +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" diff --git a/examples/state-usage/README.md b/examples/state-usage/README.md new file mode 100644 index 0000000..ac6ad10 --- /dev/null +++ b/examples/state-usage/README.md @@ -0,0 +1,29 @@ +# Plain JavaScript example for passing state during authentication flow with Passport Twitter OAuth 2.0 Strategy + +This is an example of passing state during the authentication flow with Passport and `@superfaceai/passport-twitter-oauth2` packages on Express server. After a successful login, the application shows user profile information, the state that was passed during the authentication request and logs the access token to a console. + +Check [`@superfaceai/passport-twitter-oauth2`](https://github.com/superfaceai/passport-twitter-oauth2) for more info about the package and [step-by-step tutorial](https://superface.ai/blog/twitter-oauth2-passport) on setting up the Twitter application. + +## Setup + +1. Install dependencies + ```shell + npm i + ``` +1. Copy `.env.example` to `.env` + ```shell + cp .env.example .env + ``` +1. Paste your Client ID and Client Secret from Twitter developer portal to `.env` file + +## Usage + +1. Start the server with + ```shell + npm start + ``` +1. Visit `http://localhost:3000/auth/twitter?state=my-very-long-state-12234567890` + +## Troubleshooting + +If you run into any issues with the example, please don't hesitate to [open an issue](https://github.com/superfaceai/passport-twitter-oauth2/issues/new). diff --git a/examples/state-usage/package.json b/examples/state-usage/package.json new file mode 100644 index 0000000..442f323 --- /dev/null +++ b/examples/state-usage/package.json @@ -0,0 +1,21 @@ +{ + "name": "state-usage", + "version": "1.0.0", + "description": "Plain JavaScript example for passing state during the authentication flow with Passport Twitter OAuth 2.0 Strategy", + "main": "server.js", + "private": true, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": { + "@superfaceai/passport-twitter-oauth2": "file:../..", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "express-session": "^1.17.3", + "passport": "^0.6.0" + } +} diff --git a/examples/state-usage/server.js b/examples/state-usage/server.js new file mode 100644 index 0000000..e11da4d --- /dev/null +++ b/examples/state-usage/server.js @@ -0,0 +1,76 @@ +const express = require('express'); +const passport = require('passport'); +const { Strategy } = require('@superfaceai/passport-twitter-oauth2'); +const session = require('express-session'); +require('dotenv').config(); + +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( + new Strategy( + { + clientID: process.env.TWITTER_CLIENT_ID, + clientSecret: process.env.TWITTER_CLIENT_SECRET, + clientType: 'confidential', + callbackURL: `${process.env.BASE_URL}/auth/twitter/callback`, + }, + (accessToken, refreshToken, profile, done) => { + console.log('Success!', { accessToken, refreshToken }); + return done(null, profile); + } + ) +); + +const app = express(); + +app.use(passport.initialize()); +app.use( + session({ secret: 'keyboard cat', resave: false, saveUninitialized: true }) +); + +app.get( + '/auth/twitter', + (req, res, next) => { + const stateObject = { + key: req.query.state + }; + + passport.authenticate('twitter', { + scope: ['tweet.read', 'users.read', 'offline.access'], + state: stateObject // Passing the state as an object is required by the Passport strategy + })(req, res, next); + } +); + + +app.get( + '/auth/twitter/callback', + passport.authenticate('twitter'), + function (req, res) { + // Regenerate the session to prevent session fixation attacks + req.session.regenerate(function (err) { + if (err) { + return res.status(500).json({ error: 'Failed to regenerate session' }); + } + + const state = JSON.stringify(req.session.req.authInfo.state, undefined, 2); + const userData = JSON.stringify(req.user, undefined, 2); + res.end( + `

Authentication succeeded

User data:
${userData}
+ State: +
${state}
+ ` + ); + }); + } +); + +app.listen(3000, () => { + console.log(`Listening on ${process.env.BASE_URL}`); +});