Oauth2 is an authentication method where you allow clients to access resources in a server by authenticating in a different server. I am building a system where I will need this infrastructure so I will do my best to explain how to build and use an Oauth2 server.
The components
- Resource owner: This is a person. Lets call him Adrian
- Resource server: This is a server where Adrian’s information lives (along with other people’s information). The resource server needs to show Adrian only his information. We’ll call this app server
- Client: This can be a browser or an app that Adrian uses to interact with the app server. This is the browser
- Authorization server: This is our Oauth server. It validates user credentials and assigns tokens among other things. We’ll call this one oauth server
The interactions
This diagram shows at a high level how all the parts of an Oauth2 system interact:
When I drew that diagram things kind of made sense but the truth is that there are a few implementation details that make this a little harder than I first thought.
The implementation
Trying to access something without a token
In the diagram above I show an example of Adrian trying to access a resource in the app server without an access token. To test this in real life lets have our user try to access http://localhost:8080/picture5.jpg and have our server respond with an error page to the user.
App server
We will need to create a server that listens to port 8080, checks for a cookie with the access token and responds with an error if the token is not found.
To create a quick server with node and express run these commands from a terminal:
1
2
3
4
mkdir ~/server
cd ~/server
npm install -g generator-express-server
yo express-server
You can run your server by running:
1
node app/app.js
Now, lets customize it so it gives an error if the access token is not found. We need to modify app/controllers/main.js so it looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use strict';
var app = require('../app');
function main(req, res) {
if (!req.cookies.accessToken) {
res.send(
'You need to log-in to access this resource.\n' +
'<a href="http://localhost:9090/">Log in</a>'
);
}
}
// Routes
app.get('*', main);
We changed the matched route to *
so for now it will match any route under http://localhost:8080. We also added a check for the accessToken cookie, if it is not there then we send a message with a log-in link. Notice that the log in link goes to a different server(http://localhost:9090), this is going to be our Oauth server.
Serving the log-in page
Currently clicking on the log-in link sends the user to a non-existing page. Lets change this so it actually serves a log-in page from our Oath server.
We will need another server. This time when you are prompted for the port number choose 9090:
1
2
3
mkdir ~/oauth
cd ~/oauth
yo express-server
First lets modify app/controllers/main.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
'use strict';
var app = require('../app');
exports.loginPage = function(req, res) {
var response =
'<form method="post">' +
(req.query.failed ? '<p>Login error</p>' : '') +
'<label>user</label>' +
'<input type="text" name="user" />' +
'<label>password</label>' +
'<input type="password" name="password" />' +
'<input type="hidden" value="' + req.query.clientId + '" name="client_id" />' +
'<input type="hidden" value="' + req.query.redirectUri + '" name="redirect_uri" />' +
'<input type="submit" value="Submit">' +
'</form>';
res.send(response);
};
exports.login = function(req, res) {
if (req.body.user === 'user' && req.body.password === 'password') {
// Generate access token and redirect to redirectUri
} else {
req.query.failed = true;
exports.loginPage(req, res);
}
};
app.get('/', exports.loginPage);
app.post('/', exports.login);
Now, since the log-in page needs a clientId and redirectUri we need to change our log-in link in the app server. The new one should be something like this:
1
http://localhost:9090/?clientId=1234&redirectUri=http%3A%2F%2Flocalhost%3A8080%2Fpicture5.jpg
This includes a clientId of 1234 and a redirectUri of http://localhost:8080/picture5.jpg.
If you look at the code we added for the OAuth main controller you will notice that I hard coded a user name and password. In the real world you will want this to be an actual query to a database.
Granting the access token
If the user logs in with valid credentials we want to give him an auth code and then an access token. We are going to make some changes to oauth server’s main controller so it returns an auth code and access token:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
'use strict';
var app = require('../app');
var querystring = require('querystring');
var url = require('url');
/**
* Shows the login page
*/
exports.loginPage = function(req, res) {
var response =
'<form method="post">' +
(req.query.failed ? '<p>Login error</p>' : '') +
'<label>user</label>' +
'<input type="text" name="user" />' +
'<label>password</label>' +
'<input type="password" name="password" />' +
'<input type="hidden" value="' + req.query.clientId + '" name="client_id" />' +
'<input type="hidden" value="' + req.query.redirectUri + '" name="redirect_uri" />' +
'<input type="submit" value="Submit">' +
'</form>';
res.send(response);
};
/**
* If login is successful, then show a screen asking user to confirm that they
* want to give access to their information to that application
*/
exports.login = function(req, res) {
if (req.body.user === 'user' && req.body.password === 'password') {
// This auth code should be generated by some algorithm. This is just for
// demonstration
var authCode = '1234567890';
// Show form asking user to confirm access to that client
var response =
'<form method="get" action="/accessToken">' +
'Grant access?' +
'<input type="hidden" value="' + req.body['client_id'] + '" name="client_id" />' +
'<input type="hidden" value="' + req.body['redirect_uri'] + '" name="redirect_uri" />' +
'<input type="hidden" value="' + authCode + '" name="auth_code" />' +
'<input type="submit" value="Yes">' +
'</form>';
res.send(response);
} else {
req.query.failed = true;
exports.loginPage(req, res);
}
};
/**
* If the clientId and authCode match, an accessToken is returned via the
* redirect uri
*/
exports.accessToken = function(req, res) {
// Verify clientId and authCode match our records
if (req.query['client_id'] === '1234' && req.query['auth_code'] === '1234567890') {
// Append authCode to redirectUrl
var parsedUrl = url.parse(req.query.redirect_uri);
var search = querystring.parse(parsedUrl.query);
search.accessToken = 'ABCD';
parsedUrl.search = querystring.stringify(search);
res.redirect(url.format(parsedUrl));
}
};
/**
* Validates a token. Returns user id if token is valid.
*/
exports.validateToken = function(req, res) {
if (req.query.accessToken === 'ABCD') {
res.json({user: 1});
} else {
res.json({});
}
}
app.get('/', exports.loginPage);
app.post('/', exports.login);
app.get('/accessToken', exports.accessToken);
app.get('/validateToken', exports.validateToken);
We check the user and password and if they match we redirect to the given redirect URL with an auth code. We also created an end point that will translate an auth code into an access token.
Making a request with an access token
Now that we have an access token, we just need to include it in our request. These are the changes needed on app server’s main controller:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
'use strict';
var app = require('../app');
var http = require('http');
/**
* Validates a token against oauth server
*/
function isTokenValid(token, callback) {
// Verify if token is valid with the Oauth server
var options = {
host: 'localhost',
port: 9090,
path: '/validateToken?accessToken=' + token
};
var req = http.request(options, function(response) {
var res = '';
response.on('data', function (chunk) {
res += chunk;
});
response.on('end', function () {
var jsonRes = JSON.parse(res);
// If there is a user id, then the token is valid
if (jsonRes.user) {
callback(null, jsonRes.userId)
} else {
callback('error');
}
});
});
req.end();
}
function main(req, res) {
// If there is an access token, we need to verify it is valid
if (req.query.accessToken) {
isTokenValid(req.query.accessToken, function(err, id) {
if (err) {
return res.send('Token not valid');
} else {
return res.send('Here is your picture: :)');
}
});
} else {
res.send(
'You need to log-in to access this resource.\n' +
'<a href="http://localhost:9090/?clientId=1234&redirectUri=http%3A%2F%2Flocalhost%3A8080%2Fpicture5.jpg">Log in</a>'
);
}
}
// Routes
app.get('*', main);
And that is all.
This is of course a very naive example that I’m just using to demonstrate the happy path of a very simple OAuth2 system. In real life a database, error handling and other security implications make this significantly more complicated.
algorithms
design_patterns
]