Securing Tokens with help from JOSE (2024)

Writing Javascript that runs in a browser is great, but there is a serious downside, which is that anyone can view the source of your code, and there is a myriad of tools available to help people dig right in to even the most obfuscated stuff. That’s not a problem if you are careful to keep actual secrets out of client-facing code, but every so often you’ll hit a scenario where some sort of secret needs to be exposed in userland. To do this correctly you need to ensure that secret is encrypted. Not just one-way hashed like a password, but two-way encrypted so you can actually use it again.

A common scenario.

To service your site’s user your server needs to interact with one or more 3rd party APIs. Those APIs expect some sort of auth token to be passed in via the Authorization header and they send you this token the first time you authenticate with them on behalf of your user. The token the API needs is specific to each of your users and can’t be shared between users. So your server talks to theirs, gets back a Json Web Token (aka a JWT), and now, because your server is totally stateless†, the only place for you to keep that token is back in the user’s own browser.

So what’s a JWT?

JWTs have emerged as the most popular way to encode data for exchange between API servers. They are a simple JSON structure with built in properties that describe the issuer, audience, incept date and expiry time, as well as a subject that can be any kind of JSON data you like. JWTs are signed on creation using an arbitrary string as a signing secret, and that secret gets shared with whomsoever the creator whishes to be able to verify the validity of the JWT. It’s simple and quick, and it’s relatively secure so long as the secret is only shared between trusted parties, the API calls are all made over HTTPS and the JWT itself is never exposed. So great for API to API authentication. Not so great for browser to API authentication.

Making a JWT is trivial.

const jwt = require('jwt-simple')const secret = 'some super shared secret'
const token = jwt.encode({ sub: { someData: 'goes here' } }, secret)

However any JWT string can be decoded, even without knowing the secret. The secret only lets you verify that the JWT was correctly signed. JWTs are only encoded, not encrypted. And this makes them utterly unsuitable for exposing within a website, or, worse, a URL.

Enter JOSE.

JOSE, the JSON Object Signing and Encryption standard, solves this issue by giving you a formal mechanism to create two-way encrypted tokens. The main JOSE library for Node is made by Cisco and is called node-jose.

The Node-Jose library is also quite simple to use, but the docs assume you’ve digested the entire JOSE spec first.

Let’s assume what you really want is something that works like a JWT, but is actually secure. So you need an encrypt function that takes in some sort of arbitrary data, and returns a base64 encoded string, and you want a decrypt function that takes in a base64 encoded string and returns the decrypted data again.

Base64 Encoding

JOSE doesn’t have anything to say about base64 encoding so let’s manage that first.

Here’s a general purpose base64 encoder/decoder I use a lot in my code.

// src/utils/base64.jsconst encodeBuffer = buffer => buffer.toString('base64')
const encodeString = string => encodeBuffer(Buffer.from(string))
const encodeData = data => encodeString(JSON.stringify(data))
const encode = (data) => {
if (Buffer.isBuffer(data)) return encodeBuffer(data)
if (typeof data === 'string') return encodeString(data)
return encodeData(data)
}
const decode = (string) => {
const decoded = Buffer.from(string, 'base64').toString()
try {
return JSON.parse(decoded)
} catch (e) {
return decoded
}
}
module.exports = { encode, decode }

It’s tested via this:

// test/unit/utils/base64_spec.jsconst { expect } = require('chai')
const faker = require('faker')
const { encode, decode } = require('../../../src/utils/base64')
describe('base64', () => {
describe('encode', () => {
describe('given a string', () => {
const raw = faker.lorem.words()
const original = `${raw}`
it('encodes without altering the original string', () => {
expect(encode(raw)).to.exist
expect(raw).to.equal(original)
})
})
describe('given a buffer', () => { const raw = Buffer.from(faker.lorem.words()) it('encodes', () => {
expect(encode(raw)).to.exist
})
})
describe('given an object', () => { const raw = { test: faker.lorem.words() } it('encodes', () => {
expect(encode(raw)).to.exist
})
})
})
describe('decode', () => {
describe('given an encoded string', () => {
const raw = { test: faker.lorem.words() }
const encoded = encode(raw)
const original = `${encoded}`
it('decodes without altering the original string', () => {
expect(decode(encoded)).to.eql(raw)
expect(encoded).to.equal(original)
})
})
})
})

Encrypting / decrypting.

Now we’ve got the underlying base64 encoding out of the way, the actual JOSE part is simply this

// src/utils/jose.jsconst { JWE } = require('node-jose')
const { encode, decode } = require('./base64')
const jose = (privateKey, publicKey) => { async function encrypt(raw) {
if (!raw) throw new Error('Missing raw data.')
const buffer = Buffer.from(JSON.stringify(raw))
const encrypted = await JWE.createEncrypt(publicKey)
.update(buffer).final()
return encode(encrypted)
}
async function decrypt(encrypted) {
if (!encrypted) throw new Error('Missing encrypted data.')
const decoded = decode(encrypted)
const { payload } = await JWE.createDecrypt(privateKey)
.decrypt(decoded)
return JSON.parse(payload)
}
return { encrypt, decrypt }
}
module.exports = jose

The encrypt function JSON.stringifys the raw data then uses the publicKey provided to then encrypt it via node-jose’sJWE, and then base64 encodes the result.

The decrypt function base64 decodes the incoming data and then uses the privateKey to decrypt it, then parses the returned JSON result back into an object.

Test this as follows

// test/unit/utils/jose_spec.jsconst { expect } = require('chai')
const faker = require('faker')
const keygen = require('generate-rsa-keypair')
const { JWK } = require('node-jose')
const jose = require('../../../src/utils/jose')const makeKey = pem => JWK.asKey(pem, 'pem')describe('jose-simple', () => {
const raw = {
iss: 'test',
exp: faker.date.future().getTime(),
sub: { test: faker.lorem.words() }
}
let encrypted
let decrypted
const keys = keygen() before(async () => {
const jwKeys = await Promise.all([
makeKey(keys.private),
makeKey(keys.public)
])
const { encrypt, decrypt } = jose(...jwKeys)
encrypted = await encrypt(raw)
decrypted = await decrypt(encrypted)
})
it('encrypts', () => {
expect(encrypted).to.exist
expect(encrypted).to.be.a('string')
})
it('decrypts', () => {
expect(decrypted).to.exist
expect(decrypted).to.be.an('object')
})
it('decrypted version of encrypted is raw', () => {
expect(decrypted).to.eql(raw)
})
})

So now in your code you can still use 3rd party JWTs but then wrap them in a properly encrypted token that only someone with access to the correct private key can decrypt. You can use whatever kind of keys you like (In my test I create an RSA key pair).

Encryption ought to be simple, and widespread.

I wrote this because I found the Node Jose docs confusing, there is a lack of JOSE code examples online, and very few people seem to use it, instead mistakenly assuming that JWTs are actually secure. This is a terrible situation I wish to rectify.

The code I have provided is of course fairly trivial but if you wish to improve it, I have wrapped all this up into an actual npm library called jose-simple( Sourcecode in GitHub at github.com/davesag/jose-simple.

Update: 2018–05–10

I’ve tidyied up the example code a bit and updated the jose-simple package to version 1.0.1 to support Node 10+. The update has also been published to npm.

Update: 2018–06–04

Updated a number of dependencies and released version 1.0.2 to npm.

† Why a stateless server? That’s a topic for a whole other article, but it’s valid and increasingly common.

Like this but not a subscriber? You can support the author by joining via davesag.medium.com.

Securing  Tokens with help from JOSE (2024)

FAQs

How do I make my tokens secure? ›

Token Best Practices
  1. Keep it secret. ...
  2. Do not add sensitive data to the payload: Tokens are signed to protect against manipulation and are easily decoded. ...
  3. Give tokens an expiration: Technically, once a token is signed, it is valid forever—unless the signing key is changed or expiration explicitly set.

How do you securely store access tokens? ›

We recommend storing tokens on the server, as this offers traditional web apps the maximum level of security. If this cannot be done, you should use encrypted session cookies so the client cannot read token values.

How to secure access token in client side? ›

The OAuth Proxy decrypts the cookies and adds the token to the upstream API. The cookie attributes ensure that the browser only adds cookies to HTTPS requests, ensuring they are secure in transit. Since tokens are encrypted, they are also secured at rest. Tokens are then used to get secure access to APIs.

What is the best way to store JWT tokens? ›

To keep them secure, you should always store JWTs inside an HttpOnly cookie. This is a special kind of cookie that's only sent in HTTP requests to the server. It's never accessible (both for reading and writing) from JavaScript running in the browser.

How do you check if a token is safe? ›

To verify the legitimacy of a coin, start with the most easily accessible methods, such as Google searches and Twitter, which include researching the coin and its team, checking for any red flags or warning signs, and looking for reliable sources of information, such as official websites, news articles, and verified ...

What does secure token do? ›

A security token is a peripheral device used to gain access to an electronically restricted resource. The token is used in addition to, or in place of, a password.

Is it safe to save tokens in local storage? ›

On the downside, localStorage is potentially vulnerable to cross-site scripting (XSS) attacks. If an attacker can inject malicious JavaScript into a webpage, they can steal an access token in localStorage. Also, unlike cookies, localStorage doesn't provide secure attributes that you can set to block attacks.

What is the best way to store token in browser? ›

What are the best practices for storing and sending JWT tokens in the browser?
  1. Use HTTPS.
  2. Choose the right storage option.
  3. Use the HttpOnly and Secure flags.
  4. Use short-lived tokens.
  5. Validate and decode your tokens.
  6. Here's what else to consider.
Mar 21, 2023

Where should I store authentication token? ›

Auth0 recommends storing tokens in browser memory as the most secure option. Using Web Workers to handle the transmission and storage of tokens is the best way to protect the tokens, as Web Workers run in a separate global scope than the rest of the application.

How secure is token authentication? ›

Advantages and Disadvantages of Token-based Authentication

Tokens provide a far more secure method for user authentication because they are self-contained, and only the server that created the token can verify it.

Is it safe to store tokens in cookies? ›

Cookies are a good option for storing tokens because they are automatically sent with every request, making it easy to authenticate requests without having to manually include the token in each request.

How do I create a security token for a user? ›

How to Generate a Security Token in Salesforce
  1. Log in to your Salesforce account. ...
  2. Click the profile avatar and choose Settings.
  3. Select My Personal Information → Reset My Security Token.
  4. Check your email for the security token.
Mar 18, 2024

How do I make my JWT token more secure? ›

Another way to secure your JWT tokens is to use short expiration times. This means that your tokens will become invalid after a certain period of time, reducing the window of opportunity for attackers to use them. You can set the expiration time in the payload of your JWT token, using the exp claim.

Is JWT secure enough? ›

Using SSL/TLS (Secure Sockets Layer/Transport Layer Security) is highly recommended when working with JSON Web Tokens (JWT) or any other sensitive data in transit. While JWTs themselves provide a mechanism for securing the token data, they do not inherently protect against network-level attacks or eavesdropping.

How do I secure my JWT in local storage? ›

Best Practices for securely storing JSON web tokens
  1. Encryption: If you choose to use LocalStorage, encrypt the JWT tokens before storing them to enhance their security. ...
  2. Short validity: Set a short lifespan for JWT tokens to minimize the window of opportunity for attackers to exploit stolen tokens.
May 9, 2023

How do I make my refresh token secure? ›

To ensure a higher level of security, storing tokens in server-side storage allows you to encrypt data at rest. This ensures that even if the storage mechanism is compromised the refresh tokens are safe and secure.

What makes a token not a security? ›

One critical distinction of utility tokens is their non-investment nature. Unlike security tokens, utility tokens are not sold with the expectation of profit. Instead, users acquire them to engage with the functionalities offered by the associated blockchain project.

How do I make my crypto more secure? ›

Crypto Safety: The Basics of Protecting Your Crypto
  1. Use two-factor authentication (2FA) on your wallets and exchange.
  2. Withdraw your crypto from your exchange to a wallet.
  3. Write down the seed words for your wallet on a piece of paper, but store it safely.
  4. Use strong passwords every time.
Jun 6, 2024

How do I lock my token? ›

The Token Lock Process: A Step-by-Step Guide

Specify the Token you wish to Lock and to which wallet you wish to lock it to: Now you need to choose which type of lock you want, either a normal lock (The token lock is released in just one date) or vested lock (The token is released over multiple unlocks).

Top Articles
How much have I spent on LoL?
5 Functions of Management by Henri Fayol
7 C's of Communication | The Effective Communication Checklist
What to Serve with Lasagna (80+ side dishes and wine pairings)
Horoscopes and Astrology by Yasmin Boland - Yahoo Lifestyle
My Boyfriend Has No Money And I Pay For Everything
No Hard Feelings Showtimes Near Metropolitan Fiesta 5 Theatre
360 Training Alcohol Final Exam Answers
O'reilly's In Monroe Georgia
The Best English Movie Theaters In Germany [Ultimate Guide]
How Far Is Chattanooga From Here
Irving Hac
B67 Bus Time
Santa Clara Valley Medical Center Medical Records
Painting Jobs Craigslist
Minecraft Jar Google Drive
Moviesda3.Com
Roll Out Gutter Extensions Lowe's
10 Fun Things to Do in Elk Grove, CA | Explore Elk Grove
Kcwi Tv Schedule
Unionjobsclearinghouse
Dragger Games For The Brain
Brbl Barber Shop
Cowboy Pozisyon
The Collective - Upscale Downtown Milwaukee Hair Salon
3 Ways to Drive Employee Engagement with Recognition Programs | UKG
Shiny Flower Belinda
100 Gorgeous Princess Names: With Inspiring Meanings
Meowiarty Puzzle
Vadoc Gtlvisitme App
Citibank Branch Locations In Orlando Florida
Little Caesars Saul Kleinfeld
Ixlggusd
Slv Fed Routing Number
Gyeon Jahee
Http://N14.Ultipro.com
Ducky Mcshweeney's Reviews
New Gold Lee
Encompass.myisolved
World Social Protection Report 2024-26: Universal social protection for climate action and a just transition
Davis Fire Friday live updates: Community meeting set for 7 p.m. with Lombardo
Miami Vice turns 40: A look back at the iconic series
Coroner Photos Timothy Treadwell
Denise Monello Obituary
Peace Sign Drawing Reference
Academic Notice and Subject to Dismissal
Stosh's Kolaches Photos
Shiftselect Carolinas
A Snowy Day In Oakland Showtimes Near Maya Pittsburg Cinemas
Game Akin To Bingo Nyt
Immobiliare di Felice| Appartamento | Appartamento in vendita Porto San
Elizabethtown Mesothelioma Legal Question
Latest Posts
Article information

Author: Madonna Wisozk

Last Updated:

Views: 5839

Rating: 4.8 / 5 (48 voted)

Reviews: 95% of readers found this page helpful

Author information

Name: Madonna Wisozk

Birthday: 2001-02-23

Address: 656 Gerhold Summit, Sidneyberg, FL 78179-2512

Phone: +6742282696652

Job: Customer Banking Liaison

Hobby: Flower arranging, Yo-yoing, Tai chi, Rowing, Macrame, Urban exploration, Knife making

Introduction: My name is Madonna Wisozk, I am a attractive, healthy, thoughtful, faithful, open, vivacious, zany person who loves writing and wants to share my knowledge and understanding with you.