npm package: @luikingkit/auth-simplified
Description: Auth Simplified is an authentication module using the refresh JWT mechanism. It provides login, logout, force logout, change password, and token refresh APIs, along with middleware for authentication protection, role-based access control, and functions to
Author: Lui King Kit
Latest version: 1.0.0

# Auth Simplified

**Auth Simplified** is an authentication module using the refresh JWT mechanism. It provides login, logout, force logout, change password, and token refresh APIs, along with middleware for authentication protection, role-based access control, and functions to sign non-admin tokens.

This package is ideal for individual projects, enforcing a single admin user for simplicity. For enterprise usage or multi-user admin scenarios, fork this repository and implement full user CRUD.

## Table of contents

- [Installation](#installation)
- [Prerequisites](#prerequisites)
- [Environment Setup](#environment-setup)
- [Password Rules](#password-rules)
- [Quick Start](#quick-start)
  - [Integration Example](#integration-example)
  - [Defining Your Routes](#defining-your-routes)
  - [Signing Non-Admin Tokens](#signing-non-admin-tokens)
- [Roles and Error Codes](#roles-and-error-codes)
- [API Overview](#api-overview)
  - [Authentication](#authentication)
  - [API Endpoints](#api-endpoints)
- [Clean up](#clean-up)
- [References](#references)
- [Contributing](#contributing)
- [License](#license)
- [Support](#support)

<br />

## Installation

Install via npm:

```sh
npm install @luikingkit/auth-simplified
```

Or via yarn:

```sh
yarn add @luikingkit/auth-simplified
```

<br />

## Prerequisites

This package requires:

- express (>=4.16.0)
- mongoose (>=6.8.0, <9.0.0)

Install these in your project if you haven’t already:

via npm:

```sh
npm install express@">=4.16.0"
npm install mongoose@">=6.8.0 < 9.0.0"
```

or via yarn:

```sh
yarn add express@">=4.16.0"
yarn add mongoose@">=6.8.0 < 9.0.0"
```

<br />

## Environment Setup

Set up the following environment variables (e.g. in .env, Docker config, or your cloud provider):

```
JWT_ACCESS_SECRET_ADMIN=changeToYourAdminAccessSecret
JWT_REFRESH_SECRET_ADMIN=changeToYourAdminRefreshSecret
JWT_ACCESS_SECRET_COMMON=changeToYourCommonAccessSecret
JWT_REFRESH_SECRET_COMMON=changeToYourCommonRefreshSecret
JWT_ACCESS_EXPIRES_IN_MINUTES=5
JWT_REFRESH_EXPIRES_IN_MINUTES=30
DB_URI=mongodb://127.0.0.1:27017/yourDatabaseName
USER_NAME=admin
TEMPORARY_PASSWORD=yourPassword
```

### Important:

- `JWT_REFRESH_EXPIRES_IN_MINUTES` must be greater than `JWT_ACCESS_EXPIRES_IN_MINUTES`.
- For security, keep `JWT_ACCESS_EXPIRES_IN_MINUTES` short (e.g. 1–15 minutes). Longer access token lifetimes increase the risk of token leakage.

<br />

## Password Rules

- at least 16 characters long
- contain at least one uppercase letter
- contain at least one lowercase letter
- contain at least one digit
- contain at least one special character

<br />

## Quick Start

### Integration Example

index.js or app.js

```js
const express = require('express');
const mongoose = require('mongoose');
const { initAppRoutes, seedAdmin } = require('auth-simplified');
const router = require('./routes'); // Your custom router
const errorHandler = require('./errorHandler'); // Your custom error handler

const app = express();

initAppRoutes(app, router, errorHandler);

(async () => {
  try {
    const dbUri = process.env.DB_URI;
    await mongoose.connect(dbUri);
    console.log('MongoDB connected');
    await seedAdmin();

    app.listen(port, () => {
      console.log(`Server listening on port ${port}`);
    });
  } catch (error) {
    console.log('Server init error:', error.message);
  }
})();
```

- seedAdmin() will skip creation if the admin user already exists.
- initAppRoutes integrates authentication, routes, and error handling.

References:

- [Defining routes with express.Router](https://expressjs.com/en/guide/routing.html)
- [Writing error handlers](https://expressjs.com/en/guide/error-handling.html)

<br />

### Defining Your Routes

routes.js

```js
const express = require('express');
const router = express.Router();
const {
  verifyAccessToken,
  authByAdmin,
  authByRoles,
  validateObjectId,
} = require('auth-simplified');

// Protect an admin-only endpoint
router.use('/endpointA', verifyAccessToken, authByAdmin, controllerHandlerA);

// Protect an endpoint for multiple roles
router.use(
  '/endpointB',
  verifyAccessToken,
  authByRoles(['customer', 'retailer']),
  controllerHandlerB,
);

// Validate ObjectId in route parameter
router.get('/blogs/:id', validateObjectId(), controllerHandler);

router.get('/blogs/:blogId', validateObjectId('blogId'), controllerHandler);

module.exports = router;
```

#### Middleware Explanation

- verifyAccessToken: Protects endpoints from unauthenticated requests (401 AUTHENTICATION_FAILED).
- authByAdmin: Restricts endpoints to the admin user only.
- authByRoles(roles[]): Restricts access to specified roles (403 FORBIDDEN_ACCESS). **Admin is always permitted.**
- validateObjectId([paramName]): Validates route parameter as a MongoDB ObjectID (defaults to "id").

<br />

### Signing Non-Admin Tokens

```js
const {
  signNonAdminAccessToken,
  signNonAdminRefreshTokens,
} = require('auth-simplified');

// For member login
router.post('/api/auth/members/login', async (req, res) => {
  // ... authenticate member ...
  if (isAuthenticated) {
    const userId = '64f2e5c62a3b0b3e5fc8d6a2';
    const role = 'customer';
    const signed = await signNonAdminRefreshTokens(userId, role);
    if (signed) return res.json(signed);
  }
  res.status(401).json({ message: 'member access denied' });
});

// For app-public-service token
router.post('/api/auth/app', async (req, res) => {
  const { apiKey } = req.body || {};
  if (apiKey === 'thisIsAppApiKey') {
    const role = 'app';
    const signed = await signNonAdminAccessToken(role);
    if (signed) return res.json(signed);
  }
  res.status(401).json({ message: 'app access denied' });
});
```

> **Note:** 'admin' is a reserved role and cannot be used for non-admin tokens.

<br />

## Roles and Error Codes

### Auth Roles

```js
const { AUTH_ROLES } = require('auth-simplified');
```

| Key   | Value | Description                  |
| :---- | :---- | :--------------------------- |
| ADMIN | admin | Admin role, full permissions |

<br />

### Auth Errors

```js
const { AUTH_ERRORS } = require('auth-simplified');
```

| Error Key                         | Status Code | Description                                                                                         |
| :-------------------------------- | :---------- | :-------------------------------------------------------------------------------------------------- |
| USERNAME_REQUIRED                 | 400         | Missing username                                                                                    |
| PASSWORD_REQUIRED                 | 400         | Missing password                                                                                    |
| ROLE_REQUIRED                     | 400         | Missing role, the user or member not assigned a role                                                |
| INVALID_ROLE                      | 400         | Role is not in whitelist                                                                            |
| USERNAME_CANNOT_BE_WHITESPACE     | 400         | Username cannot be whitespace                                                                       |
| PASSWORD_RULES_VIOLATION          | 400         | Password does not meet requirements                                                                 |
| UNABLE_TO_SAVE_PASSWORD           | 400         | Internal error saving password                                                                      |
| INVALID_INPUT                     | 400         | Invalid input type                                                                                  |
| INVALID_REQUEST_BODY_JSON_FORMAT  | 400         | Malformed JSON in request                                                                           |
| INVALID_USERNAME_OR_PASSWORD      | 401         | Wrong credentials                                                                                   |
| LOGOUT_FAILED                     | 401         | User already logged out                                                                             |
| FORCE_USER_LOGOUT_FAILED          | 401         | User already logged out                                                                             |
| AUTHENTICATION_FAILED             | 401         | Authentication failed                                                                               |
| SINGLE_ADMIN_CONSTRAINT_VIOLATION | 403         | Only one admin allowed for simplicity                                                               |
| FORBIDDEN_ACCESS                  | 403         | Insufficient permissions                                                                            |
| CHANGE_PASSWORD_FAILED            | 403         | Change password failed                                                                              |
| TOO_MANY_LOGIN_ATTEMPTS           | 429         | Max 5 login attempts per IP in 15 minutes                                                           |
| TOO_MANY_REQUEST_ATTEMPTS         | 429         | Max 10 refresh token attempts per IP in 15 minutes, max 3 change password attempts per IP in 1 hour |
| REQUEST_BODY_TOO_LARGE            | 413         | Request body too large                                                                              |
| TOO_MANY_FIELDS_IN_REQUEST        | 413         | Too many fields in request body                                                                     |
| NOT_FOUND                         | 404         | Not found                                                                                           |
| UNEXPECTED_ERROR_OCCURRED         | 500         | Unexpected server error                                                                             |

<br />

## API Overview

### Authentication

Most endpoints require a Bearer token from the login endpoint.

#### Common Headers

```http
Content-Type: application/json
Accept: application/json
Authorization: Bearer <access_token>
```

#### Error Responses

```json
{
  "message": "ERROR_KEY"
}
```

- Check HTTP status code for error category.
- Use response.message as the error key for localization.
- [See error codes above](#auth-errors)

<br />

### API Endpoints

#### Login

`POST /api/auth/login`

**Authenticate user and obtain access and refresh tokens.**

**Request**:

```json
{
  "username": "yourUserName",
  "password": "yourPassword"
}
```

**Response**:

```json
{
  "accessToken": "...",
  "refreshToken": "..."
}
```

<br />

#### Logout

`DELETE /api/auth/logout`

**Invalidate the current refresh token.**

**Headers**:

```http
Authorization: Bearer <refresh_token>
```

**Response**:

```json
{
  "message": "LOGOUT_SUCCESS"
}
```

<br />

#### Force Logout

`DELETE /api/auth/force-logout/:userId`

**Admin only**: Invalidate all refresh tokens for a user.

**Headers**:

```http
Authorization: Bearer <admin_access_token>
```

**Response**:

```json
{
  "message": "FORCE_USER_LOGOUT_SUCCESS"
}
```

<br />

#### Refresh Token

`POST /api/auth/refresh-tokens`

**Exchange a refresh token for new tokens.**

**Headers**:

```http
Authorization: Bearer <refresh_token>
```

**Response**:

```json
{
  "accessToken": "...",
  "refreshToken": "..."
}
```

<br />

#### Change Password

`POST /api/auth/change-password`

**Change user password**

[See password rules](#password-rules)

**Request**:

```json
{
  "oldPassword": "oldPass12345678!",
  "newPassword": "newPass12345678!"
}
```

**Response**:

```json
{
  "message": "CHANGE_PASSWORD_SUCCESS"
}
```

<br />

## Clean up

After the admin account is created:

1. **Remove** `USER_NAME` and `TEMPORARY_PASSWORD` from your environment config and restart your web service.
2. **Change the password** via API or your frontend for security.

<br />

## References

- [Express Routing Guide](https://expressjs.com/en/guide/routing.html)
- [Writing Error Handlers for Express](https://expressjs.com/en/guide/error-handling.html)

<br />

## Contributing

This package is designed for simplicity and single-admin use cases. For enterprise features like multi-user management, please fork the repository and extend as needed.

<br />

## License

MIT © [Lui King Kit](https://github.com/luikingkit)

<br />

## Support

- Issues: [GitHub Issues](https://github.com/luikingkit/auth-simplified/issues)
- Documentation: [Read the full docs](https://github.com/luikingkit/auth-simplified#readme)