xwiz-server
v1.0.5
Published
X OAuth 1.0a library, gets you an access token , supports REST and STREAM apis (node.js part)
Maintainers
Readme
xwiz-server
X OAuth wizard. Works with xwiz-client.
xwiz-server does authentication and/or authorization to X with OAuth 1.0a, has built in REST api support and also supports third party STREAM and REST libs.
xwiz-clientis a browser script.
xwiz-serveris an Express middleware.
Contents
Intro
Many X apis require user authentication (access token) before usage. OAuth 1.0a is (essencially a digital signature) process of letting know who (which app) wants to use an api and on which user's behalf. In other words you tell X who you are, if X is ok with you it lets you to ask an user of your website (with X account), on authorization page, if he agrees that you act on his behalf (like post tweets on user's profile ect ...)
It happens to be a 3-leg (step) dance, it's implementation could look like this:

As you can see there are 3 actors. Your web app/site, your server and X.
X apis for authorization and authentication do not use CORS, that is, they do not emit CORS headers. So we cant send request to X directly from browser, but rather proxy them trough a Server, since request sent from server do not have any CORS restrictions applied. Your app is identified with CONSUMER_KEY and CONSUMER_SECRET. Both of which you get from X when you create new app.
On X, user is internally associated with the
access token. Like user'saccess token, your app'skeyandsecretare extreamly sensitive information. If anyone whould to get your app's key/secret then it can send requests to X just like they were sent from your app. It can try to mock you. Usually that's not what you want. Also, javascript is in plain text form in browser so we should never have ourCONSUMER_KEY/SECRETthere but on server. This is another good reason why we need a server to proxy our requests. Likewise if anyone is to get user'saccess token, then it may be able to send requests on user's behalf (who may actually never visited an app) and/or authorized such actions for that app. Altough, my guess is not in easy and straightforward way.
You ask your server to get you a
request token. Server prepares and signs your request and sends it to X. X checks yourCONSUMER_KEYand signature (by reproducing it). If all is ok, it grants (returns) you an unauthorizedrequest token. It's function is to be authorized (approved) by user in step 2, so it can be used to get user'saccess tokenin step 3.Uppon receiving
request tokendata, user is redirected to X to obtain user authorization :
if you are using
/oauth/authorizeenpoint for redirection, then every time user is redirected there it lands on authorization (interstitials) page. Even if user previously authorized your app.if you are using
/oauth/authenticateenpoint for redirection, then only first time user is redirected it lands on authorization page. On any subsequent redirection X remembers first one and user is directed back again to the app. No authorization page is showed, the user is not involved directly. Historicaly it didn't work like it should, for a time. Two were actually the same where/oauth/authenticateacted like/oauth/authorize.Even there is no authorization page showed, there still can be a redirection page flash.
- Since user approved your
request token, now it is used to get user'saccess token. Server signs your request and sends it. X does things similarly like in first step and grants you anaccess tokenwhich belongs to the user who authorized it in second step.
After we get user's access token the 3-leg OAuth is finished and we can use it to send request to X on user's behalf (like posts tweets on user's profile). In OAuth parlance this process of sending requests with the access token is called accessing protected resources, but it is not part of the OAuth.
We can see that in 3-rd leg access token is send back to web app. Which usually is not good idea, because of security implications it could have, like we mentioned earlier.
Let's see what xwiz is doing with OAuth:

Three differences are:
Optimized load
Preparing an
OAuthleg (step) mostly refers to the assembling ofSignature Base StringandAuthorization Header string. It is all done in browser (Consumer) in an effort to ease the server load for actions that bear no security concerns and thus do not need to be executed in server. Already prepared requests come to the server who acts mostly like a signing/stamping authority by merely inserting sensitive data and signing requests.Security
Another important point is that the user's access token is never send back to the browser by any means.
Haste
On the server we have a decision point where if we already have user's
access token(stored from a previous authorization ie. in database) we can usehaste(on diagram the yes branch ).Haste is a process where you verify
access tokenfreshness (verify credentials) with X and if it's fresh you can immediately go for a X api requests user actually wants. Checking token freshness is checking that user didn't revoke right to your app of doing things on it's behalf and such.This is usefull for scenarios where you save user's
access tokenafter first authorization and then just check for it's freshness before you go for an api request. User does not need to be bothered every time with 3-legOAuth, there is no interstitials page. With haste you are the one who remembers user authorization instead of letting X to do it (like it does on the/oauth/authenticate). All in order to have smooth user experience, like for instance in case/oauth/authenticatestops working as expected. Or when redirection page flashes for the moment before user is returned to app and you would like to remove that behaviour.If this is the first time a user is making a request (and we dont have the
access token) then we just continue the wholeOAuth flow(on diagram the no branch). One of xwiz's features is very easy switching between any of yourOAuthworkflows while having a redundant mechanism for smooth user experience (haste) as an option.
Examples
⬑
In order to efficiently and safely use xwiz make sure you:
- Provide HTTPS all the way (client ---> server --> X)
- In browser install
xwiz-client, on server installxwiz-server - Create app account on X
- Users (of your app) must have X accounts
Example 1 (not saving access token)
Simple use case where you do not save user's access token.
⬑
CDN: <script type="module" src="https://cdn.jsdelivr.net/npm/xwiz-client/src/xwiz-client_bundle.js"></script>
Then import in your script:
`import twizClient from 'https://cdn.jsdelivr.net/npm/xwiz-client/src/xwiz-client_bundle.js'`
(Alternatively define an importmap to shorten the long CDN urls).
Local npm:
`npm install xwiz-client`
Local yarn:
`yarn add xwiz-client`
Then drop it in script tag:
<script type="module" src="src/xwiz-client_bundle.js"></script>
Or if you are using your own builder make sure it picks up: "src/xwiz-client.js" file
client
// Let's say this code is in your page -> https://myApp.com
import xwizClient from "./xwiz-client_bundle.js";
let xwizlent = xwizClient();
// lets say we initiate oauth on click event
btn.addListener('onClick', function(){
let args = {
// address of your xwiz-server
server_url: 'https://myServer.com/xwiz-server-route',
// the location where X will redirect user after authorization
redirection_url: 'https://myApp.com/redirect-path',
// X request options
options:{
method: 'POST',
path: '/2/tweets'
body: {
text: "Hooray, new post on X!"
}
encoding: 'json'
}
}
try {
const res = await xwizlent.OAuth(args):
// handle errors
if(res.error) {
// >= 400 responses ( contains error.statusCode, error.statusText and error.data)
}
// flag that indicates browser redirection (302 - Redirect) to the redirection_url
if(res.redirection) {
console.log('Browser redirects');
}
/*
Always present in case you need to pull some data from response
(like custom server headers you might be sending)
*/
console.log(res.xhr)
}
catch(e){
// xwiz errors
console.log(e);
}
})In code for redirection_url (https://myApp.com/redirect-path) invoke the finishOAuth().
// Code in redirection_url
import xwizClient from "./xwiz-client_bundle.js";
let xwizlent = xwizClient();
try {
const args = {
// address of your xwiz-server
server_url: 'https://my-node-server.com/xwiz-server-route',
// X request options
options:{
method: 'POST',
path: '/2/tweets'
body: {
text: "Hooray, new post on X!"
}
encoding: 'json'
}
}
const res = await xwizlent.finishOAuth(args);
// handle errors
if(res.error){
// >= 400 responses
}
// handle success
if(res.data){
// will have data on succesfull xwiz.continueOAuth() call on server
}
/*
Always present in case you need to pull some data from response
(like custom server headers you might be sending)
*/
console.log(res.xhr);
}
catch(e){
// xwiz errors
console.log(e)
}Note that redirection_url does not need to be different. In example above it could have been https://myApp.com/.
Check session_data to see how you can append data to redirection_url,
For popups check popup.
server
xwiz-server is writen as an express middleware.
Continue OAuth
⬑
Since you are not saving user's access token, call the continueOAuth().
npm install xwiz-server
import xwizServer from 'xwiz-server';
import express from 'express'
const app = express();
const xwizer = xwizServer({
// Your X app account - consumer_secret
consumer_secret: process.env.CONSUMER_SECRET,
// Your X app account - consumer_key
consumer_key: process.env.CONSUMER_KEY
})
// add middleware
app.use('/xwiz-server-route', xwizer);
/*
Simple use case (you are not saving user's access token)
*/
app.on('hasteOrOAuth', async function (xwiz, verifyCredentials) {
/*
When you dont want to save access token (or don't want to use haste),
you hit complete 3 leg OAuth every time you are going to X on user behalf
*/
xwiz.continueOAuth();
})
Call stack from browser to node:
client : OAuth(args) =====> server continueOAuth() ====> client: finishOAuth(args).
This happens every time you are going to X in user behalf.
You get res.redirection set to true. Browser redirects from the page you are in.
You get final data in finishOAuth(args) call.
Test drive example 1
Test drive the Sign in with X + api data.
Example 2 (saving access token)
Improved use case where you are saving user's access token for reuse. Here redirection happens only first time. Subsequently you are using access token and going straight to an X API. Thus skipping the redirection and the whole OAuth process.
⬑
client
// Let's say this code is in your page -> https://myApp.com
import xwizClient from "./xwiz-client_bundle.js";
let xwizlent = xwizClient();
// lets say we initiate oauth on click event
btn.addListener('onClick', function(){
let args = {
// address of your xwiz-server
server_url: 'https://myServer.com/xwiz-server-route',
// the location where X will redirect user after authorization
redirection_url: 'https://myApp.com/redirect-path',
// X request options
options:{
method: 'POST',
path: '/2/tweets'
body: {
text: "Hooray, new post on X!"
}
encoding: 'json'
}
}
try {
const res = await xwizlent.OAuth(args):
// handle errors
if(res.error) {
// >= 400 responses ( contains error.statusCode, error.statusText and error.data)
}
// flag that indicates browser redirection (302 - Redirect) to the redirection_url
if(res.redirection) {
// will redirect only first time, when you've not yet accuired user's access token
console.log('Browser redirects');
return;
}
// handle success
if(res.data){
// will have data on succesfull xwiz.haste(accessToken) call on server
}
/*
Always present in case you need to pull some data from response
(like custom server headers you might be sending)
*/
console.log(res.xhr)
}
catch(e){
// xwiz errors
console.log(e);
}
})In code for redirection_url (https://myApp.com/redirect-path) invoke the finishOAuth().
// Code in redirection_url
import xwizClient from "./xwiz-client_bundle.js";
let xwizlent = xwizClient();
try {
const args = {
// address of your xwiz-server
server_url: 'https://my-node-server.com/xwiz-server-route',
// X request options
options:{
method: 'POST',
path: '/2/tweets'
body: {
text: "Hooray, new post on X!"
}
encoding: 'json'
}
}
const res = await xwizlent.finishOAuth(args);
// handle errors
if(res.error){
// >= 400 responses
}
// handle success
if(res.data){
/*
will have data on succesfull xwiz.continueOAuth() call on server
only first time you go to X for user's access token
*/
}
/*
Always present in case you need to pull some data from response
(like custom server headers you might be sending)
*/
console.log(res.xhr);
}
catch(e){
// xwiz errors
console.log(e)
}Verify credentials and haste
⬑
server
Save user's access token on server.
Then check for it's fresheness with verifyCredentials(..) and got straight to an X API with haste(..)
Without doing complete OAuth for a user, every time you are want to go to X.
Like in simple use case with continue oauth.
npm install xwiz-server
import xwizServer from 'xwiz-server';
import express from 'express'
const app = express();
const xwizer = xwizServer({
// Your X app account - consumer_secret
consumer_secret: process.env.CONSUMER_SECRET,
// your X app account - consumer_key
consumer_key: process.env.CONSUMER_KEY
})
// add middleware
app.use('/xwiz-server-route', xwizer);
/*
event where we pick `haste()` (you already saved user's access token),
or `continueOAuth()` (you did not yet accuired user's access token)
*/
app.on('hasteOrOAuth', async function (xwiz, verifyCredentials) {
try { /
/**
* Go for saved access token in your database (see 'tokenFound' bellow),
* if found go for verifyCredentials(), then for xwiz.haste()
*/
let accessToken = await goForUserAccessTokenInDB();
if (!accessToken) throw "User's access token not found";
// check user's access token freshness
let credentials = await verifyCredentials(accessToken, { skip_status: true })
// skip 3leg OAuth flow (gets X API data end sends back to browser)
xwiz.haste(accessToken)
} catch (err) {
// go for 3 leg OAuth only first time, when you don't have user's access token
xwiz.continueOAuth();
}
})
// when xwiz gets user's access token you can save it here in 'token found' event
app.on('tokenFound', function(token){
try {
// user's access token received from X that you can put in database
let accessToken = await token;
} catch (e) {
console.log(e);
}
})Call stack from browser to node:
client : OAuth(args) =====> server verfyCredentials(accessToken) ; haste(accessToken).
You get res.data from OAuth(args) call.
There is no redirection.
Redirection happens only first time, when you do not yet have an access token.
Authorize or Authenticate (Sign In with X)
⬑
By default xwizlent.OAuth(..) will use the /oauth/authorize endpoint , but you can use the /oauth/authenticate like this:
browser:
let args = {
...
endpoints:{
authorize: 'authenticate' // sets authenticate instead of authorize (notice no forward slash)
}
}This is the so called Sign in with X flow, the one that uses /oauth/authenticate endpoint. By default, xwiz gets you an access token and gets your api data immediately afterwards. After these actions you can specify your own end with xwiz.onEnd(). This is handy if you are using template rendering to show the signed-in UI to user, wheater on front-end or back-end.
Since it gets you an access token and api data in one swoop, xwiz makes the sign-in-with-X button unecessary, the whole proccess can happen in one click of a button.
As opposed to:
1. user clicks the sign-in-with-twitter button to authorize your app (you display the signed-in user UI)
2. user clicks another button to get data it wants from an X api
Access Token
⬑
Currently the minimum of what xwiz see as valid access token is an object that has properties oauth_token and oauth_token_secret set. But it can have other parameters, like screen_name.
The xwiz-server (here xwizer) is by default an ending middleware, that is it will end the request. So call it before your error handling middlewares, if any. There are cases when xwiz does not end the request, check Stream usage. Errors will be sent to the next error handling midleware with next(err) calls and same errors will also be piped back to the browser.
Prefligh
⬑
If your app is not on same domain your browser will preflight request because of CORS. So you need to use some preflight middleware before xwiz-server:
node.js:
...
app.use(yourPreflight);
app.use(xwizer);Currently you only have to set Access-Control-Allow-Origin to your app's fqdn address.
Verify credentials
⬑
The credentials object in fulfileld handler can contain a lot of information. In order to ease the memory
footprint you can use parameters object (like one with skip_status) to leave out information you don't need. Here is the list of params you can use.
Popup
⬑
Put the new_window object to args to specifiy your new popup / window characteristics and call xwizlent.finishOAuth(..) from code in that popup / window .
Note that browser doesn't differentiate much between a popup and a new window. Main difference is in dimentions.
client:
// Let's say this code is in your page -> https://myApp.com
let xwizlent = xwizClient();
// lets say we initiate oauth on click event
btn.addListener('onClick', function(){
let args = {
// address of your node server
server_url: 'https://myServer.com/xwiz-server-route',
// location of your popup/window page
redirection_url: 'https://myApp.com/popupWindow',
// popup specs
new_window:{
name: 'mypopupWindow',
features: 'resizable=yes,height=613,width=400,left=400,top=300'
},
// X request options
options:{
method: 'POST',
path: '/2/tweets'
body: {
text: "Hooray, new post on X!"
}
encoding: 'json'
}
}
try {
const res = await xwizlent.OAuth(args):
// handle errors
if(res.error) {
// >= 400 responses ( contains error.statusCode, error.statusText and error.data)
}
// handle success
if(res.data){
// will have data on succesfull xwiz.haste(accessToken) call on server
}
/*
when redirection happens instead of res.redirection notification you'le get a
reference to the popup window and the redirection will happen from that window
*/
if(res.window) {
// a reference to the popup window, when it get's opened
}
/*
Always present in case you need to pull some data from response
(like custom server headers you might be sending)
*/
console.log(res.xhr)
}
catch(e){
// xwiz errors
console.log(e);
}
})The redirection_url is now the location of the popup window.
In the new_window we specify the window/popup features where redirection_url will land . Making this more of a website use case.
The new_window object contains two properties, name and features, they act the same as windowName and windowFeatures in window.open().
Note res.window reference to newly opened window / popup instead of res.redirection.
// code in https://myApp.com/popupWindow
import xwizClient from "./xwiz-client_bundle.js";
let xwizlent = xwizClient();
try {
const args = {
// address of your xwiz-server
server_url: 'https://my-node-server.com/xwiz-server-route',
// X request options
options:{
method: 'POST',
path: '/2/tweets'
body: {
text: "Hooray, new post on X!"
}
encoding: 'json'
}
}
const res = await xwizlent.finishOAuth(args);
// handle errors
if(res.error){
// >= 400 responses
}
// handle success
if(res.data){
// will have data on succesfull xwiz.continueOAuth() call on server
}
}
catch(e){
// xwiz errors
console.log(e)
}What this enables is to have completely custom popup pages but same familiar popup, like for instance when you whould like to share something on X by pressing a X share button. Currently the downside is that users of the web site use case will get a popup warning by browser which they have to allow before popup apears.
server
import xwizServer from 'xwiz-server';
import express from 'express'
const app = express();
const xwizer = xwizServer({
// Your X app account - consumer_secret
consumer_secret: process.env.CONSUMER_SECRET,
// your X app account - consumer_key
consumer_key: process.env.CONSUMER_KEY
})
// add middleware
app.use('/xwiz-server-route',xwizer);
/*
event where we pick `haste(..)` (you already saved user's access token),
or `continueOAuth(..)` (you did not yet accuired user's access token)
*/
app.on('hasteOrOAuth', async function (xwiz, verifyCredentials) {
try { /
/**
* Go for saved access token in your database (see 'tokenFound' bellow),
* if found go for verifyCredentials(), then for xwiz.haste()
*/
let accessToken = goForUserAccessTokenInDB();
if (!accessToken) throw "User's access token not found";
// check user's access token freshness
let credentials = await verifyCredentials(accessToken, { skip_status: true })
// skip 3leg OAuth flow (gets X API data end sends back to browser)
xwiz.haste(accessToken)
} catch (err) {
// go for 3 leg OAuth only first time, when you don't have user's access token
xwiz.continueOAuth();
}
})Test drive popup
Test drive the custom pop-up workflow.
Errors
⬑
Browser
xwizlent.OAuth(..) rejected(..) handler:
error.name | error.message
----------- | --------------
redirectionUrlNotSet | You must provide a redirection_url to which users will be redirected.
serverUrlNotSet | You must proivide server_url to which request will be sent.
optionNotSet | Check that method and path are set.
noCallbackFunc | You must specify a callback function.
callbackURLnotConfirmed | Redirection(callback) url you specified wasn't confirmed by X.
noContentType | Failed to get content-type header from response. Possible CORS restrictions or header is missing.
chunkedResponseWarning | Stream is consumed chunk by chunk in xhr.onprogress(..) callback.
xwizlent.finishOAuth(..) rejected(..) handler:
error.name | error.message
----------- | --------------
verifierNotFound | "oauth_verifier" string was not found in redirection(callback) url.
tokenNotFound | "oauth_token" string was not found in redirection(callback) url.
tokenMissmatch | Request token and token from redirection(callback) url do not match.
requestTokenNotSet | Request token was not set.
requestTokenNotSaved | Request token was not saved. Check that page url from which you make request match your redirection_url.
chunkedResponseWarning | Stream is consumed chunk by chunk in xhr.onprogress(..) callback.
noRepeat | Cannot make another request with same redirection(callback) url.
spaWarning | X authorization data not found in url.
spaWarning and noRepeat are errors that have informative character and usually you dont have to pay attention to them. They happen when user loads/relods page where xwizlent.finishOAuth(..) is called on every load, imediately (which is valid). They are indications that xwizlent.finishOAuth(..) will not run. For example, spaWarning means xwizlent.finishOAuth(..) won't run on url that doesn't contain valid X authorization data. noRepeat means that you cannot make two requests with same X authorization data (like same request token). Check the Stream for explanation of chunkedResponseWarning.
Node.js
⬑
xwiz.continueOAuth(..)
Errors are ones that can happen on request or response streams (lower level) and they are hanled by calling next(..). There are no xwiz errors currently for this function. Not 200OK responses are only piped back to client and are not considered as errors.
xwiz.haste(..)
Errors work same as xwizlent.continueOAuth(..)
verifyCredentials()
Any not 200OK response are considered as an accessTokenNotVerified error. Express' next(..) is called and promise is rejected with the same error.
error.name | error.message
--------- | -------------
accessTokenNotVerified | json string
Note that the error.message will be a json string taken from response payload so you can have exact X error description, error code etc ...
Developers
⬑
xwiz-client (browser)
Before making changes:
npm run start // runs `build` and `watch` scripts
When you're done:
npm run lintBuild:
npm run build
Test:
npm testxwiz-server (node.js)
Make changes.
Lint:
npm run lintTest:
npm run test