Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"version": "0.1",
"language": "en",
"ignoreRegExpList": ["/```[\\w\\W]*?```/"],
"words": ["JWKS", "commitlint", "southlane", "markdownlint"]
"ignorePaths": ["node_modules/**", "example/**"],
"words": ["JWKS", "commitlint", "southlane", "markdownlint", "runtimes"]
}
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
node_modules
package-lock.json
dist
example
9 changes: 0 additions & 9 deletions .eslintrc

This file was deleted.

23 changes: 23 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = {
env: {
node: true,
},
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
parserOptions: {
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
project: './tsconfig.eslint.json', // Separate config for ESlint where JS files are included
},
extends: [
'eslint:recommended', // the set of rules which are recommended for all projects by the ESLint Team
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
],
rules: {
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-floating-promises': ['error'],
},
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ coverage/
# IDE configuration
.idea/
.vscode/

dist
104 changes: 40 additions & 64 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# cognito-jwt-verifier

Verify ID and access JWT tokens from AWS Cognito in your node/Lambda backend
with minimal npm dependencies.
or browser environment with minimal npm dependencies.

Why this library? I couldn't find anything checking
all the boxes for me:
Expand All @@ -11,11 +11,14 @@ all the boxes for me:
- JWKS (public keys) caching
- test coverage

This module is a thin layer on top of [jose](https://github.com/panva/jose)
(the only dependency), to make it easy to work with Cognito tokens.

## Getting Started

### Prerequisites

- Node.js version >=10.13.0
For the list of supported runtimes check [jose runtime support matrix](https://github.com/panva/jose#runtime-support-matrix).

### Installing

Expand All @@ -25,23 +28,36 @@ npm i @southlane/cognito-jwt-verifier

## Usage

### Obtain tokens from Cognito

1. Set up a Cognito User Pool.
Note **User Pool ID** on the "General Settings" page in AWS Console.
2. Within the User Pool, create an Application Client.
Note **App Client ID** on the App Clients page.
3. Fetch ID/access tokens. Either by making an AWS SDK / Amplify call
or from a Hosted UI redirect.
3. Fetch ID/access tokens. Either by making an AWS SDK / Amplify call or
from a Hosted UI redirect.

- (test flow for the Hosted UI and implicit flow) Create a new user
in **General settings** / **Users and groups** / **Create user**.

- Launch the Hosted UI: **App integration** / **App client settings**
/ **Launch Hosted UI**.

- Enter login and password for the user you created. Set a new password.

- Cognito will redirect you to the app's target URL (it doesn't have to
resolve) and you can inspect ID and access tokens from the URL.

Now you can programmatically verify issued ID and access tokens:
- Use tokens to decode them at [jwt.io](https://jwt.io/) and/or test
with this library.

### Verify issued ID and access tokens programmatically

```js
const {
verifierFactory,
errors: { JwtVerificationError, JwksNoMatchingKeyError },
} = require('@southlane/cognito-jwt-verifier')
const { verifierFactory } = require('@southlane/cognito-jwt-verifier')

// get a verifier instance. Put your config values here.
const verifier = verifierFactory({
// get a verifier function instance. Put your config values here.
const verify = verifierFactory({
region: 'us-east-1',
userPoolId: 'us-east-1_PDsy6i0Bf',
appClientId: '5ra91i9p4trq42m2vnjs0pv06q',
Expand All @@ -53,17 +69,10 @@ const expiredToken =
'eyJraWQiOiI0UFFoK0JaVExkRVFkeUM2b0VheVJDckVjblFDSXhqbFZFbTFVd2RhZ2ZNPSIsImFsZyI6IlJTMjU2In0.eyJhdF9oYXNoIjoiQlNFSWQ1bkYyN3pNck45QkxYLVRfQSIsInN1YiI6IjI0ZTI2OTEwLWU3YjktNGFhZC1hOTk0LTM4Nzk0MmYxNjRlNyIsImF1ZCI6IjVyYTkxaTlwNHRycTQybTJ2bmpzMHB2MDZxIiwiZXZlbnRfaWQiOiJiNmQ3YTYyZC01NGRhLTQ5ZTYtYTgzOS02NjUwNmYwYzIxYjUiLCJ0b2tlbl91c2UiOiJpZCIsImF1dGhfdGltZSI6MTU4NzMxMTgzOCwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfUERzeTZpMEJmIiwibmFtZSI6Ik1heCBJdmFub3YiLCJjb2duaXRvOnVzZXJuYW1lIjoiMjRlMjY5MTAtZTdiOS00YWFkLWE5OTQtMzg3OTQyZjE2NGU3IiwiZXhwIjoxNTg3MzE1NDM4LCJpYXQiOjE1ODczMTE4MzgsImVtYWlsIjoibWF4QHNvdXRobGFuZS5jb20ifQ.GrlpeYQDwB81HjBZRkuqzw0ZXSGFBi_pbMoWC1QvHyPYrc6NRto02H4xgMls5OmCGa4bZBYWTT6wfo0bxuOLZDP__JRSfOyPUIbiAWTu1IiyAhbt3nlW1xSNSvf62xXQNveF9sPcvG2Gh6-0nFEUrAuI1a5QAVjXbp1YDDMr2TzrFrugW7zl2Ntzj42xWIq7P0R75S2JYVmBfhAxS6YNO1n8KpOFzxagxmn89leledx4PTxuOdWdmT6vZkW9q9QnOI9kjgUIxfWjx55205P4BwkOeqY7AN0j85LBwAHbhezfzNETybX1pwnMBh1p5_iLYgQMMZ60ZJseGl3cMRsPnQ'

try {
const tokenPayload = await verifier.verify(expiredToken)
const tokenPayload = await verify(expiredToken)
} catch (e) {
if (
e instanceof JwtVerificationError ||
e instanceof JwksNoMatchingKeyError
) {
// token is malformed, invalid, expired or cannot be validated with known keys
// act accordingly, e.g. return HTTP 401 error
}

throw e
// token is malformed, invalid, expired or cannot be validated with known keys
// act accordingly, e.g. return HTTP 401 Unauthorized error
}
```

Expand All @@ -86,27 +95,18 @@ On successful verification `tokenPayload` will hold the body (payload) of the JW
}
```

Check [complete usage examples](./example).

### Errors Thrown

- `TypeError` on invalid input arguments.
- `JwksFetchError` on failed https request to fetch JSON Web Key Set.
- `JwksNoMatchingKeyError` on JWT referencing key which is missing in the key set.
- `JwtVerificationError` on failed JWT verification.
Inspect error object's `originalError` property to find out verification error
details.

Underlying Jose library may throw lower-level errors,
like if you try to import invalid JWKS.
<https://github.com/panva/jose/blob/master/docs/README.md#errors>.
Those are not supposed to be thrown under normal course of operation and
probably signify a programmer's error.
- `JwtCognitoClaimValidationError` when token's `token_use` does not match.
- Instances of [`JOSEError`](https://github.com/panva/jose/blob/master/docs/modules/_util_errors_.md).

### Leveraging Cache

Verifier instance you get from `verifierFactory()` call has an internal JWKS cache
to avoid hitting the network on subsequent calls.

Make sure verifier instance is shared across `verifier.verify()` calls.
Verify function instance you get from `verifierFactory()` call has an internal
JWKS cache (via `jose`) to avoid hitting the network on subsequent calls.

## Running the Tests

Expand All @@ -124,7 +124,7 @@ Run tests with coverage report:
npm run test-coverage
```

### Coding Style and Documentation Tests
### Coding Quality Tests

Make sure code has no syntax errors and is properly formatted.
Make sure docs are valid Markdown.
Expand All @@ -141,33 +141,6 @@ Make sure there are no known vulnerabilities in dependencies.
npm run audit-security
```

## Built With

- [Jose](https://github.com/panva/jose) - "JSON Web Almost Everything" -
JWA, JWS, JWE, JWK, JWT, JWKS for Node.js with minimal dependencies
- [Mocha](https://github.com/mochajs/mocha),
[Sinon](https://github.com/sinonjs/sinon),
[Chai](https://github.com/chaijs/chai),
[nyc](https://github.com/istanbuljs/nyc),
[mock-req](https://github.com/diachedelic/mock-req),
[mock-res](https://github.com/diachedelic/mock-res) - Testing, Mocking & Coverage
- [ESLint](https://github.com/eslint/eslint),
[Prettier](https://github.com/prettier/prettier),
[cspell](https://github.com/streetsidesoftware/cspell),
[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli) -
Linting & Formatting
- [commitlint](https://github.com/conventional-changelog/commitlint),
[Husky](https://github.com/typicode/husky) - Commit Message Linting
- [audit-ci](https://github.com/IBM/audit-ci) - Package Security Audit

### Dependency Graph

```text
@southlane/[email protected] (2 deps, 280.94kb, 120 files)
╰─┬ [email protected] (1 dep, 266.29kb, 108 files)
╰── @panva/[email protected] (45.74kb, 18 files)
```

## Getting Help

If you have questions, concerns, bug reports, etc, please file an issue in this
Expand All @@ -191,3 +164,6 @@ see the [LICENSE.md](LICENSE.md) file for details
## Credits and references

- [Verifying a JSON Web Token](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html)
- [jose](https://github.com/panva/jose) - Universal "JSON Web Almost
Everything" - JWA, JWS, JWE, JWT, JWK with no dependencies using native
crypto runtimes
24 changes: 24 additions & 0 deletions example/node/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { verifierFactory } = require('@southlane/cognito-jwt-verifier')

async function main() {
const verify = verifierFactory({
region: 'us-east-1',
userPoolId: 'us-east-1_eB1Xdqe4i',
appClientId: '11tk4h9tul8n81u9uv4nmpo5sn',
tokenType: 'access', // either "access" or "id"
})

const token =
'eyJraWQiOiI0UHZLaGtsbTAxNWQ4YXJsOFRJWUhpcFRUU05oWmhiemNFVFN4c0U0UW1vPSIsImFsZyI6IlJTMjU2In0.eyJhdF9oYXNoIjoiampZMlFkQ0ZBdFNROXF2Vk96WXgwdyIsInN1YiI6IjEyMDM0MzFhLWJhNzMtNDM1Ni1iYjZjLThjNTZiYzI4OTUwMyIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0xLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMV9lQjFYZHFlNGkiLCJjb2duaXRvOnVzZXJuYW1lIjoiMTIwMzQzMWEtYmE3My00MzU2LWJiNmMtOGM1NmJjMjg5NTAzIiwiYXVkIjoiMTF0azRoOXR1bDhuODF1OXV2NG5tcG81c24iLCJldmVudF9pZCI6ImIxNWJmZjBhLWM5NDAtNGU2NC1iOGQ4LTAwNzViNjY3NmQxOCIsInRva2VuX3VzZSI6ImlkIiwiYXV0aF90aW1lIjoxNjA4Njg0OTM3LCJuYW1lIjoiTWF4IiwiZXhwIjoxNjA4Njg4NTM3LCJpYXQiOjE2MDg2ODQ5MzcsImVtYWlsIjoiaGVsbG9AbWF4aXZhbm92LmlvIn0.Ikkzy8sE-SCFVhtUrtQlDzR4JE5qIC3-XR_IOrbKw2jkOTf-ziL9og06X0OVfhpOKipKB7sBR4gs0P1I63qP_j-q0L3a8g_Fy81qxwtVfd64j2bXs__5gzZupmX2DEkOfAugCmIi-kHKQwynmHCT6X-C8GLqnqILqmphi6du_pIexVEDpMQfBAz2PhN12x35iKKT1ejv4bDrYJ0ZHYpmjytwNnVHJvI0jzODz7pDglnURXAJ1mxdaSgMw4WsEIdZlooNnRtWOeJeXCBMDyO70QSV4lV_G6SnCqh73h_WTMZaOYoj09I54fuyrw1e7lq-wnGOPAALpzkquX992bOlYw'

try {
const tokenPayload = await verify(token)
console.log('Token payload', tokenPayload)
} catch (e) {
console.log('Error', e)
}
}

;(async () => {
await main()
})()
15 changes: 15 additions & 0 deletions example/node/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "test-cognito-jwt-verifier",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@southlane/cognito-jwt-verifier": "file:../cognito-jwt-verifier"
}
}
4 changes: 4 additions & 0 deletions example/react/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": ["@babel/plugin-transform-runtime"]
}
1 change: 1 addition & 0 deletions example/react/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
5 changes: 5 additions & 0 deletions example/react/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

{
"singleQuote": true,
"printWidth": 70
}
35 changes: 35 additions & 0 deletions example/react/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# minimal-react-webpack-babel-setup

[![Build Status](https://travis-ci.org/rwieruch/minimal-react-webpack-babel-setup.svg?branch=master)](https://travis-ci.org/rwieruch/minimal-react-webpack-babel-setup) [![Greenkeeper badge](https://badges.greenkeeper.io/rwieruch/minimal-react-webpack-babel-setup.svg)](https://greenkeeper.io/)

Read how to set it up yourself: [React with Webpack Tutorial](https://www.robinwieruch.de/minimal-react-webpack-babel-setup/).

[![Edit minimal-react-webpack-babel-setup](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/rwieruch/minimal-react-webpack-babel-setup/tree/master/?fontsize=14)

## Features

- React 16
- Webpack 5
- Babel 7
- Hot Module Replacement

## DIY Add-Ons

- [ESLint](https://www.robinwieruch.de/react-eslint-webpack-babel/)
- [CSS Modules](https://www.robinwieruch.de/react-css-modules/)
- [SVG Icons](https://www.robinwieruch.de/react-svg-icon-components/)
- [Fonts Support](https://www.robinwieruch.de/webpack-font/)
- [Images Support](https://www.robinwieruch.de/webpack-images/)
- [Docker](https://www.robinwieruch.de/docker-react-development)

## Alternatives

- [Advanced React Webpack Babel Setup](https://github.com/rwieruch/advanced-react-webpack-babel-setup) via this [Tutorial](https://www.robinwieruch.de/webpack-advanced-setup-tutorial)

## Installation

- `git clone [email protected]:rwieruch/minimal-react-webpack-babel-setup.git`
- cd minimal-react-webpack-babel-setup
- npm install
- npm start
- visit `http://localhost:8080/`
30 changes: 30 additions & 0 deletions example/react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "minimal-react-webpack-babel-setup",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack serve --config ./webpack.config.js --mode development",
"test": "echo \"No test specified\" && exit 0"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.12.3",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.1",
"@babel/preset-react": "^7.12.1",
"@babel/runtime": "^7.12.5",
"babel-loader": "^8.1.0",
"react-hot-loader": "^4.13.0",
"webpack": "^5.3.2",
"webpack-cli": "^4.1.0",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
"@southlane/cognito-jwt-verifier": "file:../cognito-jwt-verifier",
"react": "^17.0.1",
"react-dom": "^17.0.1"
}
}
3 changes: 3 additions & 0 deletions example/react/sandbox.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"template": "node"
}
Loading