JavaScript Agent Integration¶
This integration mode provides fully invisible, behind-the-scenes device identity verification. The JavaScript agent automatically starts a Relock gateway session and rotates the cryptographic keys during session lifetime.
Key rotation is enforced both at the beginning of the session and periodically throughout its lifetime. Rotation at session start is enforced and required, durring the session liftime rekeying is enforced at fixed time intervals or even at every single request, depending on your security requirements. The frequency is configurable, ranging from every few minutes to each request for maximum assurance.
Because this process runs silently in the background, users experience no interruptions or redirects, while the application maintains continuous device verification and strong session protection.
Reverse Proxy Setup (Required)¶
Your reverse proxy must route all requests from /relock/* to
https://relock.host/ and include the header X-Key-Wildcard containing
your unique client UUID. This UUID binds the requests to your specific gateway
and ensures that Relock can properly verify the origin of each request.
For security and reliability, make sure this header is added automatically to every forwarded request. Without it, the Relock gateway will reject or treat the session as untrusted.
Detailed reverse proxy configuration examples (for both Nginx and Apache) are provided earlier in this document. Use those examples as a reference to set up your proxy correctly.
HTTP Strict Transport Security (HSTS)¶
Relock depends on the integrity of the browser’s origin enforcement. To prevent downgrade or redirection attacks, it is strongly recommended to enable HTTP Strict Transport Security (HSTS) on all domains that integrate Relock.
HSTS instructs browsers to:
Always connect via HTTPS (never downgrade to HTTP).
Cache this rule for a specified duration.
Refuse connections with invalid or self-signed certificates.
Recommended configuration:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Where:
max-age=63072000enforces HTTPS for two years.includeSubDomainsapplies the rule to all subdomains.preloadallows the domain to be added to browser preload lists for protection from the very first request.
Using HSTS ensures that Relock’s origin-bound tokens and cryptographic proofs cannot be intercepted or stripped of TLS protection by downgrade attacks, DNS manipulation, or man-in-the-middle proxies.
Relock.js Library Integration¶
In this integration option, a lightweight Relock JavaScript SDK (``relock.js``) is required. The JavaScript file should be included in the <head> section of every page generated by your web application server to ensure it runs consistently.
<script src="/relock/relock.js" async></script>
The behavior of the Relock agent differs slightly between Multi-Page Applications (MPAs) and Single-Page Applications (SPAs). The distinction is important, because MPAs automatically reload the <head> on each navigation, while SPAs typically render views dynamically without reloading the entire page.
The following sections explain the differences and provide guidance for integrating Relock depending on your application’s architecture.
Content Security Policy (CSP) with Nonces¶
To keep the Relock JavaScript agent safe from injection and supply-chain risks, we strongly recommend deploying a CSP with per-request nonces. This allows the browser to run only scripts you explicitly authorize for that page load, and it pairs well with Relock’s request-level cryptography.
Block inline/script-injected code (XSS) while allowing your vetted scripts.
Authorize relock.js via a fresh nonce on every response.
Remove ‘unsafe-inline’ and avoid inline event handlers.
Recommended CSP (header)¶
Set a CSP header on every HTML response with a unique, unpredictable nonce:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM_NONCE}' 'strict-dynamic';
connect-src 'self' https://relock.host;
style-src 'self';
img-src 'self' data:;
frame-ancestors 'none';
base-uri 'none';
object-src 'none';
upgrade-insecure-requests;
require-trusted-types-for 'script';
Notes:
- Replace {RANDOM_NONCE} with a cryptographically strong random value per response.
- 'strict-dynamic' lets nonce-approved scripts load other same-origin scripts without expanding the attack surface.
- Add other endpoints you call to connect-src (e.g., your own /relock/* if reverse-proxied on your domain).
Generating the nonce (server examples)¶
Node / Express
import crypto from "node:crypto";
export function csp(req, res, next) {
const nonce = crypto.randomBytes(16).toString("base64");
res.locals.cspNonce = nonce;
res.setHeader("Content-Security-Policy",
[
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
"connect-src 'self' https://relock.host",
"style-src 'self'",
"img-src 'self' data:",
"frame-ancestors 'none'",
"base-uri 'none'",
"object-src 'none'",
"upgrade-insecure-requests",
"require-trusted-types-for 'script'"
].join("; ")
);
next();
}
Then in your template:
<script src="/relock/relock.js" nonce="{{cspNonce}}" async></script>
Python / Flask
import os, base64
from flask import g, make_response, render_template
def make_nonce():
return base64.b64encode(os.urandom(16)).decode()
@app.after_request
def add_csp(response):
nonce = getattr(g, "csp_nonce", None) or make_nonce()
g.csp_nonce = nonce
csp = (
"default-src 'self'; "
f"script-src 'self' 'nonce-{nonce}' 'strict-dynamic'; "
"connect-src 'self' https://relock.host; "
"style-src 'self'; img-src 'self' data:; "
"frame-ancestors 'none'; base-uri 'none'; object-src 'none'; "
"upgrade-insecure-requests; require-trusted-types-for 'script';"
)
response.headers["Content-Security-Policy"] = csp
return response
And in Jinja:
<script src="/relock/relock.js" nonce="{{ g.csp_nonce }}" async></script>
SPA considerations¶
Make sure your framework/template passes the nonce to any runtime that adds scripts (e.g., hydration, lazy bundles) or adopt 'strict-dynamic' so nonce-blessed entry points can load their children.
Avoid dynamically injecting script text; prefer module/script URLs under your origin.
If you use Trusted Types, create a simple policy for framework bootstraps and set require-trusted-types-for 'script'.
Subresource Integrity (SRI)¶
If any script is loaded from another origin, add SRI hashes in addition to CSP nonces.
If you reverse-proxy Relock under your origin (recommended), SRI is optional because script-src 'self' 'nonce-...' already constrains execution to your assets.
Common pitfalls checklist¶
Do not send different nonces in the header and in
<script>tags.Do not include
'unsafe-inline'or'unsafe-eval'inscript-src.Remove inline handlers (
onclick=etc.) and replace with delegated listeners.Keep
connect-srctight; include only your API hosts andhttps://relock.host(or your proxied path).Use HSTS (with preload) and disallow mixed content to preserve origin guarantees.
Why this matters¶
Relock blocks session hijacking and replay, but same-origin script execution (XSS, malicious extension injecting into your DOM, or compromised third-party JS) could still invoke the legitimate agent. A strict CSP with nonces materially reduces that risk by ensuring only the scripts you explicitly bless per request can run, keeping the Relock control plane and SOTT derivations under your code’s control.
Subresource Integrity (SRI)¶
Relock.js should always be loaded from your own application domain
(e.g. https://example.com/relock/relock.js), never from a third-party origin. This ensures that the script is covered by SameSite browser protections and cannot be substituted cross-site.
For additional assurance, Relock Cloud provides an official SRI hash in the Admin Panel. You should copy this hash into your script tag so that the browser verifies the file contents even when served through your own reverse proxy:
<script src="/relock/relock.js"
integrity="sha384-..."
crossorigin="anonymous"
nonce="{{RANDOM_NONCE}}"></script>
MPA Web Applications¶
For non-SPA (Multi-Page Application) web applications, including relock.js in the <head> section ensures the Relock agent is triggered automatically on every page load. The agent starts session validation and generates a fresh token and signature for each page relaod.
On every page reload, new SamesSite ‘lax’ non-HTTP-only cookies are generated:
X-Key-Token→ A fresh, hex-encoded token derived from secret material.X-Key-Signature→ An hex-encoded Ed25519 signature of the token.
These cookies can be used for GET request–level authentication of the user’s device identity. In practice, the token and signature serve as a cryptographic proof of the browser’s identity, allowing your backend or microservices to validate that the request originates from a trusted device and legitimate browser sandbox.
Multi-Tab Browsing Support¶
Relock natively supports multi-tab browsing without compromising security. Each browser tab is enumerated with a unique tab identifier. If a new tab is opened and the identifier changes, the Relock agent automatically invokes a fresh session agreement with the gateway by default.
This ensures that:
SOTTs remain single-use and tied to the active tab context.
Tokens or session state cannot be replayed across tabs.
Race conditions between parallel requests in different tabs are avoided.
From the user’s perspective, the experience is seamless: opening new tabs continues the session, but cryptographic material is refreshed to preserve origin binding and one-time-use guarantees.
This mechanism prevents session artifacts from being replayed across tabs, ensures one-time-use guarantees remain intact, and eliminates potential race conditions between parallel requests. From the user’s perspective the experience is seamless, while security is preserved at request-level.
SPA Web Applications¶
Single-Page Applications (SPAs) update the page content dynamically without reloading the entire page. Because the <head> section is not reloaded on navigation, the Relock agent must be manually notified whenever a view change occurs. This is achieved by triggering a custom JavaScript event called X-Key-View-Change.
You can dispatch the event as follows:
let event = new CustomEvent('X-Key-View-Change',
{ bubbles: false });
window.dispatchEvent(event);
Many JavaScript frameworks allow you to handle this automatically within their routing mechanisms. For example, in Angular you can listen for route changes and dispatch the event each time a navigation occurs:
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
constructor(private router: Router) {
this.router.events
.pipe(filter(event => event instanceof NavigationEnd))
.subscribe(() => {
let event = new CustomEvent('X-Key-View-Change',
{ bubbles: false });
window.dispatchEvent(event);
});
}
Key Established Event¶
Each time the view changes, Relock.js agent accesses the secret material and verifies the session state and if needed rotatates the symmetric key. The key rotation is not enforced always when the X-Key-View-Change event is fired except the session start. This operation usually takes a few milliseconds as it retrieves the secret material from storage and decrypts/rotates it.
As Relock.js agent is asynchronus and if it started before the the DOMContentLoaded event the keys are typically available in browser memory before the web page is entirely processed. Depending on the user device computing power.
Once the JavaScript agent triggers the X-Key-Established event, authentication token generation becomes available (during this event, cookies for GET requests are also created).
window.addEventListener('X-Key-Established', async function (event) {
let token = await window.relock.token();
let signature = await window.relock.sign(token);
});
In addition, the Relock.js agent fires the X-Key-Rekeying-Done event whenever the re-keying or re-initialization process has finished.
window.addEventListener('X-Key-Rekeying-Done', function (event) {
console.log(event.detail);
});
Both events have the same JSON payload containing information about the device authentication process and the resulting state. The Key-Established event fires only when the state of verification is successful.
The event payload has a major key difference between the fresh and the state attributes:
fresh→trueonly if the device has never interacted with the gateway before or storage was completly cleared.
state→trueif the gateway has decided to enroll new secret material and perform a fresh key agreement (e.g., after reset).
Established Event Payload¶
Example JSON Payload:
{"fresh": false, // First-time interaction with the gateway?
"valid": true // Was the secret validation successful?
"state": false, // Was a new key agreement performed?
"owner": false, // Device has assigned owner?
"network": false, // Network location change?
"authenticated": false, // Is the user authenticated?
"status": "OK", // Human-readable status message
"code": 200} // Operation status code
Attributes explained
Field |
Description |
|---|---|
|
Boolean. Indicates if the user is authenticated in the current session. |
|
Integer. HTTP-like status code of the operation. |
|
Boolean. |
|
Boolean. |
|
Boolean. |
|
Boolean. |
|
String. Human-readable description of the operation result. |
|
Boolean. |
Backend-Side Request Verification¶
The web application backend can verify each incoming request using browser delivered cryptographic proofs, with the level of verification depending on the accepted risk of the request type.
Typically, GET requests that do not change the application state do not need to be strongly authenticated — in such cases, proof of possession is sufficient. However, all POST requests, as well as GET requests with path parameters that could alter or expose web app data, should be validated by both token
and signature verification.
Below are examples of basic and advanced request-level authentication approaches.
Basic Request Authentication¶
Non-critical requests in a web application can be authenticated in a simple way by verifying the user device’s signature. During secret material enrollment, the device public key becomes available as a property of the window.relock object.
window.addEventListener('X-Key-Established', function (event) {
console.log(window.relock.public);
});
The device public key is hex-encoded and remains unchanged unless the secret material used for token generation is replaced. This public key can be securely stored by the backend application and used to verify signatures on each request (verification can be performed within milliseconds).
Security note:
If basic-level authentication is used, remember to implement token replay prevention. Replay prevention ensures that an attacker cannot capture a valid token from one request and reuse it to impersonate the client in subsequent requests.
Your backend application should reject any request that attempts to reuse a token that has already been validated within the current session.
Without replay protection, even a simple interception of traffic (for example, through a compromised network node) could allow an attacker to repeatedly replay the same token and gain unauthorized access despite the presence of signature verification.
Critical Request Authentication¶
All critical requests to your backend should be authenticated not only through a simple signature check, but also by validating that the token is derived from the current version of the secret material.
The Relock gateway includes built-in token replay prevention by default. To verify both the token and its signature, your backend application must make an API call to the Relock confirmation endpoint.
Verification Endpoint:
POST /relock/confirm
If the request returns 200 OK or 407 Proxy Authentication Required,
the request can safely proceed. If it returns any other status,
the request should be rejected and the authentication flow restarted.
JavaScript Example:
let token = await window.relock.token();
let signature = await window.relock.sign(token);
let xsid = ""; // session ID, available as HTTP-only cookie
// included to the request send to the backend
return await fetch("/relock/confirm", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
"X-Key-Token": token.hexlify(),
"X-Key-Signature": signature.hexlify(),
"X-Key-Session": xsid
})
}).then((res) => res.json());
Relock Gateway Status Codes¶
Relock Gateway Status Codes
Status Code |
Description |
|---|---|
|
The token and signature are valid, and the user is signed in to the Relock gateway. The cryptographic assets are confirmed, and the gateway considers the session authenticated. |
|
The token and signature are valid, but the user is not currently signed in to the Relock gateway. This confirms that cryptographic verification succeeded, but indicates the web application has not yet invoked the Relock sign-in flow, so the gateway has no active authentication state. |
|
The provided token does not match the server’s current version of the secret material. This usually means the client is using outdated or revoked secret material to derive the token. The request must be rejected immediately. |
|
The token signature is invalid. This means the token may have been tampered with or generated by an unauthorized client. The request must be rejected immediately. |
|
The confirmation call does not contain a valid token and/or signature. This occurs when required authentication fields are missing or malformed. Every confirmation request must include both a valid token and signature. |
|
The gateway is awaiting re-keying confirmation and cannot currently verify the data. This occurs when the secret material is in the process of being rotated. The browser must complete the re-keying process before new tokens can be generated. |
|
The device data does not exist in the current session. This may happen if the session has expired, if the device is unknown to the gateway, or if the session state has been reset. The client must re-authenticate to continue. |
|
The session no longer exists. This indicates the session has been explicitly terminated or has permanently expired. The client must establish a new session and repeat the authentication process. |
Login and Logout a User in the Gateway¶
Signing a user in to the Relock gateway simplifies the authentication flow. After a successful login, future sessions can automatically recognize a returning user (the owner of the device). This feature helps reduce friction and minimize the cost of repeated MFA (Multi-Factor Authentication) prompts.
The sign-in and sign-out API routes can be called from either the backend or frontend, depending on the web application’s architecture. However, backend API calls are generally recommended, as handling sensitive user data directly in the frontend may violate data privacy and compliance requirements.
API Routes:
/relock/login → Call after successful credential verification (e.g., password).
/relock/logout → Call when a user signs out (no user ID required).
Example: ``/relock/login``
async login(user = String(),
email = String(),
xsid = String()) // session ID, available as an HTTP-only cookie
// included in the request sent to the backend
{
let token = await window.relock.token();
let signature = await window.relock.sign(token);
return await fetch("/relock/login", {
method: "POST",
credentials: 'include',
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
"X-Key-Token": token.hexlify(),
"X-Key-Signature": signature.hexlify(),
"X-Key-Session": xsid,
"user": user,
"email": email
})
}).then((res) => res.json());
}
Zero-Trust Remember-Me¶
An additional benefit of having a signed-in user is the ability to implement a secure “remember me” feature. If the web application allows automatic logins for returning users, cookie-based login can be enhanced with device-level cryptographic proof of identity.
In this scenario, the backend application should verify both the token’s authenticity and the device signature. If the remember me cookie is valid, the user can be signed in without requiring a full login flow.
The backend should also track the time of the last session and decide whether to prompt for a password (or MFA) or to allow seamless auto-login.
The gateway can also provide the information about remote address of user device and prevent automatic login if network location changes.
Example Implementation:
window.addEventListener('X-Key-Established', function (event) {
if (event.detail.authenticated === false) {
if (event.detail.fresh === true) {
document.location = '/sign_on';
} else {
if (event.detail.state === true) {
document.location = '/login_input_and_mfa';
} else if (event.detail.owner && event.detail.network) {
document.location = '/autologin';
} else if (event.detail.owner) {
document.location = '/password';
}
}
} else {
document.location = '/homepage';
}
});
Best Practices for Secure Deployment¶
Relock strengthens session security by design, but its protection is only as strong as the surrounding environment. Apply the following hardening measures when integrating Relock into your applications:
HTTP Strict Transport Security (HSTS) Force all traffic to use HTTPS, preventing downgrade and interception.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Content Security Policy (CSP) with nonces Restrict JavaScript execution to only trusted scripts with a fresh per-request nonce. This prevents XSS and keeps control of the Relock agent.
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM_NONCE}' 'strict-dynamic';
connect-src 'self' https://relock.host;
style-src 'self';
img-src 'self' data:;
frame-ancestors 'none';
base-uri 'none';
object-src 'none';
upgrade-insecure-requests;
require-trusted-types-for 'script'
Relock.js loading (SameSite only) + SRI
Always serve relock.js from your own application origin (e.g., /relock/relock.js) via your reverse proxy. Do not load it from third-party domains or CDNs. For supply-chain assurance, copy the official SRI hash from the Relock Cloud Admin Panel into your script tag and include your CSP nonce:
<script src="/relock/relock.js"
integrity="sha384-..."
crossorigin="anonymous"
nonce="{{RANDOM_NONCE}}"
async></script>
Reverse proxy header hygiene
Inject trust headers (e.g., X-Key-Wildcard) only at your trusted proxy; strip any inbound copies from client requests. Optionally enforce allow-listed proxy IPs or mTLS between proxy ↔ gateway.
SameSite and secure cookies
Relock’s per-request SOTT, when bound to (method, path, intence), provides strong CSRF protection by making forged cross-site requests unverifiable. Still set cookies as Secure and HttpOnly with SameSite=Lax or SameSite=Strict for defense in depth, and keep classic CSRF tokens for legacy form posts or endpoints that don’t yet validate SOTTs.
No secret logging Never log tokens, nonces, signatures, or Relock headers. Emit only structured security events (codes, reasons, redacted device/session IDs).
Together, these measures reinforce Relock’s request-level proofs so origin binding, replay protection, and compromise detection are not undermined by common web application misconfigurations.