Skip to content

Commit 4bc4c9b

Browse files
author
aalonsog
committed
first commit
0 parents  commit 4bc4c9b

File tree

6 files changed

+299
-0
lines changed

6 files changed

+299
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/node_modules

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
oauth2-example-client
2+
===================
3+
4+
Oauth2 authentication example for FI-WARE GE applications
5+
6+
- Software requirements:
7+
8+
+ nodejs
9+
+ npm
10+
11+
- Install the dependencies:
12+
13+
npm install
14+
15+
- Configure oaut2 credentials in config.js file (you will find theme in your IDM account)
16+
17+
- Start example server
18+
19+
sudo node server
20+
21+
- Connect to http://localhost to try the example
22+
23+
* Connect to http://localhost/logout to delete session cookies once you have logout in IDM portal
24+

config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
var config = {}
2+
3+
config.idmURL = 'https://account.lab.fi-ware.eu';
4+
config.client_id = '';
5+
config.client_secret = '';
6+
config.callbackURL = 'http://localhost/login';
7+
8+
module.exports = config;

oauth2.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
var querystring= require('querystring'),
2+
https= require('https'),
3+
http= require('http'),
4+
URL= require('url');
5+
6+
exports.OAuth2= function(clientId, clientSecret, baseSite, authorizePath, accessTokenPath, callbackURL, customHeaders) {
7+
this._clientId= clientId;
8+
this._clientSecret= clientSecret;
9+
this._baseSite= baseSite;
10+
this._authorizeUrl= authorizePath || "/oauth/authorize";
11+
this._accessTokenUrl= accessTokenPath || "/oauth/access_token";
12+
this._callbackURL = callbackURL;
13+
this._accessTokenName= "access_token";
14+
this._authMethod= "Basic";
15+
this._customHeaders = customHeaders || {};
16+
}
17+
18+
// This 'hack' method is required for sites that don't use
19+
// 'access_token' as the name of the access token (for requests).
20+
// ( http://tools.ietf.org/html/draft-ietf-oauth-v2-16#section-7 )
21+
// it isn't clear what the correct value should be atm, so allowing
22+
// for specific (temporary?) override for now.
23+
exports.OAuth2.prototype.setAccessTokenName= function ( name ) {
24+
this._accessTokenName= name;
25+
}
26+
27+
exports.OAuth2.prototype._getAccessTokenUrl= function() {
28+
return this._baseSite + this._accessTokenUrl;
29+
}
30+
31+
// Build the authorization header. In particular, build the part after the colon.
32+
// e.g. Authorization: Bearer <token> # Build "Bearer <token>"
33+
exports.OAuth2.prototype.buildAuthHeader= function() {
34+
var key = this._clientId + ':' + this._clientSecret;
35+
var base64 = (new Buffer(key)).toString('base64');
36+
return this._authMethod + ' ' + base64;
37+
};
38+
39+
exports.OAuth2.prototype._request= function(method, url, headers, post_body, access_token, callback) {
40+
41+
var http_library= https;
42+
var parsedUrl= URL.parse( url, true );
43+
if( parsedUrl.protocol == "https:" && !parsedUrl.port ) {
44+
parsedUrl.port= 443;
45+
}
46+
47+
// As this is OAUth2, we *assume* https unless told explicitly otherwise.
48+
if( parsedUrl.protocol != "https:" ) {
49+
http_library= http;
50+
}
51+
52+
var realHeaders= {};
53+
for( var key in this._customHeaders ) {
54+
realHeaders[key]= this._customHeaders[key];
55+
}
56+
if( headers ) {
57+
for(var key in headers) {
58+
realHeaders[key] = headers[key];
59+
}
60+
}
61+
realHeaders['Host']= parsedUrl.host;
62+
63+
//realHeaders['Content-Length']= post_body ? Buffer.byteLength(post_body) : 0;
64+
if( access_token && !('Authorization' in realHeaders)) {
65+
if( ! parsedUrl.query ) parsedUrl.query= {};
66+
parsedUrl.query[this._accessTokenName]= access_token;
67+
}
68+
69+
var queryStr= querystring.stringify(parsedUrl.query);
70+
if( queryStr ) queryStr= "?" + queryStr;
71+
var options = {
72+
host:parsedUrl.hostname,
73+
port: parsedUrl.port,
74+
path: parsedUrl.pathname + queryStr,
75+
method: method,
76+
headers: realHeaders
77+
};
78+
79+
this._executeRequest( http_library, options, post_body, callback );
80+
}
81+
82+
exports.OAuth2.prototype._executeRequest= function( http_library, options, post_body, callback ) {
83+
// Some hosts *cough* google appear to close the connection early / send no content-length header
84+
// allow this behaviour.
85+
var allowEarlyClose= options.host && options.host.match(".*google(apis)?.com$");
86+
var callbackCalled= false;
87+
function passBackControl( response, result ) {
88+
if(!callbackCalled) {
89+
callbackCalled=true;
90+
if( response.statusCode != 200 && (response.statusCode != 301) && (response.statusCode != 302) ) {
91+
callback({ statusCode: response.statusCode, data: result });
92+
} else {
93+
callback(null, result, response);
94+
}
95+
}
96+
}
97+
98+
var result= "";
99+
100+
var request = http_library.request(options, function (response) {
101+
response.on("data", function (chunk) {
102+
result+= chunk
103+
});
104+
response.on("close", function (err) {
105+
if( allowEarlyClose ) {
106+
passBackControl( response, result );
107+
}
108+
});
109+
response.addListener("end", function () {
110+
passBackControl( response, result );
111+
});
112+
});
113+
request.on('error', function(e) {
114+
callbackCalled= true;
115+
callback(e);
116+
});
117+
118+
if(options.method == 'POST' && post_body) {
119+
request.write(post_body);
120+
}
121+
request.end();
122+
}
123+
124+
exports.OAuth2.prototype.getAuthorizeUrl= function() {
125+
return this._baseSite + this._authorizeUrl + '?response_type=code&client_id=' + this._clientId + '&state=xyz&redirect_uri=' + this._callbackURL;
126+
}
127+
128+
exports.OAuth2.prototype.getOAuthAccessToken= function(code, callback) {
129+
130+
var post_data = 'grant_type=authorization_code&code=' + code + '&redirect_uri=' + this._callbackURL;
131+
132+
var post_headers= {
133+
'Authorization': this.buildAuthHeader(),
134+
'Content-Type': 'application/x-www-form-urlencoded'
135+
};
136+
137+
this._request("POST", this._getAccessTokenUrl(), post_headers, post_data, null, function(error, data, response) {
138+
if( error ) callback(error);
139+
else {
140+
var results;
141+
try {
142+
// As of http://tools.ietf.org/html/draft-ietf-oauth-v2-07
143+
// responses should be in JSON
144+
results= JSON.parse(data);
145+
}
146+
catch(e) {
147+
// .... However both Facebook + Github currently use rev05 of the spec
148+
// and neither seem to specify a content-type correctly in their response headers :(
149+
// clients of these services will suffer a *minor* performance cost of the exception
150+
// being thrown
151+
results= querystring.parse(data);
152+
}
153+
callback(null, results);
154+
}
155+
});
156+
}
157+
158+
exports.OAuth2.prototype.get= function(url, access_token, callback) {
159+
this._request("GET", url, {}, "", access_token, callback);
160+
}

package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "oauth2-example-client",
3+
"version": "0.1.0",
4+
"description": "Oauth2 authentication example for FI-WARE GE applications",
5+
"author": "GING DIT UPM",
6+
"dependencies": {
7+
"express": "3.x"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/ging/oauth2-example-client"
12+
},
13+
"contributors": [
14+
{
15+
"name": "Alvaro Alonso",
16+
"email": "aalonsog@dit.upm.es"
17+
},
18+
{
19+
"name": "Javier Cerviño",
20+
"email": "jcervino@dit.upm.es"
21+
}
22+
]
23+
}

server.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
var express = require('express');
2+
var OAuth2 = require('./oauth2').OAuth2;
3+
var config = require('./config');
4+
5+
6+
// Express configuration
7+
var app = express();
8+
app.use(express.logger());
9+
app.use(express.bodyParser());
10+
app.use(express.cookieParser());
11+
app.use(express.session({
12+
secret: "skjghskdjfhbqigohqdiouk"
13+
}));
14+
15+
app.configure(function () {
16+
"use strict";
17+
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
18+
//app.use(express.logger());
19+
app.use(express.static(__dirname + '/public'));
20+
});
21+
22+
23+
// Config data from config.js file
24+
var client_id = config.client_id;
25+
var client_secret = config.client_secret;
26+
var idmURL = config.idmURL;
27+
var callbackURL = config.callbackURL;
28+
29+
// Creates oauth library object with the config data
30+
var oa = new OAuth2(client_id,
31+
client_secret,
32+
idmURL,
33+
'/oauth2/authorize',
34+
'/oauth2/token',
35+
callbackURL);
36+
37+
// Handles requests to the main page
38+
app.get('/', function(req, res){
39+
40+
// If auth_token is not stored in a session cookie it redirects to IDM authentication portal
41+
if(!req.session.oauth_token) {
42+
43+
var path = oa.getAuthorizeUrl();
44+
res.redirect(path);
45+
46+
// If auth_token is stored in a session cookie goes to the main page and prints some user data (getting it also from the session cookie)
47+
} else {
48+
49+
var user = JSON.parse(req.session.user);
50+
res.send("Wellcome " + user.displayName + "<br> Your email address is " + user.email);
51+
}
52+
});
53+
54+
// Handles requests from IDM with the access code
55+
app.get('/login', function(req, res){
56+
57+
// Using the access code goes again to the IDM to obtain the access_token
58+
oa.getOAuthAccessToken(req.query.code, function (e, results){
59+
60+
// Stores the access_token in a session cookie
61+
req.session.oauth_token = results.access_token;
62+
63+
var url = config.idmURL + '/user/';
64+
65+
// Using the access token asks the IDM for the user info
66+
oa.get(url, results.access_token, function (e, response) {
67+
68+
// Stores the user info in a session cookie and redirects to the main page
69+
req.session.user = response;
70+
res.redirect('/');
71+
});
72+
});
73+
});
74+
75+
// Handles logout requests to remove access_token from the session cookie
76+
app.get('/logout', function(req, res){
77+
78+
req.session.oauth_token = undefined;
79+
res.redirect('/');
80+
});
81+
82+
console.log('Server listen in port 80. Connect to localhost');
83+
app.listen(80);

0 commit comments

Comments
 (0)