A zero-dependency library that ties an obfuscated API key to the specific CodePen pen it was generated for. Copy the encoded token elsewhere — it decodes to nothing.
CodePen is a popular tool for sharing front-end demos. A common workflow: build a demo calling a third-party API (OpenAI, Google Maps, weather services). The problem — CodePen pens are public. Embed your real API key and anyone who views the pen can read and steal it.
The correct long-term answer is a server-side API proxy. But setting one up is overkill for a quick demo, a classroom exercise, or a proof-of-concept. SaltyKeys.js fills that gap by encoding the key in a token that only works on the pen it was built for.
Context-bound
The token embeds the pen's ID as a salt. A copied token can't decode anywhere else.
Zero dependencies
A single static class. No build step, no npm install, paste the source and go.
Honest about limits
Obfuscation only — not encryption. The docs say so clearly. Don't use this in production.
getPenId() parses window.location.href against the CodePen URL
pattern. Inside an iframe embed, it falls back to the injected
<link rel="canonical">, which always points to the canonical pen URL.
Key generation happens once, in the browser console on the saved pen. The raw key never appears in the pen's source.
SaltyKeys.generateSaltedKey('your-api-key') in the consoleSaltyKeys.getApiKey(SALTED)Every token bakes in four components:
Combined, base64-encoded, then reversed. Cheap visual obfuscation on top of context-binding.
At runtime, getApiKey() reverses the token, base64-decodes it, splits off the
trailing nonce, timestamp, and pen ID, then compares the embedded pen ID against
getPenId().
null, logs a
warningKeys containing :
characters are handled correctly — only the last three segments (nonce, timestamp, pen ID) are
popped; everything before is rejoined as the key.
Encode once offline, decode at runtime on the correct pen.
Encoding (run once in the console)
Decoding (runs at runtime in the pen)
All methods are static — no instantiation needed.
SaltyKeys.configure(options)
Call before any other method to override defaults.
| Option | Type | Default | Description |
|---|---|---|---|
urlPattern |
RegExp |
CodePen regex | Pattern to extract the page ID. Must include one capture group. |
cacheEnabled |
boolean |
true |
Cache the extracted ID after the first call. |
environment |
string |
'codepen' |
Controls which warnings show when ID can't be resolved. Set to 'custom'
to suppress CodePen-specific messaging. |
// Adapt to a different environment
SaltyKeys.configure({
urlPattern: /mysite\.com\/demos\/([^?#]+)/,
environment: 'custom',
});
SaltyKeys.getPenId()
→ string | null
Extracts the page/pen ID from the current URL or canonical link.
const id = SaltyKeys.getPenId();
// → "abcXYZ" on https://codepen.io/username/pen/abcXYZ
SaltyKeys.generateSaltedKey(apiKey)
→ string | null
Encodes a real API key as a context-bound token. Run once from the console on the saved pen — do not put the raw key alongside this call in source.
// Run in the CodePen console, NOT in the pen source:
const token = SaltyKeys.generateSaltedKey('sk-my-real-api-key-abc123');
console.log(token);
// → long encoded string — copy this into your pen
SaltyKeys.getApiKey(saltedKey)
→ string | null
Decodes and returns the API key — but only if the current pen ID matches the one embedded in
the token. Returns null otherwise.
// In your pen's JS source (the token is safe to be public):
const SALTED = 'XVlBzgbaiCMRAjWwhTHctcuAx...';
const apiKey = SaltyKeys.getApiKey(SALTED);
if (apiKey) {
fetch(`https://api.example.com/data?key=${apiKey}`);
} else {
console.warn('Could not retrieve API key.');
}
SaltyKeys.js ships configured for CodePen but can be adapted to any environment where the
page has a unique, consistent identifier. Override via configure() or replace
getPenId() entirely:
Custom URL pattern
SaltyKeys.configure({
urlPattern: /demos\/([^?#]+)/,
environment: 'custom',
});
Function override
SaltyKeys.getPenId = function() {
return document
.querySelector('meta[name="demo-id"]')
?.content ?? null;
};
Be honest about what this tool does and doesn't do.
Not suitable for production. A determined attacker with basic JavaScript skills can reverse the encoding. Use a server-side API proxy for anything sensitive.
| Property | Detail |
|---|---|
| Protection level | Low — obfuscation only, not encryption |
| Attacker model | Casual viewers who read source; not determined attackers |
| Reversibility | Reversible with basic JavaScript knowledge |
| Server-side protection | None — the key reaches the browser in decoded form when getApiKey() runs
|
| Production suitability | Not suitable. Use a server-side API proxy |
The correct architecture for client-side apps that need real key protection:
| Symptom | Likely cause | Fix |
|---|---|---|
Unable to extract Pen ID |
Pen not saved, or not on CodePen | Save the pen first; or configure urlPattern for your environment |
getApiKey() returns null |
Token generated on a different pen | Regenerate the salted key on the correct pen |
Key with : characters not recovered |
Token generated before v1.2.0 | Regenerate with the current version |
| Warning banner in embed | Pen viewed via embed iframe on a non-CodePen page | Set environment: 'custom' if you control the embed page |
atob is not defined |
Non-browser environment (e.g., Node.js) | Library is browser-only by design; use test shims in the test suite |
| Browser | Minimum |
|---|---|
| Chrome | 92+ |
| Firefox | 90+ |
| Safari | 15+ |
| Edge | 92+ |
| Internet Explorer | Not supported |
Requires ES2022+ for private class fields (#),
plus btoa/atob and standard DOM APIs.
30 test cases using Node.js's built-in node:test runner — no extra
dependencies.
Covers valid and invalid key generation, colon-containing keys, ID mismatch rejection,
Unicode keys, caching behavior, and custom environment configuration.
# Run the full suite
npm test
# Or directly
node --test test/SaltyKeys.test.js
A single file, no build step, 30 tests. Read the docs or browse the source on GitHub.