291 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			291 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
# auth-server-helper
 | 
						|
 | 
						|
version: 4.1.x
 | 
						|
 | 
						|
customizable and simple authentication
 | 
						|
 | 
						|
## Installation
 | 
						|
 | 
						|
npm:
 | 
						|
 | 
						|
> npm i --save auth-server-helper
 | 
						|
 | 
						|
yarn:
 | 
						|
 | 
						|
> yarn add auth-server-helper
 | 
						|
 | 
						|
## Usage
 | 
						|
 | 
						|
### 1. put a gateway in front of the routes you want to secure
 | 
						|
 | 
						|
```js
 | 
						|
const {create_gateway} = require('@sapphirecode/auth-server-helper');
 | 
						|
 | 
						|
const gateway = create_gateway({
 | 
						|
  redirect_url: '/auth', // if defined, unauthorized requests will be redirected
 | 
						|
  cookie: { name: 'auth_cookie' }, // if defined, access tokens will be read from or written to this cookie,
 | 
						|
  refresh_cookie: { name: 'refresh_cookie' }, // if defined, refresh tokens will be read and used to automatically refresh client tokens (requires the refresh_settings attribute)
 | 
						|
  refresh_settings: {
 | 
						|
    // same as settings for allow_access under section 2
 | 
						|
    // the options data, redirect_to and leave_open are not supported here
 | 
						|
  }
 | 
						|
});
 | 
						|
 | 
						|
// express
 | 
						|
app.use(gateway);
 | 
						|
 | 
						|
// node http
 | 
						|
http.createServer((main_req, main_res) =>
 | 
						|
  gateway(main_req, main_res, (req, res) => {
 | 
						|
    // your request handler
 | 
						|
  });
 | 
						|
);
 | 
						|
```
 | 
						|
 | 
						|
the gateway will forward any authorized requests to the next handler and
 | 
						|
redirect all others to the specified url
 | 
						|
 | 
						|
#### 1.1. Creating a gateway for manual processing of requests
 | 
						|
 | 
						|
```js
 | 
						|
const {GatewayClass} = require('@sapphirecode/auth-server-helper');
 | 
						|
 | 
						|
const gateway = new GatewayClass({ /* options */ }); // options are the same as for create_gateway above
 | 
						|
 | 
						|
// process a request
 | 
						|
if (gateway.authenticate(http_request)) { // returns true if request is valid and sets req.connection.token_id and .token_data
 | 
						|
  console.log('access granted');
 | 
						|
} else {
 | 
						|
  gateway.redirect(response); // redirects the client, triggers deny if no redirect_url was set in options
 | 
						|
  // or
 | 
						|
  gateway.deny(response); // sends status 403
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
### 2. creating the auth endpoint
 | 
						|
 | 
						|
```js
 | 
						|
const {create_auth_handler} = require('@sapphirecode/auth-server-helper');
 | 
						|
 | 
						|
const handler = create_auth_handler(
 | 
						|
  async (req) => {
 | 
						|
    if (req.user === 'foo' && req.password === 'bar')
 | 
						|
      const {access_token_id, refresh_token_id} = await req.allow_access({
 | 
						|
        access_token_expires_in: 600, // seconds until access tokens expire
 | 
						|
        include_refresh_token: true, // should the answer include a refresh token? default: false
 | 
						|
        refresh_token_expires_in: 3600, // seconds until refresh tokens expire (required if refresh tokens are generated)
 | 
						|
        data: {user: 'foo'}, // additional custom data to include in the token
 | 
						|
      });
 | 
						|
 | 
						|
    if (req.user === 'part' && req.password === 'baz')
 | 
						|
      const part_id = await req.allow_part(
 | 
						|
        60, // seconds until part_token expires
 | 
						|
        'some_module', // next module handler (defined below)
 | 
						|
        {foo: 'bar'} // custom data to attach to the token
 | 
						|
      );
 | 
						|
 | 
						|
    // all allow_ functions return a token id, which can later be used to invalidate specific tokens from the server side
 | 
						|
 | 
						|
    req.deny();
 | 
						|
  },
 | 
						|
  {
 | 
						|
    refresh: {
 | 
						|
      /*...same options as allow_access */
 | 
						|
    }, // define the behaviour of refresh tokens. Refresh tokens will not be accepted if this option is undefined
 | 
						|
    modules: {
 | 
						|
      some_module(req) {
 | 
						|
        // request handlers for part_tokens
 | 
						|
 | 
						|
        // access custom data:
 | 
						|
        const auth_data = req.request.connection.auth;
 | 
						|
        auth_data.token_id; // token id
 | 
						|
        auth_data.token_data; // custom data
 | 
						|
        // the same works in handlers after the gateway, information is always stored in request.connection.auth
 | 
						|
      },
 | 
						|
    },
 | 
						|
    cookie: { name: 'auth_cookie' }, // if defined, access tokens will be stored in this cookie,
 | 
						|
    refresh_cookie: { name: 'refresh_cookie' }, // if defined, refresh tokens will be stored in this cookie
 | 
						|
    parse_body: true // read the request body into a string (default false)
 | 
						|
  }
 | 
						|
);
 | 
						|
 | 
						|
// express
 | 
						|
app.use(handler);
 | 
						|
 | 
						|
// node http
 | 
						|
// ... create server, on path /auth run the handler
 | 
						|
handler(req, res); // the handler will also return true if allow_access or allow_part was called
 | 
						|
```
 | 
						|
 | 
						|
after the auth handler, the request will be completed, no additional content
 | 
						|
should be served here. (Read 2.1 for info on disabling this)
 | 
						|
 | 
						|
#### 2.1. Processing Auth Requests without closing the response object
 | 
						|
 | 
						|
to prevent the auth handler from closing the response object you can provide
 | 
						|
additional options on each of the allow/deny functions.
 | 
						|
 | 
						|
```js
 | 
						|
allow_access({leave_open: true, ...});
 | 
						|
allow_part(
 | 
						|
  60,
 | 
						|
  'some_module',
 | 
						|
  {foo: 'bar'},
 | 
						|
  true // additional flag to leave request open
 | 
						|
);
 | 
						|
invalid('error description', true);
 | 
						|
deny(true);
 | 
						|
```
 | 
						|
 | 
						|
if this flag is set, no data will be written to the response body and no data
 | 
						|
will be sent. Status code and Headers will still be set.
 | 
						|
 | 
						|
### Defining Custom Cookie Settings
 | 
						|
 | 
						|
By default all cookies will be sent with 'Secure; HttpOnly; SameSite=Strict'
 | 
						|
Attributes
 | 
						|
 | 
						|
In the appropriate settings object, you can set the following options:
 | 
						|
 | 
						|
```js
 | 
						|
{
 | 
						|
  name: 'foo', // name of the cookies
 | 
						|
  secure: true, // option to enable or disable the Secure option default: true
 | 
						|
  http_only: true, // option to enable or disable HttpOnly default: true
 | 
						|
  same_site: 'Strict', // SameSite property (Strict, Lax or None) default: 'Strict'. Set this to null to disable
 | 
						|
  expires: 'Mon, 10 Jan 2022 09:28:00 GMT', // Expiry date of the cookie
 | 
						|
  max_age: 600, // Maximum age in Seconds
 | 
						|
  domain: 'example.com', // Domain property
 | 
						|
  path: '/cookies_here' // Path property
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
For Documentation on the different Cookie Attributes see
 | 
						|
<https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#creating_cookies>
 | 
						|
 | 
						|
### Invalidating tokens after they are delivered to the client
 | 
						|
 | 
						|
```js
 | 
						|
const {blacklist} = require('@sapphirecode/auth-server-helper');
 | 
						|
 | 
						|
await blacklist.add_signature(token_id); // the token id is returned from any function that creates tokens
 | 
						|
```
 | 
						|
 | 
						|
#### Logout function
 | 
						|
 | 
						|
```js
 | 
						|
const {GatewayClass} = require('@sapphirecode/auth-server-helper');
 | 
						|
 | 
						|
const gateway = new GatewayClass({ /* options */ });
 | 
						|
 | 
						|
// create a new express route
 | 
						|
app.get('logout', (req, res) => {
 | 
						|
  // call the gateway's logout function
 | 
						|
  gateway.logout(req);
 | 
						|
 | 
						|
  // respond ok
 | 
						|
  res.status(200);
 | 
						|
  res.end();
 | 
						|
});
 | 
						|
```
 | 
						|
 | 
						|
### Exporting and importing public keys to validate tokens across server instances
 | 
						|
 | 
						|
```js
 | 
						|
const {keystore} = require('@sapphirecode/auth-server-helper');
 | 
						|
 | 
						|
const export = keystore.export_verification_data();
 | 
						|
 | 
						|
// second instance
 | 
						|
 | 
						|
keystore.import_verification_data(export);
 | 
						|
```
 | 
						|
 | 
						|
These keys can also be live synchronized with redis to allow sessions to be
 | 
						|
shared between servers
 | 
						|
 | 
						|
```js
 | 
						|
const {keystore} = require('@sapphirecode/auth-server-helper');
 | 
						|
 | 
						|
keystore.sync_redis('redis://localhost');
 | 
						|
```
 | 
						|
 | 
						|
### Exporting and importing blacklist entries across server instances
 | 
						|
 | 
						|
```js
 | 
						|
const {blacklist} = require('@sapphirecode/auth-server-helper');
 | 
						|
 | 
						|
const export = blacklist.export_blacklist();
 | 
						|
 | 
						|
// second instance
 | 
						|
 | 
						|
blacklist.import_blacklist(export);
 | 
						|
```
 | 
						|
 | 
						|
### Clearing Keystore and Blacklist
 | 
						|
 | 
						|
Resetting the Keystore instance generates a new instance id and deletes all
 | 
						|
imported or generated keys.
 | 
						|
 | 
						|
```js
 | 
						|
const {keystore, blacklist} = require('@sapphirecode/auth-server-helper');
 | 
						|
 | 
						|
// clear keystore
 | 
						|
keystore.reset_instance();
 | 
						|
 | 
						|
// clear blacklist
 | 
						|
await blacklist.clear();
 | 
						|
 | 
						|
// clear blacklist items older than 10 seconds
 | 
						|
await blacklist.clear(Date.now() - 10000);
 | 
						|
```
 | 
						|
 | 
						|
### Setting and checking permissions
 | 
						|
 | 
						|
When allowing access to a client a list of permissions can be added. Permissions
 | 
						|
are case sensitive.
 | 
						|
 | 
						|
```js
 | 
						|
allow_access({permissions: ['foo','bar'], ...})
 | 
						|
```
 | 
						|
 | 
						|
The gateway can be told to check those permissions before forwarding a request.
 | 
						|
 | 
						|
```js
 | 
						|
const gateway = new GatewayClass({
 | 
						|
  require_permissions: ['foo'], // Only clients with the 'foo' permission will be granted access
 | 
						|
});
 | 
						|
```
 | 
						|
 | 
						|
additional checks can be run later
 | 
						|
 | 
						|
```js
 | 
						|
(req, res) => {
 | 
						|
  const has_both = gateway.check_permissions(req, ['foo', 'bar']); // returns true if both permissions are set
 | 
						|
  const has_bar = gateway.has_permission(req, 'bar'); // returns true if permission 'bar' is set
 | 
						|
};
 | 
						|
```
 | 
						|
 | 
						|
### Reading connection info
 | 
						|
 | 
						|
Data like the used token id, custom data and permissions can be read from
 | 
						|
`req.connection.auth` or using the function `gateway.get_info(req)`
 | 
						|
 | 
						|
```js
 | 
						|
const info = gateway.get_info(req);
 | 
						|
 | 
						|
console.log(info);
 | 
						|
 | 
						|
/*
 | 
						|
{
 | 
						|
  token_id: 'foo',
 | 
						|
  data: {}, // custom data
 | 
						|
  permissions: ['foo','bar']
 | 
						|
}
 | 
						|
*/
 | 
						|
```
 | 
						|
 | 
						|
## License
 | 
						|
 | 
						|
MIT © Timo Hocker <timo@scode.ovh>
 |