1.1.0 • Published 5 months ago

@bcwdev/auth0provider v1.1.0

Weekly downloads
38
License
ISC
Repository
github
Last release
5 months ago

@bcwdev/auth0Provider

This library provides easily configured middleware that will validate user auth tokens, roles, permissions and provides a simple approach to get userInfo associted with a user account. Each middleware will call next with an error on any failure so be sure to setup a default error handler. Also note that we extend the express request object with

  • req.user: { UserIdentity }
  • req.userInfo: { UserInfo }

Enable RBAC or Extended Rules (required)

In your auth0 dashboard be sure to enable RBAC or add in this custom rule

//AUTH0 RULE
/**
 * Adds common properties to userInfo
 */
function extendUserInfo(user, context, callback) {
    context.idToken = context.idToken || {};
    context.authorization = context.authorization || {};
    user.app_metadata = user.app_metadata || { };
    user.app_metadata.new = user.app_metadata.id ? false : true;
    user.app_metadata.id = user.app_metadata.id || generateId();

    for (const key in user.app_metadata) {
        context.idToken[`${key}`] = user.app_metadata[key];
    }
    context.idToken[`roles`] = context.authorization.roles;
    context.idToken[`permissions`] = context.authorization.permissions;
    context.idToken[`user_metadata`] = user.user_metadata;
    
    if(!user.app_metadata.new){
        return callback(null, user, context);
    }
    delete user.app_metadata.new;
    auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
        .then(function () {
            callback(null, user, context);
        })
        .catch(function (err) {
            callback(err);
        });  
  
  function generateId() {
    let timestamp = (new Date().getTime() / 1000 | 0).toString(16);
    return timestamp + 'xxxxxxxxxxxxxxxx'.replace(/[x]/g, () => (
      Math.random() * 16 | 0).toString(16)).toLowerCase();
	}
}

Application Use

Example of how to use and configure auth0Provider, You can configure the auth0Provider anywhere in your application and then import it and use the middleware anywhere

import { Auth0Provider } from "@bcw/auth0-server";

Auth0Provider.configure({
  domain: process.env.AUTH_DOMAIN,
  clientId: process.env.AUTH_CLIENT_ID,
  audience: process.env.AUTH_AUDIENCE
});

// validates a request has a Bearer auth token in req.headers.authentication
app.use("/authenticated", Auth0Provider.isAuthenticated, (req, res, next) => {
  res.send({ userIdentity: req.user });
});

// validates the request token and extracts the userInfo saved in auth0
app.use("/user-profile", getAuthorizedUserInfo, (req, res, next) => {
  res.send({ userIdentity: req.user, userInfo: req.userInfo });
});

// validates the request token, extracts the userIdentity and userInfo
// fails if role is not found in the token
// Enable RBAC or Extended Rules
app.use(
  "/admins-only",
  Auth0Provider.hasRoles("admin"),
  (req, res, next) => {}
);

// validates the request token, extracts the userIdentity and userInfo
// fails if any permission is not found in the token
// Enable RBAC or Extended Rules
app.use(
  "/messages",
  Auth0Provider.hasPermissions(["read:messages", "write:messages"]),
  (req, res, next) => {}
);

//recommended default error handler
app.use((error, req, res, next) => {
  if (error.status == 500 || !error.status) {
    error.message = console.error(error); // should write to external
  }
  error = error || {
    status: 400,
    message: "An unexpected error occured please try again later"
  };
  res.status(error.status).send({ ...error, url: req.url });
});

Using chained methods with express.Router()

express
  .Router()
  .get("/blogs", this.getAll)
  .use(Auth0Provider.isAuthorized)
  // everything below this point requires authorization
  .get("/blogs/:id", this.getById);
  .put("/blogs/:id", this.updateById);
  .use(Auth0Provider.hasPermission("delete:blog"))
  // requires permission to reach this point
  .delete("/blogs/:id", this.deleteById);

Mocking the Middleware

Production code can be directly tested by mocking the behavior of Auth0Provider, overriding the need for a bearer token and directly setting the user.

// BlogsController.spec.js
import { MockAuth0Provider } from "@bcw/auth0-server";

const AUTH_MOCK = new MockAuth0Provider()

const USERS = {
  user_billy: { sub: "122", email: "Billy Tester", roles: ['user'], permissions: [] },
  admin_jimmy: { sub: "123", email: "Jimmy Tester", roles: ['admin'], permissions: ['delete:blog'] }
}


describe("blogs controller"){
    
  it('expects a 403 forbidden when attempting to remove a blog without permission', async ()=>{
    
    // Sets the user without the correct permissions for removing a blog
    AUTH_MOCK.setMockUserInfo(USERS.user_billy)

    let res = await request(app)
                      .delete('/blogs/b174arD')
                      .expect(403)
    // ... 
  })

  it('permission required to delete blog', async ()=>{
    
    // Sets the user bypassing the need for a bearer token
    AUTH_MOCK.setMockUserInfo(USERS.admin_jimmy)

    let res = await request(app)
                      .delete('/blogs/b174arD')
                      .expect(200)
    // ... 
  })

}
1.1.0

5 months ago

1.0.18

5 months ago

1.0.17

5 months ago

1.0.16

8 months ago

1.0.15

10 months ago

1.0.14

1 year ago

1.0.13

2 years ago

1.0.9

2 years ago

1.0.8

2 years ago

1.0.11

2 years ago

1.0.10

2 years ago

1.0.12

2 years ago

1.0.7

3 years ago

1.0.6

3 years ago

1.0.5

3 years ago

1.0.3

3 years ago

1.0.2

4 years ago

1.0.1

4 years ago

1.0.0

4 years ago

0.0.20

4 years ago

0.0.18

4 years ago

0.0.19

4 years ago

0.0.17

4 years ago

0.0.16

4 years ago

0.0.15

4 years ago

0.0.14

4 years ago

0.0.12

4 years ago

0.0.13

4 years ago

0.0.11

4 years ago

0.0.10

4 years ago

0.0.9

4 years ago

0.0.8

4 years ago

0.0.7

4 years ago

0.0.6

4 years ago

0.0.3

4 years ago

0.0.5

4 years ago

0.0.4

4 years ago

0.0.2

4 years ago

0.0.1

4 years ago