Created
November 16, 2012 18:10
-
-
Save pmhsfelix/4089529 to your computer and use it in GitHub Desktop.
OAuth 2 Client demo using Node.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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