npm package: @gamesolutionslab/server_authentication
Description: The aim of this package is to provide an easy way to authenticate users on node.js servers.     Authentication for browser to server can happen via `Google` and `JWT`.     Authentication for application to server can happen via `JWT`.
Latest version: 1.0.7

# About this package
The aim of this package is to provide an easy way to authenticate users on node.js servers.    
Authentication for browser to server can happen via `Google` and `JWT`.    
Authentication for application to server can happen via `JWT`.

# Installation
Install via `npm i @gamesolutionslab/server_authentication --save`.  

# Dependancies
* A MongoDB for saving user data

# Usage
## Install needed packages
For both Google and JWT you need the following packages:
* dotenv
* express
* express-session
* mongoose

For Google you need the following extra package:
* connect-ensure-login

Install via the usual way:   
`npm i [packagename] --save`

Require the packages (exept `connect-ensure-login`) in your main server file (usually `server.js`):    
```javascript
require('dotenv').config();
var express = require('express');
var expressSession = require('express-session');
var mongoose = require('mongoose');
```

## Create .env file
Create a new file called `.env` if your project doen't have one already.    
A `.env` file is a file where you usually keep secrets and passwords. Therefore it is important to **NEVER** commit this file to source control. 

Add the following key and replace its value:

```
EXPRESS_SESSION_SECRET=THIS IS USED FOR EXPRESS SESSION, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING
```
For JWT, add the following key and replace its value:
```
JWT_SECRET=THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING
```
For Google, follow the following steps:
* Go to the [google developer console](https://console.developers.google.com) 
* Create a new project
* Go to `credentials`
* Select `create credentials > OAuth client ID`
* Select web application
* Enter a name
* In `Authorised JavaScript origins` enter your server url (for example: http://localhost:8080)
* In `Authorised redirect URIs` enter your callback url (for example: http://localhost:8080/google/return)
* Save
* Select your credential in the `OAuth 2.0 client IDs` list    

At the top of the page you will see your Client ID and Client Secret. Add the following keys to your `.env` file and paste the Client ID and Client Secret behind the Client ID and Client Secret Key and add the Callback URL you entered behind the Callback URL key:
```
GOOGLE_CLIENT_ID=[Client ID]
GOOGLE_CLIENT_SECRET=[Client Secret]
GOOGLE_CALLBACK_URL=[Callback url, in example: /google/return]
```
## Add and configure middleware
Configure the `express-session` middleware in your main server file (usually `server.js`). Please refer to the [express-session npm page](https://www.npmjs.com/package/express-session) for all available options and explanations.
```javascript
app.use(expressSession({
    secret: process.env.EXPRESS_SESSION_SECRET,
    resave: true,
    saveUninitialized: true,
    cookie: {
        maxAge: 1000 * 60 * 60 * 24
    }
}));
```

For JWT, import and use the `jwt_error_handler` middleware as well:
```javascript
var errorHandler = require('@gamesolutionslab/server_authentication').jwt_error_handler;
app.use(errorHandler);
```
## Create a user
A user needs to be saved in a database.    
If you don't have one already, create a `MongoDB`. 
Use the following code in your main server file (usually `server.js`) to use `mongoose` to connect to your `MongoDB` if you don't have a connection already: 
```javascript
//Replace [MongoDB connection string] with your own
var db = mongoose.connection;
mongoose.connect([MongoDB connection string]);
db.on('error', console.error.bind(console, 'connection error: '));
db.once('open', () => {
    console.log("Db connection opened: " + [MongoDB connection string]);
});
```

The first user needs to be created by hand, or before you secure your routes.    
A user has the following fields:     

| Field         | Type      | For Google    | For JWT       | Default       | Explanation |
| ------------- | --------- | ------------- | ------------- | ------------- | ----------- |
| **password**  | String    | *optional*    | required      | ""            | password for login in and getting JWT token |
| **name**      | String    | required      | required      | ""            | (user)name of the user |
| **email**     | String    | required      | *optional*    | ""            | email of the user |
| **hd**        | String    | *optional*    | *optional*    | ""            | the part of the email after the @ sign (for example gmail.com) |
| **role**      | String    | required      | required      | User          | role of the user, see *Secure your routes* |
| **active**    | Boolean   | *optional*    | *optional*    | true          | the active state of the user account, see *Create log in* |

To create a user via code:
```javascript
var usermodel = require('@gamesolutionslab/server_authentication').user_model;

function CreateUser()
{
    new usermodel({
        password: "password",
        name: "name",
        email: "email",
        hd: "hd",
        role: "role",
        active: true
    }).save((error, result) => {
        if (error) {
            console.log("Error: " + error);
        }
        else {
            console.log("Succes: " + result);
        }
    });
}
```

## Create log in
Now that we have a user, we have to create a way for that user to log in.    
The flow for JWT and Google is the same: 
* Get the user credentials
* Check the user credentials in the database
* When in the database and correct, return JWT token (JWT) or save user in session (Google)
* When not in the database or incorrect, throw an error

For JWT you will need to send the user credentials in the body of the log in request. Below an example on how to log in with a simple HTML page:
```HTML
<!-- Simple HTML page with username and password input fields and a button to log in-->
<!DOCTYPE html>
<html>
<body>
    <form action="#">
        <input type="text" id="txt_username">
        <input type="text" id="txt_password">
        <button type="button" id="btn_login">
            Log in
        </button>
    </form>
</body>
</html>
```
```javascript
//Simple JS script to send an AJAX request when the log in button has been pressed
$(document).ready(function () {
    $('#btn_login').click(function () {
        username = $('#txt_username').val();
        password = $('#txt_password').val();

        var aData = {};
        aData.username = username;
        aData.password = password;

        $.ajax({
            method: 'POST',
            url: '/jwt/authenticate',
            data: aData,
            error: function (error) {
                console.log("error");
            }
        }).done(function (token) {
            console.log(token);
        });
    });
}
```
The above script calls the route `/jwt/authenticate`, the code snippet below is an example on how to create this route:
```javascript
var express = require('express');
var router = express.Router();
var userService = require('@gamesolutionslab/server_authentication').jwt_user_service;

router.post('/authenticate', authenticate);

module.exports = router;

function authenticate(req, res, next) {
    userService.authenticate(req.body)
        .then((token) => {
            if (token)
            {
                //OPTIONAL: Check if the logged in user has an active account
                if (req.user.active) {
                    //The account is active, redirect to desired page or do other actions here
                    res.redirect('/');
                }
                else {
                    //Account is inactive
                    res.redirect('/error');
                }

                res.json(token)
            }
            else{
                res.status(400).json({ message: 'Username or password is incorrect' })
            }
        })
        .catch(err => next(err));
}
```
This `userService.authenticate` method checks if the username and password combination is correct and if it is correct, it generates a JWT token.    
To be able to use this route, the above exported router needs to be imported into your main server file (usually `server.js`):
```javascript
var express = require('express');
var app = express();

//Replace [Script with router] with the name of your script with the exported router.
//Due to using the path '/jwt', all routes from the router will be prefixed with '/jwt'.
//In this example you get '/jwt/authenticate' as the full route path.
app.use('/jwt', require('./[Script with router]'));
```
It is up to you on how to proceed further with the token the user gets returned. In case of a HTML page, you can save it in *Localstorage* (**WARNING**: This is not a secure way to save it) or somewhere else to your liking. In case of a application you can simply save it in a variable or write it to a file. Keep in mind that the returned token has te be kept secret and thus needs to be stored away securely.


Google comes with its own webpage for logging in, so there's no need to make one yourself. The code below shows how to show this webpage and handle the callback it gives:
```javascript
var express = require('express');
var router = express.Router();

var passport = require('@gamesolutionslab/server_authentication').google_passport;

router.use(passport.initialize());
router.use(passport.session());

router.get('/login', google_auth);
router.get(process.env.GOOGLE_CALLBACK_URL, passport.authenticate('google', { failureRedirect: '/error' }), callback);
router.get('/error', loginError);

module.exports = router;

function google_auth(req, res, next) {
    //Shows the Google webpage for logging in
    passport.authenticate('google', function (err, user, info) {
        if (err) {
            console.log(err);
            return res.status(401).json(err);
        }
        if (user) {
            console.log("user: " + JSON.stringify(user));
        }
        else {
            res.status(401).json(info);
        }
    })(req, res)
}

//The callback is called when a user has succesfully logged in
function callback(req, res, next) {
    //OPTIONAL: Check if the logged in user has an active account
    if (req.user.active) {
        //The account is active, redirect to desired page or do other actions here
        res.redirect('/');
    }
    else {
        //Account is inactive
        res.redirect('/google/error');
    }
}

function loginError(req, res, next) {
    //Handle log in errors
    res.redirect('/google/error');
}
```
To be able to use these routes, the above exported router needs to be imported into your main server file (usually server.js):
```javascript
var express = require('express');
var app = express();

//Replace [Script with router] with the name of your script with the exported router.
//Due to using the path '/google', all routes from the router will be prefixed with '/google'.
//In this example you get '/google/login', '/google/return' and '/google/error' as the full route paths.
app.use('/google', require('./[Script with router]'));
```

## Create log out
In most cases, you want your users to be able to log out after logging in. To do this, you need to create a logout route.    
When using JWT, you simply have to stop sending the JWT token to have a logged out user. If you save the token somewhere, it is a good practice to delete it now.    
For Google you simply need to create a new route in the router created in *Create log in*:
```javascript
router.get('/logout', logout);

function logout(req, res, next) {
    req.logout();
    //Redirect or do other actions after logout
}
```

## Secure your routes
Now that your users are able to log in and out, it is time to secure your routes. There are two ways to secure your routes:
* Only logged in users have access
* Only logged in users with the correct roles have access

For both ways you will need to pass along the JWT token of the user to the secured route when using JWT.

To secure a route with JWT without a role you will need to use the `authorize` middleware in your routes and tell it you are using JWT:
```javascript
var authorize = require('@gamesolutionslab/server_authentication').authorize;

router.get('/data', authorize("JWT"), exportData);

function exportData(req, res, next) {
    res.status(200).send("data send: secured data");
}
```

When using Google, you wil need to use the `connect-ensure-login` package in your routes.
```javascript
//Ensures the user is logged in, when not the case, redirect to /google/login
var ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn({ redirectTo: '/google/login' });

router.get('/data', ensureLoggedIn, exportData);

function exportData(req, res, next) {
    res.status(200).send("data send: secured data");
}
```

If you want to use roles, you will need to create a file where you define the roles, something like this:
```javascript
module.exports = {
    Admin: 'Admin',
    Moderator: 'Moderator'
}
```
For JWT you then simply pass the roles that you want to have access to the route to the `authorize` middleware:
```javascript
//Replace [role file name] with the name of the file where you have defined your roles
const Role = require('/[role file name]');
var authorize = require('@gamesolutionslab/server_authentication').authorize;

//A single role can be passed along like so
router.get('/data', authorize("JWT", Role.Admin), exportData);
//Multiple roles have to be passed as an array
router.get('/data', authorize("JWT", [Role.Admin, Role.Moderator]), exportData);

function exportData(req, res, next) {
    res.status(200).send("data send: secured data");
}
``` 
For Google you will need to use the `connect-ensure-login` package along with the `authorize` middleware. Tell the middleware you are using Google and pass along the roles you want to have acces to the route:

```javascript
//Replace [role file name] with the name of the file where you have defined your roles
const Role = require('/[role file name]');
//Ensures the user is logged in, when not the case, redirect to /google/login
var ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn({ redirectTo: '/google/login' });
var authorize = require('@gamesolutionslab/server_authentication').authorize;

//A single role can be passed along like so
router.get('/data', ensureLoggedIn, authorize("Google", Role.Admin), exportData);
//Multiple roles have to be passed as an array
router.get('/data', ensureLoggedIn, authorize("Google", [Role.Admin, Role.Moderator]), exportData);

function exportData(req, res, next) {
    res.status(200).send("data send: secured data");
}
```
## Secure your HTML pages and other files
Now that all your routes are secured, you might want to secure your HTML pages or other files. This is very similar to securing your routes.   
To secure a HTML page or a file, create a route that serves that HTML page or file. Then secure it just like you've done with your other routes:
```javascript
var express = require('express');
var router = express.Router();
//No need to install this package, you already have it by default
var path = require('path');

var passport = require('@gamesolutionslab/server_authentication').google_passport;
var ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn({ redirectTo: '/google/login' 
});
var authorize = require('@gamesolutionslab/server_authentication').authorize;

router.use(passport.initialize());
router.use(passport.session());

//JWT - no roles
router.get('/', authorize("JWT"), fileRoute);
//JWT - with roles
router.get('/', authorize("JWT", Role.Admin), fileRoute);
//Google - no roles
router.get('/', ensureLoggedIn, fileRoute);
//Google - with roles
router.get('/', ensureLoggedIn, authorize("Google", Role.Admin), fileRoute);

function fileRoute(req, res, next) {
    //Replace [path to folder] with the path to the folder where your HTML page or file is located
    //Replace [file name] with the file name and extension of the file you want to serve
    res.sendFile(path.join(__dirname, '[path to folder]', '[file name]'));
}
```


# Troubleshooting
**I can't authenticate my users**    
**I'm getting a `Bad request token` error**    
**Stuck on Google Authentication Screen (infinite loading)**    
Please check if your server calls arrive correctly. If they do, it's highly likely that the problem lies with the database. Make sure you have a connection and make sure you only have one version of `mongoose` installed. You can check this by going into your `node_modules` folder. There should be a single `mongoose` folder. If this is not the case, delete all but one. If this is not the case, got to the `node_modules` folder and find the `@gamesolutionslab > server_authentication > node_modules` folder. Check if this folder contains a `mongoose` folder. If it does, delete it.