Authentication – POST for /api/authenticate using parameters ‘name’ and ‘password’
Here’s we authenticate by giving the server a name and password. If the name and password match the ones stored in the database, then we return it a token for the iOS client to use. Whenever that the iOS client needs to access a protected URL, we use that token.
Server side
/config.js
1 2 3 4 5 |
module.exports = { //secret: used when we create and verify JSON Web Tokens 'secret': 'ilovescotchyscotch', 'database': 'mongodb://localhost/PersonDatabase' }; |
First notice that in the beginning of the file, we use set to have
superSecret name as an application setting name.
/server.js
1 |
app.set('superSecret', config.secret); // secret variable |
We set the key ‘superSecret’ to match config.secret which is ‘ilovescotchyscotch’ in /config.js
Later we will use jwt’s sign function on this string along with the user object in order to create a token for the iOS client to use.
We use express’s router function to get variable apiRoutes. We prefix all routes with ‘/api’.
Then for POST on url /authenticate, we use mongoose’s model User to find if the user exist. If it does, it will then run the async method to see if the passwords match.
If the passwords match, then we use jwt’s sign function to return a token for the user to use. We wrap that token along with other string info in a json object and return it via the response object.
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 |
// API ROUTES ------------------- // get an instance of the router for api routes var apiRoutes = express.Router(); // route to authenticate a user (POST http://localhost:8080/api/authenticate) apiRoutes.post('/authenticate', function(req, res) { console.log('name value: ' + req.body.name); console.log('password value: ' + req.body.password); //User is model object made by mongoose // mongoose's findOne function to find the user User.findOne({ name: req.body.name }, function(err, user) { if (err) throw err; if (!user) { res.json({ success: false, message: 'Authentication failed. User not found.' }); } else if (user) { //user with that name is returned...we just have to check if the pwds match // check if password matches if (user.password != req.body.password) { res.json({ success: false, message: 'Authentication failed. Wrong password.' }); } else { console.log('user found, and password is correct'); // if user is found and password is right // create a token, jsonwebtoken to create the token. var token = jwt.sign(user, app.get('superSecret'), { expiresInMinutes: 1440 // expires in 24 hours }); // return the information including token as JSON res.json({ success: true, message: 'Enjoy your token!', token: token }); } } }); }); ... ... // apply the routes to our application with the prefix /api app.use('/api', apiRoutes); |
iOS client
We create NSDictionary with name and password key. Then we encode it into a NSString.
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 |
#pragma mark - ------------- LifeCycle ------------- -(instancetype)initWithEmail:(NSString*)email andAuthToken:(NSString*)token andID:(NSString*)newID { if(self=[super init]) { self.email = email; self.auth_token = token; //build an info object and convert to json self.info = [NSDictionary dictionaryWithObjectsAndKeys: @"Shirley Qian", @"name", @"12345678", @"password", nil]; NSError *error = nil; //1st step NSData *jsonInputData = [NSJSONSerialization dataWithJSONObject:self.info options:NSJSONWritingPrettyPrinted error:&error]; //2nd step self.jsonInputString = [[NSString alloc] initWithData:jsonInputData encoding:NSUTF8StringEncoding]; } return self; } |
Notice we then call the URL ‘http://192.168.1.102:8080/api/authenticate’ with POST, and the json string in the body of the request.
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 |
-(void)logUserIn:(OnUserLogInCloud)userLogInBlk; { NSLog(@"logUserIn"); NSString * urlStr = [NSString stringWithFormat:@"http://192.168.1.102:8080/api/authenticate"]; NSURL *url = [NSURL URLWithString:urlStr]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request setHTTPMethod:@"POST"]; [request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"]; [request setHTTPBody:[self.jsonInputString dataUsingEncoding:NSUTF8StringEncoding]]; /* create the connection */ self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; if (self.connection) { self.buffer = [NSMutableData data]; /* initialize the buffer */ [self.connection start]; /* start the request */ self.completed = userLogInBlk; } } |
After we POST this request, our server will hit the apiRoutes.post(‘/authenticate’, function(req, res) {…}) function, which uses jwt.sign(user, app.get(‘superSecret’)) to return a token for this particular user.
It returns:
res.json({
success: true,
message: ‘Enjoy your token!’,
token: token
});
Hence the server code, we see that the returned json object has 3 keys:
success, message, and token. Use the value from key token in order to authenticate yourself when doing updates and other restricted activities.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
-(void)connectionDidFinishLoading:(NSURLConnection *)connection { NSError *error = nil; NSDictionary* json = [NSJSONSerialization JSONObjectWithData: self.buffer options: kNilOptions error: &error]; NSString * success = [json objectForKey:@"success"]; NSString * msg = [json objectForKey:@"message"]; self.auth_token = [json objectForKey:@"token"]; if(error) { //if url is wrong //[self showMessageBox:@"error" andMessage:[error localizedDescription] andCancelTitle:@"ok"]; } else { self.completed(self.email, self.auth_token, self.userID); } } |
Using Token to authenticate itself and access protected URLs
Reading list of users using approved authentication
Now that we have the authentication, we need to use it to read protected data
In your server.js, put this code to protect your URLs:
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 |
// route middleware to verify a token apiRoutes.use(function(req, res, next) { console.log('------apiRoutes.use function------------'); //X-ACCESS-TOKEN is the key, we need to give it value of token //so it can authenticate us console.log("req.headers[x-access-token]: " + req.headers['x-access-token']); console.log("req.headers[x-access-name]: " + req.headers['x-access-name']); // check header or url parameters or post parameters for token var token = req.headers['x-access-token']; var name = req.headers['x-access-name']; console.log('apiRoutes.use: ' + name); console.log('apiRoutes.use: ' + token); // decode token if (token) { // verifies secret and checks exp jwt.verify(token, app.get('superSecret'), function(err, decoded) { if (err) { return res.json({ success: false, message: 'Failed to authenticate token.' }); } else { // if everything is good, save to request for use in other routes req.decoded = decoded; req.body.name = name; next(); } }); } else { // if there is no token // return an error return res.status(403).send({ success: false, message: 'No token provided.' }); } }); |
So that when you hit
http://localhost:8080/api/users
You’ll get:
{“success”:false,”message”:”No token provided.”}
So as you can see a client that does not provide the correct token and user name cannot access that what http://localhost:8080/api/users has to offer.
Thus, we access it by using the token received earlier when we authenticated with our user name and password. We set the token and our username into the header of the request packet.
AppDelegate.m
As you can see, once you log in, use the token string to get info.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
LogUserConnection * userLogIn = [[LogUserConnection alloc] initWithEmail:@"Shirley Qian" andAuthToken:nil andID:nil]; [userLogIn logUserIn:^(NSString *email, NSString *auth_token, NSString *userID) { NSLog(@"%@ logged in", email); GetInfoConnection * getInfo = [[GetInfoConnection alloc] initWithEmail:email andAuthToken:auth_token andID:nil]; [getInfo getUserInfo:^(NSString *email, NSString *auth_token, NSString *userID) { NSLog(@"data received"); }]; }]; |
GetInfoConnection.h
1 2 3 4 5 6 7 8 9 |
typedef void (^OnGetInfoCloud) (NSString * email, NSString * auth_token, NSString* userID); @interface GetInfoConnection : NSObject -(instancetype)initWithEmail:(NSString*)email andAuthToken:(NSString*)token andID:(NSString*)newID; -(void)getUserInfo:(OnGetInfoCloud)userInfoBlk; @end |
GetInfoConnection.m
Notice how we set the request’s header with the user name and token value in the method getUserInfo.
These values are then sent to the server where they get processed by apiRoutes.use(function(req, res, next) {…}), which uses jwt.verify to verify if this username and token is valid.
Then the server passes it onto the next middleware layer, namely, apiRoutes.get(‘/users’, function(req, res) {…}) to have the database retrieve the valid user.
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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
#import "GetInfoConnection.h" @interface GetInfoConnection () <NSURLConnectionDataDelegate>{ } @property (nonatomic, strong) NSMutableData *buffer; @property (nonatomic, strong) NSURLConnection *connection; //block that gets retained when posting to WS, then used when the connection is finished @property (nonatomic, copy) OnGetInfoCloud completed; @property (nonatomic, copy) NSString * auth_token; @property (nonatomic, copy) NSString * email; @end @implementation GetInfoConnection #pragma mark - ------------- LifeCycle ------------- -(instancetype)initWithEmail:(NSString*)email andAuthToken:(NSString*)token andID:(NSString*)newID { if(self=[super init]) { //self.trustedHosts = [NSArray arrayWithObjects:@"cloudatcost.mesotech.ca", nil]; self.email = email; self.auth_token = token; } return self; } -(void)getUserInfo:(OnGetInfoCloud)userInfoBlk { NSString * urlStr = [NSString stringWithFormat:@"http://192.168.1.102:8080/api/users"]; NSURL *url = [NSURL URLWithString:urlStr]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request addValue:self.email forHTTPHeaderField:@"x-access-name"]; [request addValue:self.auth_token forHTTPHeaderField:@"x-access-token"]; [request setHTTPMethod:@"GET"]; [request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"]; /* create the connection */ self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; if (self.connection) { self.buffer = [NSMutableData data]; /* initialize the buffer */ [self.connection start]; /* start the request */ self.completed = userInfoBlk; } } #pragma mark - ------------- NSURLConnectionDataDelegate ------------- - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { NSLog(@"did fail with error %@", [error debugDescription]); self.completed(nil, nil, nil); } -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { NSLog(@"didReceiveResponse - %@, %@", [self debugDescription], [response debugDescription]); [self.buffer setLength:0]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { NSLog(@"%@ - didReceiveData - length of data is: %lu", [self debugDescription], (unsigned long)[data length]); [self.buffer appendData:data]; } -(void)connectionDidFinishLoading:(NSURLConnection *)connection { NSError *error = nil; NSDictionary* json = [NSJSONSerialization JSONObjectWithData: self.buffer options: kNilOptions error: &error]; if(error) { } else { self.completed(self.email, self.auth_token, self.userID); } } @end |
Full server code
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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
// ======================= // get the packages we need ============ // ======================= var express = require('express'); var app = express(); var bodyParser = require('body-parser'); var morgan = require('morgan'); var mongoose = require('mongoose'); var jwt = require('jsonwebtoken'); // used to create, sign, and verify tokens var config = require('./config'); // get our config file var User = require('./models/user'); // get our mongoose model // ======================= // configuration ========= // ======================= var port = 8080; // used to create, sign, and verify tokens mongoose.connect(config.database); // connect to database var db = mongoose.connection; db.on('error', console.error.bind(console, 'connection error:')); db.once('open', function (callback) { // yay! console.log('mongoose db connected'); }); //app.set(name, value) //Assigns setting name to application setting : value, //where name is one of the properties from the app.set('superSecret', config.secret); // secret variable // use body parser so we can get info from POST and/or URL parameters app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); // use morgan to log requests to the console app.use(morgan('dev')); // API ROUTES ------------------- // get an instance of the router for api routes var apiRoutes = express.Router(); // route to authenticate a user (POST http://localhost:8080/api/authenticate) apiRoutes.post('/authenticate', function(req, res) { console.log('name value: ' + req.body.name); console.log('password value: ' + req.body.password); //User is model object made by mongoose // mongoose's findOne function to find the user User.findOne({ name: req.body.name }, function(err, user) { if (err) throw err; if (!user) { res.json({ success: false, message: 'Authentication failed. User not found.' }); } else if (user) { //user with that name is returned...we just have to check if the pwds match // check if password matches if (user.password != req.body.password) { res.json({ success: false, message: 'Authentication failed. Wrong password.' }); } else { console.log('user found, and password is correct'); // if user is found and password is right // create a token, jsonwebtoken to create the token. var token = jwt.sign(user, app.get('superSecret'), { expiresInMinutes: 1440 // expires in 24 hours }); // return the information including token as JSON res.json({ success: true, message: 'Enjoy your token!', token: token }); } } }); }); // route middleware to verify a token apiRoutes.use(function(req, res, next) { console.log('------apiRoutes.use function------------'); //X-ACCESS-TOKEN is the key, we need to give it value of token //so it can authenticate us console.log("req.body.token: " + req.body.token); console.log("req.query.token: " + req.query.token); console.log("req.headers[x-access-token]: " + req.headers['x-access-token']); // check header or url parameters or post parameters for token var token = req.body.token || req.query.token || req.headers['x-access-token']; // decode token if (token) { // verifies secret and checks exp jwt.verify(token, app.get('superSecret'), function(err, decoded) { if (err) { return res.json({ success: false, message: 'Failed to authenticate token.' }); } else { // if everything is good, save to request for use in other routes req.decoded = decoded; next(); } }); } else { // if there is no token // return an error return res.status(403).send({ success: false, message: 'No token provided.' }); } }); // route to return all users (GET http://localhost:8080/api/users) apiRoutes.get('/users', function(req, res) { console.log('(GET http://localhost:8080/api/users)'); User.find({}, function(err, users) { res.json(users); }); }); // apply the routes to our application with the prefix /api app.use('/api', apiRoutes); // http://localhost:8080/setup //app.get('/setup', function(req, res) { app.post('/setup', function(req, res) { console.log('POST parameter received, name is: ' + req.body.name); // create a sample user var nick = new User({ name: req.body.name, password: req.body.password, //You would protect your passwords by hashing it. admin: true }); // save the sample user nick.save(function(err) { if (err) throw err; console.log('User ' + req.body.name + ' saved successfully...!'); res.json({ success: true }); }); }); // route to show a random message (GET http://localhost:8080/api/) app.get('/', function(req, res) { res.json({ message: '(GET http://localhost:8080/)' }); }); // ======================= // start the server ====== // ======================= app.listen(port); console.log('Magic happens at http://localhost:' + port); |