Skip to content

Instantly share code, notes, and snippets.

@pmhsfelix
Created November 16, 2012 18:10
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pmhsfelix/4089529 to your computer and use it in GitHub Desktop.
Save pmhsfelix/4089529 to your computer and use it in GitHub Desktop.
OAuth 2 Client demo using Node.js
(function(){
var querystring = require('querystring');
var url = require('url');
var request = require('request');
var http = require('http');
var util = require('util');
var child_process = require('child_process');
var config = {
client : {
client_id : null,
client_secret : null,
redirect_uri : "http://localhost:8080/callback"
},
authzServer : {
authorizationEndpoint : "https://github.com/login/oauth/authorize",
tokenEndpoint : "https://github.com/login/oauth/access_token",
},
exampleResource : {
uri : "https://api.github.com/user",
scope : "user repo"
}
};
// Just a mock DB to hold the authorization request state
var Db = {}
console.info("Hi, welcome to the OAuth 2.0 Authorization Code Grant Demo");
if (!config.client.client_id || !config.client.client_secret){
console.warn("The client_id or the client_secret are not configured."
+ " Please register a client application and set these properties on the config object");
process.exit(1);
}
console.info("Creating a self-hosted server to handle the authorization callback...");
var server = http.createServer(handleCallback);
server.listen(8080, function(){
console.info("Done. Server is listening, the OAuth 2.0 dance can now start");
console.info("First, lets do an authorization request via the User's browser (a new browser tab will be opened)");
var authzRequest = {
client_id : config.client.client_id,
response_type : "code",
scope : config.exampleResource.scope,
redirect_uri : config.client.redirect_uri,
state : require('crypto').randomBytes(16).toString('base64')
}
Db.state = authzRequest.state;
var uri = config.authzServer.authorizationEndpoint + '?' + querystring.stringify(authzRequest);
console.info("Redirecting browser to %s ...", uri);
// HACK only for windows
child_process.exec('start ' + '"title" ' + '"' + uri + '"', function(err){
if(err !== null){
console.warn("Unable to open browser tab");
process.exit(1);
}
console.info("And the dance has begun. Please look at the new browser tab");
console.info("The next time we met it will be on the callback handler\n");
})
});
function handleCallback(req, res){
if(url.parse(req.url).pathname != url.parse(config.client.redirect_uri).pathname){
res.writeHead(404);
res.end();
return;
}
var fatal = function(msg,args){
var s = util.format(msg,args);
console.warn(s);
res.writeHead(403,{"content-type":"text/plain"});
res.end(s);
process.nextTick(function(){process.exit(1)});
};
console.info("Great, just received the response from the Authorization Endpoint, lets look at it...");
var authzResponse = querystring.parse(url.parse(req.url).query);
if(authzResponse.error){
return fatal("Unfortunately the request was not sucessfull. The returned error is %s, ending", authzResponse.error);
}
if(!authzResponse.code || !authzResponse.state){
return fatal("Missing the 'code' and 'state' fields, ending");
}
console.info("Good, the request was sucessfull. Lets see if the returned state matches the sent state...");
/* HACK - double encoding problem on a certain AS */
if(decodeURIComponent(authzResponse.state) != Db.state){
return fatal("Hum, the returned state does not match the send state. Ignoring the response, sorry.");
}
console.info("Nice, the state matches. Lets now exchange the code for an access token using the token endpoint ...");
var tokenRequest = {
code : authzResponse.code,
grant_type : "authorization_code",
redirect_uri : config.client.redirect_uri,
client_id : config.client.client_id,
client_secret : config.client.client_secret
}
var requestUri = config.authzServer.tokenEndpoint;
request(
{
uri : requestUri,
method : 'POST',
headers : {
'Accept' : 'application/json'
},
form : tokenRequest
},
function (error, response, body){
if(error){
return fatal("Error while exchaging code for a token, ending");
}
console.info(response.request.uri);
console.info("The token endpoint responded with status %s",response.statusCode)
if(response.statusCode !== 200){
return fatal("Not a 200 response, ending.");
}
if(response.headers['content-type'].indexOf('application/json') === -1){
return fatal("Expecting 'application/json' from the token endpoint, however it returned '%s', ending.",
response.headers['content-type']);
}
var tokenResp = JSON.parse(body);
console.info(tokenResp.token_type);
if(!tokenResp.token_type || tokenResp.token_type.toLowerCase() != "bearer"){
return fatal("Unfortunately, the returned token type is missing or unknown, ending",tokenResp.token_type);
}
var theAccessToken = tokenResp.access_token;
console.info("Great, we have an access token %s. Lets use it to GET the resource representation",theAccessToken);
request.get(
{
uri: config.exampleResource.uri,
method: 'GET',
headers : {
'Authorization' : 'Bearer ' + theAccessToken
}
},
function(error,response,body){
if(error){
return fatal("Error while GETing resource representation, ending");
}
res.writeHead(response.statusCode, response.headers);
res.end(body);
console.info("Returning the resource server response, I hope you liked this demo ...");
process.nextTick(function(){process.exit(1)})
});
});
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment