Skip to content

Commit

Permalink
Refresh and reword + better deploy workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
zainazeem committed Apr 12, 2024
1 parent b1dad12 commit 11e6ca2
Show file tree
Hide file tree
Showing 8 changed files with 1,389 additions and 946 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
push:
branches:
- main
paths-ignore:
- 'example/**'

jobs:
build-and-publish:
Expand Down
64 changes: 49 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
# session-lock

A library to enhance the security of session tokens by ensuring they are only valid if they are used by the browser to which they were originally issued, thereby protecting against session-jacking attacks.
A library to enhance the security of session tokens by ensuring they are only
valid if they are used by the browser to which they were originally issued,
thereby protecting against session-jacking attacks.

It works by adding a timestamp and a browser-generated Elliptic Curve Digital Signature Algorithm (ECDSA) signature when the token is used to access a protected resource.
It works by adding a timestamp and a browser-generated Elliptic Curve Digital
Signature Algorithm (ECDSA) signature when the token is used to access a
protected resource.

The signature is verified server-side at that time by a public key embedded either within a JWT's payload or in a cookie store associated with the cookie. This public key is generated client-side at the time of the initial authentication and sent to the server alongside the user's credentials. The public key's corresponding private key is stored in an unextractable manner in the browser's IndexedDB database. This private key is used to sign the token when a protected resource is requested.
The signature is verified server-side at that time by a public key embedded
either within a JWT's payload or in a cookie store associated with the cookie.
This public key is generated client-side at the time of the initial
authentication and sent to the server alongside the user's credentials. The
public key's corresponding private key is stored in an unextractable manner in
the browser's IndexedDB database. This private key is used to sign the token
when a protected resource is requested.

The library currently provides first-class functionality for JWTs but the critical functions can also be used with other session token formats.
The library currently provides first-class functionality for JWTs but the
critical functions can also be used with other session token formats.

## Requirements

- The server-side functions provided in this library only run on **Node.js v16+**
- The client-side functions will not work in Firefox Private Browsing mode because IndexedDB is not available in that mode.
- The server-side functions provided in this library have only been tested to
run on **Node.js v16+**
- The client-side functions will not work in Firefox Private Browsing mode
because IndexedDB is not available in that mode.

## Installation

Expand All @@ -21,33 +34,49 @@ npm i session-lock

# Usage

See the `/example` directory for a working example spanning client and server.
See the `/example` directory in the repository on GitHub for a working example
spanning client and server.

## Client-side (Browser)

1. Generate an ECDSA key pair and store the private key in IndexedDB. Run this function during the initial authentication process and include the public key that this function returns in your authentication request (alongside username/password for example).
1. Generate an ECDSA key pair and store the private key in IndexedDB. Run this
function during the initial authentication process and include the public key
that this function returns in your authentication request (alongside
username/password for example).

```javascript
const publicKey = await generateKeyPair();
```

**n.b.** If the authentication is valid (the provided credentials are valid), include the public key in the payload of the JWT that you return from your server. If you're not using JWTs or some other token format that encodes a payload, you can store the public key in a stateful DB and associate it with your serialized token.
**n.b.** If the authentication is valid (the provided credentials are valid),
include the public key in the payload of the JWT that you return from your
server. If you're not using JWTs or some other token format that encodes a
payload, you can store the public key in a stateful DB and associate it with
your serialized token.

2. Lock a session token with a timestamp and ECDSA signature. Run this function when a protected resource is requested and use the locked token in the request header in place of the regular token.
2. Lock a session token with a timestamp and ECDSA signature. Run this function
when a protected resource is requested and use the locked token in the
request header in place of the regular token.

```javascript
const lockedToken = await lockToken(token);
```

## Server-side

3. Verify a locked token's timestamp and signature. Run this function when a protected resource is requested and use the validation message to determine whether to allow access. `externalPublicKey` is an optional param to provide if you're not storing the public key in the JWT payload.
3. Verify a locked token's timestamp and signature. Run this function when a
protected resource is requested and use the validation message to determine
whether to allow access. `externalPublicKey` is an optional param to provide
if you're not storing the public key in the JWT payload.

```javascript
const validationResult = await verifyLockedToken(lockedToken, validityInterval?, externalPublicKey?);
```
4. Split a locked token into its components. Run this function after `verifyLockedToken` to extract the JWT payload and public key from the locked token. Validate the JWT as you would normally after running `verifyLockedToken`.
4. Split a locked token into its components. Run this function after
`verifyLockedToken` to extract the JWT payload and public key from the locked
token. Validate the JWT as you would normally after running
`verifyLockedToken`.
```javascript
const tokenElements = splitLockedToken(lockedToken);
Expand Down Expand Up @@ -77,6 +106,11 @@ const tokenElements = splitLockedToken(lockedToken);
`verifyLockedToken(lockedToken, validityInterval?, externalPublicKey?)`
- Verifies a locked token's timestamp and signature.
- `validityInterval` is the number of milliseconds that the locked token is valid for. If not provided, the token is valid for 2000 ms. This setting allows you to balance preventing replay attacks and accommodating latency between the client and server. 2000 ms is a good default.
- `externalPublicKey` is an optional param to provide if you're not storing the public key in the JWT payload.
- Returns a promise that resolves with a validation message: 'valid' | 'Invalid signature' | 'Token expired'
- `validityInterval` is the number of milliseconds that the locked token is
valid for. If not provided, the token is valid for 2000 ms. This setting
allows you to balance preventing replay attacks and accommodating latency
between the client and server. 2000 ms is a good default.
- `externalPublicKey` is an optional param to provide if you're not storing the
public key in the JWT payload.
- Returns a promise that resolves with a validation message: 'valid' | 'Invalid
signature' | 'Token expired'
18 changes: 9 additions & 9 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,24 @@
"lint": "next lint"
},
"dependencies": {
"@vercel/analytics": "^1.0.0",
"@vercel/analytics": "^1.2.2",
"bcryptjs": "^2.4.3",
"dotenv": "^16.0.3",
"dotenv": "^16.4.5",
"eslint": "8.37.0",
"eslint-config-next": "13.2.4",
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.2",
"next": "13.2.4",
"pg": "^8.10.0",
"pg": "^8.11.5",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-markdown": "^8.0.6",
"react-markdown": "^8.0.7",
"rehype-video": "^1.2.2",
"session-lock": "^0.1.3"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.9",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.21",
"tailwindcss": "^3.3.1"
"@tailwindcss/typography": "^0.5.12",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3"
}
}
Loading

0 comments on commit 11e6ca2

Please sign in to comment.