├── .eslintrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── main.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierrc ├── README.md ├── bun.lockb ├── commitlint.config.cjs ├── package.json ├── src └── index.ts ├── tests ├── fixtures │ ├── private │ └── public └── index.test.ts ├── tsconfig.build.json └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["prettier", "plugin:prettier/recommended", "plugin:@typescript-eslint/recommended"], 4 | "plugins": ["@typescript-eslint", "prettier"], 5 | "parser": "@typescript-eslint/parser", 6 | "parserOptions": { 7 | "project": "./tsconfig.json" 8 | }, 9 | "ignorePatterns": ["dist/", "node_modules/"], 10 | "overrides": [ 11 | { 12 | "files": ["src/**/*.ts", "tests/**/*.ts"] 13 | } 14 | ], 15 | "rules": { 16 | "@typescript-eslint/explicit-module-boundary-types": "off", 17 | "@typescript-eslint/no-explicit-any": "off" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | issuehunt: talentlessguy 4 | github: [tinyhttp, talentlessguy] 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | 15 | Steps to reproduce the behavior: 16 | 17 | 1. ... 18 | 2. ... 19 | 3. ... 20 | 21 | **Expected behavior** 22 | 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Versions** 26 | 27 | - `node`: 14 28 | - `@tinyhttp/cli`: 1.X 29 | 30 | **Additional context** 31 | 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | What problem does it solve? 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | - uses: oven-sh/setup-bun@v1 9 | with: 10 | bun-version: latest 11 | - run: bun install 12 | - run: bun test:coverage 13 | - run: bun test:report 14 | - name: Coveralls 15 | uses: coverallsapp/github-action@master 16 | with: 17 | github-token: ${{ secrets.GITHUB_TOKEN }} 18 | path-to-lcov: ./lcov.info 19 | build: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | - uses: oven-sh/setup-bun@v1 24 | with: 25 | bun-version: latest 26 | - run: bun install 27 | - run: bun run build 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | lcov.info 4 | coverage -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | bunx commitlint --config commitlint.config.cjs --edit "$1" -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | bun lint && bun run build && bun run test -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 120, 5 | "trailingComma": "none", 6 | "tabWidth": 2 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # @tinyhttp/jwt 4 | 5 | [![NPM][npm-badge]][npm-url] [![NPM][dl-badge]][npm-url] [![GitHub Workflow Status][actions-img]][github-actions] [![Coverage][cov-img]][cov-url] 6 | 7 | JWT middleware for HTTP servers. 8 | 9 |
10 | 11 | ## Install 12 | 13 | ```sh 14 | bun i @tinyhttp/jwt 15 | ``` 16 | 17 | ## API 18 | 19 | ```ts 20 | import { jwt } from '@tinyhttp/jwt' 21 | ``` 22 | 23 | ### Options 24 | 25 | #### `jwt(options)` 26 | 27 | - `secret`: can be an array of strings (in case you are using private / public key encryption), or just a string if you are using basic HMAC signing (see the examples below) 28 | - `algorithm? ("HS256")`: the algorithm used to sign and verify the token 29 | - `audience?`: the expected "audience" of the jwt token 30 | - `issuer?`: who issued this token 31 | - `expiresIn?`: expiration time of the token (ex: `1d` for 1 day) 32 | - `notBefore?`: not before date of the token (ex: `20m` for 20 minutes) 33 | - `requestHeaderName? ("Authorization")`: the name of the header contaning the Bearer token 34 | - `responseHeaderName? ("X-Token")`: the name of the response header containing the new signed token that will be used later on 35 | - `getToken(string)?: string`: the method used for ex 36 | 37 | ## Example 38 | 39 | ### Basic secret 40 | 41 | ```ts 42 | import { App } from '@tinyhttp/app' 43 | import { jwt } from '@tinyhttp/jwt' 44 | 45 | new App() 46 | .use(jwt({ secret: 'secret', algorithm: 'HS256' })) 47 | .get('/', (req, res) => res.send(`Data inside the payload: ${req['user']}`)) 48 | .listen(8080) 49 | ``` 50 | 51 | ### Private / Public key 52 | 53 | ```ts 54 | import { App } from '@tinyhttp/app' 55 | import { jwt } from '@tinyhttp/jwt' 56 | 57 | new App() 58 | .use(jwt({ secret: ['PRIVATE KEY', 'PUBLIC KEY'], algorithm: 'RS256' })) 59 | .get('/', (req, res) => res.send(`Data inside the payload: ${req['user']}`)) 60 | .listen(8080) 61 | ``` 62 | 63 | [npm-badge]: https://img.shields.io/npm/v/@tinyhttp/jwt?style=for-the-badge&color=hotpink&label=&logo=npm 64 | [npm-url]: https://npmjs.com/package/@tinyhttp/jwt 65 | [dl-badge]: https://img.shields.io/npm/dt/@tinyhttp/jwt?style=for-the-badge&color=hotpink 66 | [actions-img]: https://img.shields.io/github/actions/workflow/status/tinyhttp/jwt/main.yml?style=for-the-badge&logo=github&label=&color=hotpink 67 | [github-actions]: https://github.com/tinyhttp/jwt/actions 68 | [cov-img]: https://img.shields.io/coveralls/github/tinyhttp/jwt?style=for-the-badge&color=hotpink&a 69 | [cov-url]: https://coveralls.io/github/tinyhttp/jwt 70 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyhttp/jwt/e1e5814601e9e5885980e57daa47557aab9ea2e0/bun.lockb -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const defaultConfig = require('@commitlint/config-conventional') 3 | 4 | module.exports = { 5 | extends: ['@commitlint/config-conventional'], 6 | rules: { 7 | ...defaultConfig.rules, 8 | 'type-enum': [ 9 | 2, 10 | 'always', 11 | ['fix', 'test', 'tooling', 'refactor', 'revert', 'example', 'docs', 'format', 'feat', 'chore'] 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tinyhttp/jwt", 3 | "version": "1.3.1", 4 | "type": "module", 5 | "description": "tiny JWT middleware for Node.js", 6 | "homepage": "https://github.com/tinyhttp/jwt#readme", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/tinyhttp/jwt.git" 10 | }, 11 | "engines": { 12 | "node": ">=14.18 || >=16.20" 13 | }, 14 | "types": "./dist/index.d.ts", 15 | "module": "./dist/index.js", 16 | "exports": "./dist/index.js", 17 | "keywords": [ 18 | "tinyhttp", 19 | "nodejs", 20 | "web", 21 | "backend", 22 | "jwt" 23 | ], 24 | "author": "BRA1L0R", 25 | "contributors": [ 26 | "BRA1L0R ", 27 | "v1rtl " 28 | ], 29 | "license": "MIT", 30 | "dependencies": { 31 | "jsonwebtoken": "^9.0.2" 32 | }, 33 | "files": [ 34 | "dist" 35 | ], 36 | "scripts": { 37 | "build": "tsc -p tsconfig.build.json", 38 | "test": "tsx tests/index.test.ts", 39 | "test:coverage": "c8 tsx --test tests/index.test.ts", 40 | "test:report": "c8 report --reporter=text-lcov > lcov.info", 41 | "lint": "eslint . --ext=ts", 42 | "format": "prettier --check \"./**/*.{ts,md}\"", 43 | "format:fix": "prettier --write \"./**/*.{ts,md}\"", 44 | "prepare": "husky install" 45 | }, 46 | "devDependencies": { 47 | "@commitlint/cli": "^18.2.0", 48 | "@commitlint/config-conventional": "^18.1.0", 49 | "@types/jsonwebtoken": "^9.0.4", 50 | "@types/node": "^20.8.9", 51 | "@typescript-eslint/eslint-plugin": "^6.9.0", 52 | "@typescript-eslint/parser": "^6.9.0", 53 | "bun-types": "^1.0.7", 54 | "c8": "^8.0.1", 55 | "eslint": "^8.52.0", 56 | "eslint-config-prettier": "^9.0.0", 57 | "eslint-plugin-prettier": "^5.0.1", 58 | "expect": "^29.7.0", 59 | "husky": "^8.0.3", 60 | "prettier": "^3.0.3", 61 | "tsx": "^3.14.0", 62 | "typescript": "^5.2.2" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { IncomingMessage, ServerResponse } from 'node:http' 2 | import jwtoken, { Algorithm } from 'jsonwebtoken' 3 | 4 | export interface JwtMwProps { 5 | secret: string | string[] 6 | algorithm?: Algorithm 7 | audience?: string 8 | issuer?: string 9 | expiresIn?: string 10 | notBefore?: string 11 | requestHeaderName?: string 12 | responseHeaderName?: string 13 | getToken?: (header: string) => string 14 | } 15 | 16 | export interface RequestWithUser extends IncomingMessage { 17 | user?: any 18 | } 19 | 20 | const getTokenFromHeader = (header: string) => (!header ? '' : header.split(' ')[1]) 21 | 22 | /** 23 | * JWT middleware 24 | */ 25 | export const jwt = ({ 26 | secret, 27 | algorithm = 'HS256', 28 | audience = '', 29 | issuer = '', 30 | expiresIn = '50y', 31 | notBefore = '0s', 32 | requestHeaderName = 'authorization', 33 | responseHeaderName = 'X-Token', 34 | getToken = getTokenFromHeader 35 | }: JwtMwProps) => { 36 | return function (req: RequestWithUser, res: ServerResponse, next: () => void) { 37 | const token: string = getToken((req.headers[requestHeaderName] as string) ?? '') 38 | 39 | try { 40 | // Verify the JWT token 41 | const verify = jwtoken.verify(token, Array.isArray(secret) ? secret[1] : secret, { 42 | algorithms: [algorithm], 43 | audience, 44 | issuer 45 | }) 46 | 47 | req.user = verify 48 | 49 | res.setHeader( 50 | responseHeaderName, 51 | jwtoken.sign(req.user, Array.isArray(secret) ? secret[0] : secret, { 52 | audience, 53 | issuer, 54 | expiresIn, 55 | notBefore, 56 | algorithm 57 | }) 58 | ) 59 | next() 60 | } catch { 61 | next() 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/fixtures/private: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | XMrPObULdcgZLODNOYz8vNHQcM8fvw== 52 | -----END PRIVATE KEY----- -------------------------------------------------------------------------------- /tests/fixtures/public: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnYLhjrHJthJTbcUzZNxR 3 | O8n1H2/PAwlM/B2lZ91SMIcZKOqROot3fRCO7uj1HBc0Oy3+E+Spn221t6FfxeOh 4 | fS3EoyW9mqBr5o6XW2dijYKrgYn0GutUCcgQHWc+z+4JSfRQINwt1eHhmUtZOORS 5 | /XCyd3Xelm5Rn1MVBbHRpD6YWcaftMxl8D93tq6HosO8M4btA+7wmiLMOXQm/aTa 6 | DH+nZQJly4YW4l0vhGtvvctppHzZ94cqLCdAAx0p+vyeI4haQVzYtiWGgCg7q2OE 7 | c/fZxO1r6SKLGq+nGcEoRIo532Anrv6NbfVMSDBXnWwR7je2N1+wL7P8kAk0+XJ7 8 | XT6HHE7vYvM1BQ6a8GVqFPB4tQcW/o7HrlrYC3EFZs+bYW9DxQO1YrkRe3Wjrl7d 9 | MmUM+90vU0qBsxzWn2bOT8lhn0X3ZPGILa4MYY59C+FBl+xEiDjaiZ00VYn6rqAN 10 | sStL/zBvtXITkSewBB2kMnh22lofkY2EyffTwGzJuGe+byjl/ZAYP/FxonvWxjXU 11 | xsqWECPxfTTYIhvur84ElpL+a7S6a/39Z7stwAtBOiEcle1UlMDDhk0NwOg/9LVS 12 | whakIBA+IdSxEJ88BoKMNlke0frHfCK1bKSGCRJn4SW7n9O9GkBSpLYBv+stMksY 13 | 9rEuuARFsNX3/tsnyzd8nlkCAwEAAQ== 14 | -----END PUBLIC KEY----- 15 | -------------------------------------------------------------------------------- /tests/index.test.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, it } from 'node:test' 2 | import type { ServerResponse } from 'node:http' 3 | import fs from 'node:fs' 4 | import jsonwebtoken, { Algorithm } from 'jsonwebtoken' 5 | import { expect } from 'expect' 6 | import { jwt, RequestWithUser } from '../src/index.js' 7 | 8 | const req = {} as RequestWithUser 9 | const res = {} as ServerResponse 10 | 11 | beforeEach(() => { 12 | req.headers = {} 13 | req.user = {} 14 | }) 15 | 16 | it('should work using default algorithm if authorization header is valid jsonwebtoken', () => { 17 | const secret = 'shhhhhh' 18 | const token = jsonwebtoken.sign({ foo: 'bar' }, secret) 19 | 20 | req.headers.authorization = 'Bearer ' + token 21 | 22 | jwt({ secret: secret })(req, res, () => { 23 | expect(req.user.foo).toBe('bar') 24 | }) 25 | }) 26 | 27 | it('should work with different HMAC algorithms', () => { 28 | const algorithms: Algorithm[] = ['HS512', 'HS256', 'HS384'] 29 | const secret = 'shhhhhh' 30 | 31 | algorithms.forEach((algorithm) => { 32 | const token = jsonwebtoken.sign({ foo: 'bar' }, secret, { algorithm }) 33 | req.headers.authorization = 'Bearer ' + token 34 | 35 | jwt({ secret: secret, algorithm })(req, res, () => { 36 | expect(req.user.foo).toBe('bar') 37 | }) 38 | }) 39 | }) 40 | 41 | it('should work if authorization header is valid with a buffer secret', () => { 42 | const secret = Buffer.from('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 'base64') 43 | const token = jsonwebtoken.sign({ foo: 'bar' }, secret) 44 | 45 | req.headers.authorization = 'Bearer ' + token 46 | 47 | jwt({ secret: secret.toString(), algorithm: 'HS256' })(req, res, () => { 48 | expect(req.user.foo).toBe('bar') 49 | }) 50 | }) 51 | 52 | it('should handle private key encryption', () => { 53 | const privateKey = fs.readFileSync('tests/fixtures/private', { encoding: 'utf-8' }) 54 | const publicKey = fs.readFileSync('tests/fixtures/public', { encoding: 'utf-8' }) 55 | 56 | req.headers.authorization = 'Bearer ' + jsonwebtoken.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256' }) 57 | 58 | jwt({ secret: [privateKey, publicKey], algorithm: 'RS256' })(req, res, () => { 59 | expect(req.user.foo).toBe('bar') 60 | }) 61 | }) 62 | 63 | it('should work with different RSA algorithms', () => { 64 | const algorithms: Algorithm[] = ['RS256', 'RS384', 'RS512'] 65 | const privateKey = fs.readFileSync('tests/fixtures/private', { encoding: 'utf-8' }) 66 | const publicKey = fs.readFileSync('tests/fixtures/public', { encoding: 'utf-8' }) 67 | 68 | algorithms.forEach((algorithm) => { 69 | req.headers.authorization = 'Bearer ' + jsonwebtoken.sign({ foo: 'bar' }, privateKey, { algorithm }) 70 | 71 | jwt({ secret: [privateKey, publicKey], algorithm })(req, res, () => { 72 | expect(req.user.foo).toBe('bar') 73 | }) 74 | }) 75 | }) 76 | 77 | it('should not work with malformed input', () => { 78 | const fake_secret = 'notmuchsecret' 79 | const true_secret = 'verysecret' 80 | const token = jsonwebtoken.sign({ foo: 'bar' }, fake_secret) 81 | 82 | req.headers.authorization = 'Bearer ' + token 83 | 84 | jwt({ secret: true_secret })(req, res, () => { 85 | expect(req.user.foo).toBeUndefined() 86 | }) 87 | }) 88 | 89 | it('should not work if authorization header is missing', () => { 90 | const secret = 'shhhhhh' 91 | 92 | jwt({ secret: secret })(req, res, () => { 93 | expect(req.user.foo).toBeUndefined() 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "declaration": true 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "NodeNext", 4 | "target": "esnext", 5 | "moduleResolution": "NodeNext", 6 | "strict": true, 7 | "skipLibCheck": true, 8 | "types": [ 9 | "bun-types" // add Bun global 10 | ], 11 | "lib": ["DOM"] 12 | } 13 | } 14 | --------------------------------------------------------------------------------