GitHub example repo
At christian-fei/twitter-oauth-login-in-nodejs on GitHub you can find the whole source code.
Learn how to create a Twitter OAuth Application
OAuth utilities
To authenticate through the Twitter API I found the following set of OAuth utilities pretty useful.
This is the file oauth-utilities.js
:
const oauth = require('oauth')
const { promisify } = require('util')
const TWITTER_CONSUMER_API_KEY = process.env.npm_config_twitter_consumer_api_key || process.env.TWITTER_CONSUMER_API_KEY
const TWITTER_CONSUMER_API_SECRET_KEY = process.env.npm_config_twitter_consumer_api_secret_key || process.env.TWITTER_CONSUMER_API_SECRET_KEY
const oauthConsumer = new oauth.OAuth(
'https://twitter.com/oauth/request_token', 'https://twitter.com/oauth/access_token',
TWITTER_CONSUMER_API_KEY,
TWITTER_CONSUMER_API_SECRET_KEY,
'1.0A', 'http://127.0.0.1:3000/twitter/callback', 'HMAC-SHA1')
module.exports = {
oauthGetUserById,
getOAuthAccessTokenWith,
getOAuthRequestToken
}
async function oauthGetUserById (userId, { oauthAccessToken, oauthAccessTokenSecret } = {}) {
return promisify(oauthConsumer.get.bind(oauthConsumer))(`https://api.twitter.com/1.1/users/show.json?user_id=${userId}`, oauthAccessToken, oauthAccessTokenSecret)
.then(body => JSON.parse(body))
}
async function getOAuthAccessTokenWith ({ oauthRequestToken, oauthRequestTokenSecret, oauthVerifier } = {}) {
return new Promise((resolve, reject) => {
oauthConsumer.getOAuthAccessToken(oauthRequestToken, oauthRequestTokenSecret, oauthVerifier, function (error, oauthAccessToken, oauthAccessTokenSecret, results) {
return error
? reject(new Error('Error getting OAuth access token'))
: resolve({ oauthAccessToken, oauthAccessTokenSecret, results })
})
})
}
async function getOAuthRequestToken () {
return new Promise((resolve, reject) => {
oauthConsumer.getOAuthRequestToken(function (error, oauthRequestToken, oauthRequestTokenSecret, results) {
return error
? reject(new Error('Error getting OAuth request token'))
: resolve({ oauthRequestToken, oauthRequestTokenSecret, results })
})
})
}
An example OAuth 1.0a flow
fastify HTTP server
First import the following modules, fastify
, fastify-session
and fastify-cookie
(npm install
them):
const fastify = require('fastify')
const fastifySession = require('fastify-session')
const fastifyCookie = require('fastify-cookie')
const {
getOAuthRequestToken,
getOAuthAccessTokenWith,
oauthGetUserById
} = require('./oauth-utilities')
const path = require('path')
const fs = require('fs')
HTML files
An index.js
file which invites the user to login, and a template.html
file that renders the logged in user’s screen name.
Read them from the file system:
const INDEX = fs.readFileSync(path.resolve(__dirname, 'client', 'index.html'), { encoding: 'utf8' })
const TEMPLATE = fs.readFileSync(path.resolve(__dirname, 'client', 'template.html'), { encoding: 'utf8' })
Session secret
Generate a session secret, longer than 32 character, and set it as environment variable or in your .npmrc
:
const COOKIE_SECRET = process.env.npm_config_cookie_secret || process.env.COOKIE_SECRET
The main
program
This runs the main program:
main()
.catch(err => console.error(err.message, err))
Create a fastify instance:
async function main () {
const app = fastify({ logger: true })
...
Register the plugins for handling user sessions and cookies:
app.register(fastifyCookie)
app.register(fastifySession, {
cookieName: 'sessionId',
secret: COOKIE_SECRET || 'secretsecretsecretsecretsecretsecretsecret',
cookie: { secure: false },
expires: 900000
})
html pages
Register the /
route, for logged in users the username will be shown with the logout button. For all other users, the INDEX
page with login buttons.
app.get('/', {
handler (request, reply) {
reply.type('text/html')
console.log('/ request.cookies', request.cookies)
if (request.cookies && request.cookies.twitter_screen_name) {
console.log('/ authorized', request.cookies.twitter_screen_name)
return reply.send(TEMPLATE.replace('CONTENT', `
<h1>Hello ${request.cookies.twitter_screen_name}</h1>
<br>
<a href="/twitter/logout">logout</a>
`))
}
reply.send(INDEX)
}
})
The authentication part could probably be achieved with
app.addHook('preHandler', (request, reply, next) => {
// auth logic
next()
})
User logout
The users needs to be able to logout, so you need to register a /twitter/logout
route that clears the cookie and redirects to /
:
app.get('/twitter/logout', logout)
function logout (request, reply) {
reply.clearCookie('twitter_screen_name', { path: '/' })
reply.redirect('/')
}
Initiate OAuth flow
The user clicks on the login button which points to either /twitter/authenticate
or /twitter/authorize
, dependending on the fact if the user has previously authorized the Twitter application or not:
app.get('/twitter/authenticate', twitter('authenticate'))
app.get('/twitter/authorize', twitter('authorize'))
function twitter (method = 'authorize') {
return async (request, reply) => {
console.log(`/twitter/${method}`)
const { oauthRequestToken, oauthRequestTokenSecret } = await getOAuthRequestToken()
console.log(`/twitter/${method} ->`, { oauthRequestToken, oauthRequestTokenSecret })
request.session.oauthRequestToken = oauthRequestToken
request.session.oauthRequestTokenSecret = oauthRequestTokenSecret
const authorizationUrl = `https://api.twitter.com/oauth/${method}?oauth_token=${oauthRequestToken}`
console.log('redirecting user to ', authorizationUrl)
reply.redirect(authorizationUrl)
}
}
Completing the OAuth flow
After the user clicks on the Authorize
button on the Twitter OAuth Consent Screen, they are redirected to the Callback URL set as http://127.0.0.1:3000/twitter/callback
.
This finally sets the user session and a cookie with the twitter_screen_name
:
app.get('/twitter/callback', async (request, reply) => {
const { oauthRequestToken, oauthRequestTokenSecret } = request.session
console.log('request.session', { oauthRequestToken, oauthRequestTokenSecret })
console.log('request.query', request.query)
const { oauth_verifier: oauthVerifier } = request.query
console.log('/twitter/callback', { oauthRequestToken, oauthRequestTokenSecret, oauthVerifier })
const { oauthAccessToken, oauthAccessTokenSecret, results } = await getOAuthAccessTokenWith({ oauthRequestToken, oauthRequestTokenSecret, oauthVerifier })
request.session.oauthAccessToken = oauthAccessToken
const { user_id: userId /*, screen_name */ } = results
const user = await oauthGetUserById(userId, { oauthAccessToken, oauthAccessTokenSecret })
request.session.twitter_screen_name = user.screen_name
reply
.setCookie('twitter_screen_name', user.screen_name, {
domain: '127.0.0.1',
path: '/',
secure: false
})
console.log('user succesfully logged in with twitter', user.screen_name)
reply.redirect('/')
})
Start the server
The server will listen on the port 3000
and print the HTTP address http://127.0.0.1:3000
for easy access (click on it if you’re using iTerm)
try {
await app.listen(3000)
app.log.info(`server listening on ${app.server.address().port}`)
} catch (err) {
app.log.error(err)
process.exit(1)
}
GitHub repo
At christian-fei/twitter-oauth-login-in-nodejs on GitHub you can find the whole source code.