Backend integration¶
Integrating an application server with the Relock service primarily involves establishing TCP communication and defining specific HTTP routes to facilitate interactions with the JavaScript client.
Request validation flow¶
The web application server acts as essential middleware, orchestrating all interactions with the Relock service. This architecture ensures the JavaScript client communicates exclusively through the web application’s controlled and secure interface, never directly with the Relock service.
There are two primary request flows for credential verification: one for persistent credentials and another for ephemeral credentials. This section details the persistent credential verification flow. The ephemeral credential flow is simpler and generally involves the web application acting as a transparent proxy for encrypted messages directly exchanged between the JavaScript client and the Relock service.
The following sequence diagram illustrates a typical request lifecycle for persistent credential verification among the web app server, TCP client, and Relock service.

Participants¶
Web app server: The application’s backend, acting as middleware. It receives HTTP requests from the browser and uses its internal TCP client to communicate with the Relock service.
TCP client: A component within the web app server responsible for establishing and managing TCP connections with the Relock service.
Relock service: The dedicated backend service for cryptographic key rotation, robust session management, and access control.
Sequence of events¶
The typical request lifecycle for persistent credential verification can be broadly divided into three phases:
Initial request and validation: The flow begins with the web app server receiving an HTTP request from the user’s browser. The web app server then processes this request and uses its internal TCP client to forward relevant user and device information (including cookies) to the Relock service via TCP sockets. The Relock service performs essential authentication and validation, and responds with a status code, which the TCP client relays back to the web app server. This initial exchange determines whether the request is valid and permitted to proceed.
Conditional processing: If the Relock service returns a “200 OK” status, the flow proceeds to the web app server’s main application logic. The web app server processes the request, retrieving content or proceeding with its core operation. This conditional branch ensures that only authenticated and validated requests are allowed to proceed within the web application.
Request finalization and session update: Upon completion of the request processing by the web app server, a finalization (or “close”) request is initiated. This signal travels from the web app server, through its TCP client, to the Relock service. In response, the Relock service provides updated cookie content, which the TCP client relays back to the web app server. The web app server must then include these updated cookies in its HTTP response to the browser to maintain session state and facilitate subsequent interactions.
TCP client¶
Connections between the application server and the Relock service are established via TCP sockets. Since all stored data is end-to-end encrypted, additional TLS encryption for the TCP client connection is not required, simplifying implementation while maintaining confidentiality and integrity.
The Relock service employs a lightweight communication approach: no passwords or initial handshakes are required. Communication can begin immediately, allowing for straightforward integration even with basic socket implementations.
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
conn.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
conn.connect(('172.17.0.4',8111))
conn.sendall(b'\x00\x00\x17{"route": "members"}')
conn.recvmsg(1024)
For optimal performance and to reduce overhead, it is strongly recommended to implement or use a TCP client that supports connection pooling. This maintains a pool of reusable connections, eliminating the need to repeatedly establish and close sockets.
Service discovery and load balancing¶
For optimal performance and resilience in a distributed environment, the TCP client can discover and utilize multiple Relock service instances.
To retrieve the current list of active Relock service instances in the ring, the TCP client must send a message to the members
route:
{"route": "members"}
To initiate this discovery process, the TCP client only needs the address of one initial Relock service instance. Upon sending a request to the members
route, the client will receive a complete list of all available server addresses within the ring, enabling it to establish multiple connections as needed.
{"ef4b1fad-68e7-4752-9225-12bea43d8d04":
{"addr": "172.17.0.4", "host": "0.0.0.0", "port": 8111, "id": "ef4b1fad-68e7-4752-9225-12bea43d8d04"},
"659d4dea-7dd6-415f-9e2c-db16dec1db43":
{"addr": "172.17.0.5", "host": "0.0.0.0", "port": 8111, "id": "659d4dea-7dd6-415f-9e2c-db16dec1db43"}}
It is recommended to implement a basic load balancing strategy within the TCP client to optimize communication across these multiple Relock service instances. A simple round-robin approach, where each outgoing request is sent to the next available server in a cyclical order, can significantly improve performance and reliability by distributing the load evenly and reducing bottlenecks.
Message formatting¶
All messages exchanged with the Relock service must be formatted as JSON. Each message payload must include a route
field, which specifies the Relock service route to be invoked. If the specified route is invalid or unauthorized, the socket connection will be automatically terminated.
{"route": "open",
"sid": "7ed4f5bb-cbe5-4a72-89e8-fdb0f88f7bde",
"rid": "95f5b607-5576-4211-90b6-7dcbfad259f8",
"screen": "1659286968866673430"}
With the exception of general routes, all messages must also include both a session_id
and a request_id
field. These identifiers are crucial for associating messages with the current user session and the specific request being processed by the web application.
Note
The session_id
does not need to match the web application’s internal session hash. However, it is essential that the same session_id
value is consistently used across all messages sent to the Relock service during a single session.
Message length¶
When receiving data, the socket.recv(length)
function does not guarantee that exactly length
bytes will be read. To ensure robust communication and handle common partial reads in networking, the Relock protocol includes the exact message length in the first 3 bytes of every message. The TCP client implementation must strictly adhere to this convention for both message construction and parsing.
msg = bytes()
if size := conn.recv(3):
if size := int.from_bytes(size, byteorder="big"):
while slice := conn.recv(1024):
msg += slice
if len(msg) >= size:
break
When sending data, prepend the 3-byte length prefix to the JSON message body.
if abs := len(msg).to_bytes(self._bytes, byteorder='big'):
conn.sendall(abs + msg)
Request authentication¶
The Relock service authenticates both the user and their browser for each request made to the web application, leveraging renewed symmetric cryptographic keys and device signature keys.
Before the web application server begins processing a response, it must register essential information about the request, user, and session with the Relock service. Following the application’s processing of the response, a finalization call to the Relock service is required.
Before the request¶
The “before” call is the initial request sent to the Relock service for every web application request. Its primary purpose is to initialize and validate the session and request on the Relock side. This call registers essential contextual information, including:
User metadata
Request-related data
Available cookies
Optional authentication token and device signature (if present)
It is crucial to include all cookies associated with the incoming request. While Relock-specific cookies may not exist for initial interactions, this is acceptable. Even for anonymous users, the “before” message should include the current user state to ensure proper session tracking. User-specific fields like email or user ID may be anonymized (e.g., hashed) if direct exposure is prohibited.
At this stage, the Relock service verifies the authenticity and integrity of the information received from the browser. The included Relock-specific cookies and tokens are ephemeral and single-use, strictly for validation purposes.
If the provided data is valid, the TCP response will be a JSON-formatted message containing the following key fields:
{'route': 'before',
'sid': session.sid,
'rid': request.id,
'cookies': request.cookies,
'host': request.headers.get('Host'),
'agent': request.headers.get('User-Agent'),
'addr': request.remote_addr,
'method': request.method,
'url': request.url,
'path': (path or request.path),
'user': user.get_id(),
'email': user.get_email(),
'authenticated': user.is_authenticated,
'active': user.is_active,
'anonymous': user.is_anonymous,
'X-Key-Token': request.headers.get('X-Key-Token', str()) or
request.form.get('X-Key-Token', str()) or
request.cookies.get('X-Key-Token', str()),
'X-Key-Signature': request.headers.get('X-Key-Signature', str()) or
request.form.get('X-Key-Signature', str()) or
request.cookies.get('X-Key-Signature', str())}
If the provided data is valid, the TCP response will contain a JSON-formatted message with the following fields:
xsid
: The session identifier generated by the Relock service.
screen
: A unique identifier for the browser tab (screen context).
owner
: A boolean indicating whether the user is currently assigned to the recognized device.
{"xsid": "dfc58aec21b9a3d037e03479c1b6bda9",
"screen": "f2074e78ad018dd4878709b40ec8bd30",
"owner": "false"}
If passed data are invalid or fail validation, Relock will return an integer state code. For details on interpreting these codes, refer to Status codes.
After the request¶
Once the web application server has finished processing the request, it must finalize the transaction within the Relock service. The TCP client should send an “after” call to the Relock service, providing the session ID, request ID, and the status and response code generated by the web application.
{'route': 'after',
'sid': session.sid,
'rid': request.id,
'status': response.status,
'code': response.status_code,
'host': app.config.get('SERVER_HOST')}
The Relock service will process this information and may generate appropriate service-related cookies. If the JSON message returned by the Relock service includes a cookie name and value, the web application server must include a Set-Cookie
directive in its response to the client. Conversely, if a cookie entry in the JSON contains a name but no value, the server should issue a cookie deletion directive.
{"relock": {"value": "2DP11eV00v36nAq...wn8n1GKQ+H2tlw==", "expires": 31536000.0, "max_age": 31536000.0, "path": "/", "domain": "", "secure": true, "httponly": true, "samesite": "Lax"},
"stamp": {"value": "7267e7bcb59fc9cacdd6a5eca419f6d9", "expires": 31536000.0, "max_age": 31536000.0, "path": "/", "domain": "", "secure": true, "httponly": true, "samesite": "Lax"}}
Key rotation control¶
Beyond session initialization, the web application retains full control over the key rotation process. During the processing of an HTTP request on a specific route (e.g., /user/account
), the application can initiate a call to the Relock service to generate a key rotation nonce. Once this nonce is registered by the Relock service, it will be consumed during the subsequent key validation request, thereby initiating the symmetric key rotation process.
Key rotation is triggered after a nonce TCP call, as the service will consume the registered nonce prior to validating the incoming token.
{"route": "nonce"}
This “nonce” request instructs the Relock service to generate a rotation nonce and sign it using the private key associated with the currently used device (i.e., the user’s browser).
{"nonce": "10f00e805ddc39470579a5a9a3c6500c",
"signature": "43d28e7b4f1cbaabcf7202cdb06f6b966456401000f1d9715857da4c22f3f32c910d41356debdc368c77a53cee990cfcec4e4e53e2225bcabcfa7b1ea7552a0b"}
If the web application is a template-based system, the nonce can be embedded in the <meta> section of the generated HTML response.
<meta name="x-key-nonce" content="10f00e805ddc39470579a5a9a3c6500c" />
<meta name="x-key-signature" content="43d28e7b4f1cbaabcf7202cdb06f6b966456401000f1d9715857da4c22f3f32c910d41356debdc368c77a53cee990cfcec4e4e53e2225bcabcfa7b1ea7552a0b" />
Key rotation can be triggered with every request or only on specific routes, depending on the application’s security requirements and performance considerations.
Required HTTP routes¶
The web application must implement these HTTP routes to facilitate communication between the JavaScript client and the Relock service.
/relock/relock.js¶
This route is responsible for serving the client-side Relock JavaScript library. The web application server should respond to requests for this URL by delivering the relock.js
file, which is then embedded in the browser via an HTML <script>
tag. This ensures that the client has the necessary code to perform cryptographic operations, generate tokens, and interact with the server for authentication and session management.
<script src="/relock/relock.js" nonce="bEbGDRxhjvw=" defer></script>
The server’s response to this route should be the JavaScript file content with a Content-Type: application/javascript
header. This route typically doesn’t involve direct communication with the Relock service via the TCP client, as it serves static content.
/relock/open¶
This route is invoked by client-side JavaScript using navigator.sendBeacon
when the user opens a new browser tab. For each new tab, a unique relock.screen
hash (a fresh, random byte sequence) is generated. The JavaScript transmits browser-specific data, including origin
, path
, the relock.screen
ID, the current Relock session ID (xsid
), and the server-side screen ID.
tab = new FormData()
tab.append('origin', document.location.origin)
tab.append('path', document.location.pathname)
tab.append('screen', relock.screen)
tab.append('xsid', relock.xsid)
tab.append('server', server.screen)
navigator.sendBeacon('/relock/open', tab)
This information enables the Relock service to manage the multi-tab environment and enforce new key agreements for each new browser tab. The data collected from application-side variables should be transmitted to the Relock service via the TCP client. This route typically doesn’t return any payload to the JavaScript client and responds with a “204 No Content” HTTP status.
/relock/close¶
This route is invoked by client-side JavaScript when the browser purges the content of a tab (e.g., on tab close or document.location
change). Before unloading, JavaScript sends a beacon to the server.
unload = new FormData()
unload.append('screen', relock.screen)
unload.append('origin', document.location.origin)
unload.append('path', document.location.pathname)
navigator.sendBeacon('/relock/close', unload)
In addition to location.origin
and location.pathname
, the close request informs the server of the closed screen ID (browser tab identification hash). This data should be passed through the TCP client to the Relock service. This “purge” request enables the Relock service to manage the multi-tab environment and provides the ability to terminate the session when all browser tabs have been closed.
/relock/clean¶
This route is typically invoked by the application to initiate a client-side cleanup. The server’s response to this route should be an HTML page containing JavaScript that clears specific Relock-related items from the browser’s local and session storage, followed by a redirect.
'<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">' + \
'<script type="text/javascript" nonce="'+ getattr(request, '__nonce', str()) +'">' + \
'localStorage.removeItem("signature");' + \
'localStorage.removeItem("tesseract");' + \
'localStorage.removeItem("server");' + \
'localStorage.removeItem("'+ response.get('stamp', 'stamp') +'");' + \
'localStorage.removeItem("client");' + \
'localStorage.removeItem("xsid");' + \
'sessionStorage.removeItem("'+ response.get('cookie', 'relock') +'");' + \
'sessionStorage.removeItem("screen");' + \
'setTimeout(() => {' + \
'document.location=\''+ url_for('index.index') + '\';' + \
'}, 500);' + \
'</script></head><body></body></html>'
/relock/clear¶
This route is invoked by JavaScript when a key agreement fails. When triggered, it instructs the server to delete all device-related information from server-side storage. The route is called without any parameters. Once the request is finalized and the server responds, all corresponding client-side data are also purged.
Note
The Relock service responds to the clear route with an empty response. The JavaScript client doesn’t expect any data from the server. Invoking this route via the TCP client triggers a data purge on the server. Upon completion, client-side storage in the browser is automatically cleared.
/relock/exchange¶
This route facilitates the primary cryptographic exchange between the JavaScript client and the Relock service (orchestrated by the web app server) for establishing or continuously verifying a user session. It is typically invoked by client-side JavaScript using the Fetch API, sending ephemeral authentication tokens and signatures.
let token = window.relock.token();
let signature = window.relock.sign(token);
fetch('/relock/exchange', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Key-Token': token.hexlify(),
'X-Key-Signature': signature.hexlify(),
'X-Key-Time': Math.floor(Date.now() / 1000),
},
credentials: 'include',
body: JSON.stringify({ /* additional context */ }),
});
The web app server receives these requests, extracts the Relock-specific headers and body, and forwards relevant data (including user and device info, cookies, token, and signature) to the Relock service via its TCP client. The Relock service performs crucial authentication and validation. In response, the Relock service may return updated session identifiers (like xsid
, screen
), or new cookie content. The web app server then relays this information back to the JavaScript client in its HTTP response, often via Set-Cookie
headers or a JSON payload.
/relock/validate¶
This route is invoked by client-side JavaScript to explicitly validate a cryptographic token with the Relock service, typically as part of a challenge-response protocol or after a key rotation nonce has been issued. This step confirms the client’s ability to access the shared secret and generate valid proof.
let validationToken = window.relock.validate(someToken); // 'someToken' might be derived from a nonce
fetch('/relock/validate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: 'include',
body: JSON.stringify({
token: validationToken.hexlify(), // Or a derived proof
xsid: window.relock.xsid,
screen: window.relock.screen
}),
});
The web app server receives the validation request and transmits the provided token along with current session context to the Relock service via the TCP client. The Relock service processes this validation request, consuming any registered nonces and verifying the proof against its internal state. A successful response indicates the validity of the client’s proof, potentially leading to further session updates or key rotations. An unsuccessful response (e.g., a 417 Expectation Failed status) signals a validation failure, often indicating a security threat. The web app server relays the Relock service’s response to the client.