README
SafePlaces Authentication Service
The modular authentication service for the SafePlaces backend.
# Install using NPM
npm install @pathcheck/safeplaces-auth
# Install using Yarn
yarn add @pathcheck/safeplaces-auth
Table of Contents
Examples
Securing API endpoints
The authentication service can be instantiated and attached to an existing Express application. The point or order at which you attach the service relative to other middleware affects the point at which it is invoked.
const auth = require('@pathcheck/safeplaces-auth');
// Instantiate a public key retrieval client.
const pkClient = new auth.JWKSClient(
// Pass the URL for the JWKS.
`${process.env.AUTH0_BASE_URL}/.well-known/jwks.json`,
);
// Instantiate a request verification strategy.
const auth0Strategy = new auth.strategies.Auth0({
jwksClient: pkClient,
// The API audience is checked against the user's access token.
apiAudience: process.env.AUTH0_API_AUDIENCE,
});
// Instantiate a strategy enforcer.
const enforcer = new auth.Enforcer({
strategy: auth0Strategy,
// Pass in an asynchronous database lookup function
// that takes the user id as the only argument.
userGetter: id => User.findOne(id),
});
// `app` is your Express application.
// A middleware is attached to the app, and it intercepts every
// request for verification. The argument to `enforcer.secure`
// affects which routes are secured.
enforcer.secure(app);
For greater flexibility, you access the built-in enforcer.handleRequest(req, res)
function to selectively handle requests under your own logic. Note that enforcer
will continue to end the request with 403 Forbidden
if the request is unauthorized.
app.use((req, res, next) => {
if (req.headers['X-Bypass-Login']) {
return next();
} else {
return enforcer
// Enforcer ends the request with a `403 Forbidden` if it is unauthorized,
// meaning `next` will not be called unless the request is authorized.
.handleRequest(req, res)
.then(() => next())
.catch(err => next(err));
}
});
For the most flexibility, you can use enforcer.processRequest(req)
to only validate a request,
allowing you to decide how to handle the validation result yourself, whether that be ending
the request or ignoring the error.
app.use((req, res, next) => {
enforcer
.processRequest(req)
// `.then` is called only if `processRequest` has
// determined the request to be authorized.
.then(() => next())
// Otherwise, an error describing the validation error
// will be thrown, and you can decide what to do with it.
.catch(err => res.status(403).send('Forbidden'));
});
Handling login requests
// Instantiate a login handler.
const loginHandler = new auth.handlers.Login({
auth0: {
baseUrl: process.env.AUTH0_BASE_URL,
apiAudience: process.env.AUTH0_API_AUDIENCE,
clientId: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
realm: process.env.AUTH0_REALM,
},
cookie: {
// Enable/disable cookie attributes depending on environment.
secure: process.env.NODE_ENV !== 'development',
sameSite: process.env.BYPASS_SAME_SITE !== 'true',
},
});
// Handle all requests to the login endpoint.
// Since we are passing around the `handle` function, make sure
// to bind the handle function to its object.
app.post('/auth/login', loginHandler.handle.bind(loginHandler));
Handling logout requests
// Instantiate a logout handler.
const logoutHandler = new auth.handlers.Logout({
redirect: 'https://example.com/logout-success/',
cookie: {
// Enable/disable cookie attributes depending on environment.
secure: process.env.NODE_ENV !== 'development',
sameSite: process.env.BYPASS_SAME_SITE !== 'true',
},
});
// Handle all requests to the logout endpoint.
// Since we are passing around the `handle` function, make sure
// to bind the handle function to its object.
app.get('/auth/logout', logoutHandler.handle.bind(logoutHandler));
Strategies
Supported strategies:
- Auth0 asymmetric JWT
- Symmetric JWT
Auth0
Validate the JSON Web Token by checking the signature with the retrieved public key, and validate the API audience.
const auth = require('@pathcheck/safeplaces-auth');
const pkClient = new auth.JWKSClient(
`${process.env.AUTH0_BASE_URL}/.well-known/jwks.json`,
);
const auth0Strategy = new auth.strategies.Auth0({
jwksClient: pkClient,
apiAudience: process.env.AUTH0_API_AUDIENCE,
});
Symmetric JWT
Validate the JSON Web Token by checking the signature with a fixed private key.
const auth = require('@pathcheck/safeplaces-auth');
const symJWTStrategy = new auth.strategies.SymJWT({
algorithm: 'HS256',
privateKey: process.env.JWT_SECRET,
});
Dynamic strategy selection
You can also pass a function into the strategy parameter to dynamically select the strategy based on the request or some other variables.
The function should accept the request as the only argument and return the desired strategy or a promise resolving the desired strategy.
const enforcer = new auth.Enforcer({
strategy: req => {
console.log(req);
// Check something in the request.
// ...
// Example conditional:
if (process.env.NODE_ENV === 'development') {
return symJWTStrategy;
} else {
return auth0Strategy;
}
},
});
Debugging
To debug why the enforcer
is rejecting a request, set the
AUTH_LOGGING
environment variable to verbose
, and the request
validation error will be logged every time.