├── .gitignore ├── .travis.yml ├── .prettierrc ├── .jshintrc ├── LICENSE ├── CHANGELOG ├── package.json ├── README.md ├── index.js ├── index.d.ts └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5" 4 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "expr": true, 3 | "undef": true, 4 | "esnext": true, 5 | "node": true 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Tyler Kellen 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | v3.0.0: 2 | date: 2024-11-11 3 | changes: 4 | - bump dependency on cookie module to ^1.0.1 to address https://github.com/advisories/GHSA-pxg6-pf52-xh8x (a bc break, so new major version) 5 | - bump mocha dependency to silence other warnings (these were not a concern in production) 6 | - update `var` to `const` in tests 7 | v2.4.0: 8 | date: 2019-04-06 9 | changes: 10 | - update dependenices 11 | - swap from yarn to npm package manager 12 | - update syntax to es6 13 | v2.3.0: 14 | date: 2019-04-06 15 | changes: 16 | - search for bearer in signed/unsigned cookies header 17 | - raising the minimum supported nodejs version 18 | v2.2.0: 19 | date: 2018-08-09 20 | changes: 21 | - added typescript support 22 | v2.1.1: 23 | date: 2017-12-06 24 | changes: 25 | - Update express api - `res.send(code)` to `res.status(code).send()` 26 | v2.1.0: 27 | date: 2015-01-21 28 | changes: 29 | - Support custom req key. 30 | v2.0.0: 31 | date: 2014-09-01 32 | changes: 33 | - Support custom access_token keys. 34 | v1.0.1: 35 | date: 2014-09-01 36 | changes: 37 | - Initial release. 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-bearer-token", 3 | "description": "Bearer token middleware for express.", 4 | "version": "3.0.0", 5 | "homepage": "https://github.com/tkellen/node-express-bearer-token", 6 | "authors": [ 7 | "Tyler Kellen ", 8 | "Thomas Boutell " 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/tkellen/node-express-bearer-token.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/tkellen/node-express-bearer-token/issues" 16 | }, 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "https://github.com/tkellen/node-express-bearer-token/blob/master/LICENSE" 21 | } 22 | ], 23 | "keywords": [ 24 | "bearer token", 25 | "bearer token middleware", 26 | "express token", 27 | "authorization bearer" 28 | ], 29 | "main": "index.js", 30 | "engines": { 31 | "node": ">= 6.0.0" 32 | }, 33 | "scripts": { 34 | "test": "mocha -R spec test" 35 | }, 36 | "devDependencies": { 37 | "chai": "^4.2.0", 38 | "cookie-signature": "^1.1.0", 39 | "mocha": "^10.8.2" 40 | }, 41 | "dependencies": { 42 | "cookie": "^1.0.1", 43 | "cookie-parser": "^1.4.4" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # express-bearer-token [![Build Status](https://secure.travis-ci.org/tkellen/js-express-bearer-token.png)](http://travis-ci.org/tkellen/js-express-bearer-token) 2 | > Bearer token middleware for express. 3 | 4 | [![NPM](https://nodei.co/npm/express-bearer-token.png)](https://nodei.co/npm/express-bearer-token/) 5 | 6 | ## What? 7 | 8 | Per [RFC6750] this module will attempt to extract a bearer token from a request from these locations: 9 | 10 | * The key `access_token` in the request body. 11 | * The key `access_token` in the request params. 12 | * The value from the header `Authorization: Bearer `. 13 | * (Optional) Get a token from cookies header with key `access_token`. 14 | 15 | If a token is found, it will be stored on `req.token`. If one has been provided in more than one location, this will abort the request immediately by sending code 400 (per [RFC6750]). 16 | 17 | ```js 18 | const express = require('express'); 19 | const bearerToken = require('express-bearer-token'); 20 | const app = express(); 21 | 22 | app.use(bearerToken()); 23 | app.use(function (req, res) { 24 | res.send('Token '+req.token); 25 | }); 26 | app.listen(8000); 27 | ``` 28 | 29 | For APIs which are not compliant with [RFC6750], the key for the token in each location is customizable, as is the key the token is bound to on the request (default configuration shown): 30 | ```js 31 | app.use(bearerToken({ 32 | bodyKey: 'access_token', 33 | queryKey: 'access_token', 34 | headerKey: 'Bearer', 35 | reqKey: 'token', 36 | cookie: false, // by default is disabled 37 | })); 38 | ``` 39 | 40 | Get token from cookie key (it can be signed or not) 41 | 42 | **Warning**: by __NOT__ passing `{signed: true}` you are accepting a non signed cookie and an attacker might spoof the cookies. so keep in mind to use signed cookies 43 | ```js 44 | app.use(bearerToken({ 45 | cookie: { 46 | signed: true, // if passed true you must pass secret otherwise will throw error 47 | secret: 'YOUR_APP_SECRET', 48 | key: 'access_token' // default value 49 | } 50 | })); 51 | 52 | ``` 53 | 54 | As of version 2.2.0 we've added initial support for TypeScript. 55 | 56 | [RFC6750]: https://tools.ietf.org/html/rfc6750 57 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const parseCookie = require('cookie').parse; 2 | const decodeCookie = require('cookie-parser').signedCookie; 3 | 4 | const getCookie = (serialized_cookies, key) => parseCookie(serialized_cookies)[key] || false; 5 | 6 | module.exports = opts => { 7 | try { 8 | if (!opts) { 9 | opts = { 10 | cookie: false, 11 | }; 12 | } 13 | 14 | const queryKey = opts.queryKey || 'access_token'; 15 | const bodyKey = opts.bodyKey || 'access_token'; 16 | const headerKey = opts.headerKey || 'Bearer'; 17 | const reqKey = opts.reqKey || 'token'; 18 | const cookie = opts.cookie; 19 | 20 | if (cookie && !cookie.key) { 21 | cookie.key = 'access_token'; 22 | } 23 | 24 | if (cookie && cookie.signed && !cookie.secret) { 25 | throw new Error( 26 | '[express-bearer-token]: You must provide a secret token to cookie attribute, or disable signed property' 27 | ); 28 | } 29 | 30 | return (req, res, next) => { 31 | let token; 32 | let error; 33 | 34 | // query 35 | if (req.query && req.query[queryKey]) { 36 | token = req.query[queryKey]; 37 | } 38 | 39 | // body 40 | if (req.body && req.body[bodyKey]) { 41 | if (token) { 42 | error = true; 43 | } 44 | token = req.body[bodyKey]; 45 | } 46 | 47 | // headers 48 | if (req.headers) { 49 | // authorization header 50 | if (req.headers.authorization) { 51 | const parts = req.headers.authorization.split(' '); 52 | if (parts.length === 2 && parts[0] === headerKey) { 53 | if (token) { 54 | error = true; 55 | } 56 | token = parts[1]; 57 | } 58 | } 59 | 60 | // cookie 61 | if (cookie && req.headers.cookie) { 62 | const plainCookie = getCookie(req.headers.cookie || '', cookie.key); // seeks the key 63 | if (plainCookie) { 64 | const cookieToken = cookie.signed 65 | ? decodeCookie(plainCookie, cookie.secret) 66 | : plainCookie; 67 | 68 | if (cookieToken) { 69 | if (token) { 70 | error = true; 71 | } 72 | token = cookieToken; 73 | } 74 | } 75 | } 76 | } 77 | 78 | // RFC6750 states the access_token MUST NOT be provided 79 | // in more than one place in a single request. 80 | if (error) { 81 | res.status(400).send(); 82 | } else { 83 | req[reqKey] = token; 84 | next(); 85 | } 86 | }; 87 | } catch (e) { 88 | console.error(e); 89 | } 90 | }; 91 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for express-bearer-token 2.1.1 2 | // Project: https://github.com/tkellen/js-express-bearer-token 3 | // Definitions by: Jan-Joost den Brinker 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | // TypeScript Version: 2.2 6 | 7 | /* =================== USAGE =================== 8 | 9 | import * as bearerToken from "express-bearer-token"; 10 | app.use(bearerToken({ 11 | bodyKey: 'access_token', 12 | queryKey: 'access_token', 13 | headerKey: 'Bearer', 14 | reqKey: 'token' 15 | })); 16 | 17 | =============================================== */ 18 | 19 | import * as express from "express"; 20 | 21 | /** 22 | * This module will attempt to extract a bearer token from a request from these locations: 23 | * - The key access_token in the request body. 24 | * - The key access_token in the request params. 25 | * - The value from the header Authorization: Bearer . 26 | * - Will check headers cookies if has any 'access_token=TOKEN;' 27 | * 28 | * If a token is found, it will be stored on req.token. 29 | * If a token has been provided in more than one location, the request will be aborted immediately with HTTP status code 400 (per RFC6750). 30 | * 31 | * To change the variables used by this module, you can specify an object with new options. 32 | */ 33 | declare function bearerToken(options?: bearerToken.BearerTokenOptions): express.Handler; 34 | 35 | declare namespace bearerToken { 36 | interface BearerTokenOptions { 37 | /** 38 | * Specify the key that will be used to find the token in the request body. 39 | */ 40 | bodyKey?: string; 41 | 42 | /** 43 | * Specify the key that will be used to find the token in the request params. 44 | */ 45 | queryKey? : string; 46 | 47 | /** 48 | * Specify the value that will be used to find the token in the request header. 49 | */ 50 | headerKey?: string; 51 | 52 | /** 53 | * Specify the key that will be used to bind the token to (if found on the request). 54 | */ 55 | reqKey?: string; 56 | 57 | /** 58 | * Specify cookie options with key, AND if is signed, pass a secret. 59 | */ 60 | cookie?: { 61 | signed: boolean, 62 | key: string, 63 | secret: string, 64 | }; 65 | } 66 | function bearerToken(options?: BearerTokenOptions): express.Handler; 67 | } 68 | 69 | declare global { 70 | namespace Express { 71 | export interface Request { 72 | token?: string 73 | } 74 | } 75 | } 76 | export = bearerToken; 77 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect; 2 | const bearerToken = require('./'); 3 | const cookie = require('cookie-signature'); 4 | 5 | describe('bearerToken', function () { 6 | const token = 'test-token'; 7 | const secret = 'SUPER_SECRET'; 8 | 9 | it('finds a bearer token in post body under "access_token" and sets it to req.token', function (done) { 10 | const req = {body:{access_token:token}}; 11 | bearerToken('secret')(req, {}, function () { 12 | expect(req.token).to.equal(token); 13 | done(); 14 | }); 15 | }); 16 | 17 | it('finds a bearer token in query string under "access_token" and sets it to req.token', function (done) { 18 | const req = {query:{access_token:token}}; 19 | bearerToken()(req, {}, function () { 20 | expect(req.token).to.equal(token); 21 | done(); 22 | }); 23 | }); 24 | 25 | it('finds a bearer token in headers under "authorization: bearer" and sets it to req.token', function (done) { 26 | const req = {headers:{authorization:'Bearer '+token}}; 27 | bearerToken()(req, {}, function () { 28 | expect(req.token).to.equal(token); 29 | done(); 30 | }); 31 | }); 32 | 33 | it('finds a bearer token in post body under an arbitrary key and sets it to req.token', function (done) { 34 | const req = {body:{test:token}}; 35 | bearerToken({bodyKey:'test'})(req, {}, function () { 36 | expect(req.token).to.equal(token); 37 | done(); 38 | }); 39 | }); 40 | 41 | it('finds a bearer token in query string under "access_token" and sets it to req.token', function (done) { 42 | const req = {query:{test:token}}; 43 | bearerToken({queryKey:'test'})(req, {}, function () { 44 | expect(req.token).to.equal(token); 45 | done(); 46 | }); 47 | }); 48 | 49 | it('finds a bearer token in headers under "authorization: " and sets it to req.token', function (done) { 50 | const req = {headers:{authorization:'test '+token}}; 51 | bearerToken({headerKey:'test'})(req, {}, function () { 52 | expect(req.token).to.equal(token); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('finds a bearer token in header SIGNED cookies[] and sets it to req.token', function (done) { 58 | // simulate the res.cookie signed prefix 's:' 59 | const signedCookie = encodeURI('s:' + cookie.sign(token, secret)); 60 | const req = { headers: { cookie: 'test=' + signedCookie + '; ' } }; 61 | bearerToken({ cookie: { key:'test', signed: true, secret: secret } })(req, {}, function () { 62 | expect(req.token).to.equal(token); 63 | done(); 64 | }); 65 | }); 66 | 67 | it('finds a bearer token in header NON SIGNED cookies[] and sets it to req.token', function (done) { 68 | const req = {headers:{cookie: 'test='+token+'; '}}; 69 | bearerToken({cookie:{key: 'test'}})(req, {}, function () { 70 | expect(req.token).to.equal(token); 71 | done(); 72 | }); 73 | }); 74 | 75 | it('finds a bearer token and sets it to req[]', function (done) { 76 | const req = {body:{access_token:token}}; 77 | const reqKey = 'test'; 78 | bearerToken({reqKey:reqKey})(req, {}, function() { 79 | expect(req[reqKey]).to.equal(token); 80 | done(); 81 | }); 82 | }); 83 | 84 | 85 | 86 | it('aborts with 400 if token is provided in more than one location', function (done) { 87 | const req = { 88 | query: { 89 | access_token: 'query-token' 90 | }, 91 | body: { 92 | access_token: 'query-token' 93 | }, 94 | headers: { 95 | authorization: 'bearer header-token', 96 | cookies: 'access_token=cookie-token;' 97 | }, 98 | }; 99 | const res = { 100 | status: function (code) { 101 | res.code = code; 102 | return res; 103 | }, 104 | send: function () { 105 | expect(res.code).to.equal(400); 106 | done(); 107 | } 108 | } 109 | bearerToken()(req, res); 110 | }); 111 | 112 | 113 | 114 | 115 | }); 116 | --------------------------------------------------------------------------------