Key Manager
Key Manager creates, stores and shares a unique 64-character key for each assessment. Each key is associated with a unique record ID and stored securely. So that only the people who should see an assessment's key can retrieve it.
All keys are 64 characters. It is up to the developer to parse the key however they see fit. For example:
- The first 32 characters could be used as a symmetric encryption key, and the last 32 characters could be used as an HMAC key.
- Every other character could be used as a symmetric key.
- A portion of the 64-character key could be used as an initialization vector (IV).
- A portion of the 64-character key could be hashed and stored with each record to verify the correct key was retrieved before encryping a record.
- Or whatever the developer chooses. NERCKeyManager does not know how the key will be used.
NERCKeyManager only knows the key, the assessment ID, and the user who created it. The key is never associated with a specific web service or application.
NERCKeyManager is a US-based key management solution in Seattle, WA.
Email us at support@nerckeymanager.com.
Example: using the API
const BASE_URL = 'https://yourapplication.com';
// 1. Register a new user (one time). Username and password are required,
// email is optional. Responds 201 on success, 409 if the username is taken.
await fetch(BASE_URL + '/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user: 'myUsername', email: 'me@example.com', password: 'myPassword' })
});
// 2. Sign in to get an access token
const login = await fetch(BASE_URL + '/auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user: 'myUsername', password: 'myPassword' })
});
const { accessToken } = await login.json();
const headers = {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + accessToken
};
// 3. Create a key for an assessment (you become its owner)
await fetch(BASE_URL + '/key', {
method: 'POST',
headers,
body: JSON.stringify({ assessmentID: 'assessment-123' })
});
// 4. Retrieve the key (owner or shared users only)
const res = await fetch(BASE_URL + '/key/assessment-123', { headers });
const { key } = await res.json();
// 5. Share the key with another user (owner only)
await fetch(BASE_URL + '/key/assessment-123', {
method: 'PUT',
headers,
body: JSON.stringify({
user: 'myUsername',
password: 'myPassword',
assessmentID: 'assessment-123',
updateAccess: 'otherUsername'
})
});
// 6. Revoke a user's access (owner only). The user can be a username or an email,
// so encode it for the URL. No password is needed, the token identifies the owner.
await fetch(BASE_URL + '/key/assessment-123/access/' + encodeURIComponent('otherUsername'), {
method: 'DELETE',
headers
});
Endpoint reference
| Method | Path | Auth | Request body | Success response |
|---|---|---|---|---|
| POST | /register | None | user, password, email (optional) | 201 { message } |
| POST | /auth | None | user, password | 200 { accessToken }, and sets the jwt refresh cookie |
| GET | /refresh | jwt cookie | None | 200 { accessToken } |
| GET | /logout | jwt cookie | None | 204, and clears the cookie |
| POST | /key | Bearer token | assessmentID | 201 key record |
| GET | /key/:assessmentID | Bearer token | None | 200 key record |
| PUT | /key/:assessmentID | Bearer token | user, password, assessmentID, updateAccess | 200 updated key record |
| DELETE | /key/:assessmentID/access/:user | Bearer token | None | 200 updated key record |
Key record
{
"_id": "66f3...",
"assessmentID": "assessment-123",
"key": "64 random characters",
"userId": "id of the owner",
"allowedUsers": ["otherUsername"]
}
Tokens
Send the access token on every /key request as Authorization: Bearer <accessToken>.
The token from /auth expires after 1 day. The refresh token is kept in an httpOnly
jwt cookie, so call /refresh with credentials included
(fetch(..., { credentials: 'include' })). A new access token from /refresh expires after
15 seconds. Call /logout to end the session.
Errors and status codes
| Request | Situation | Response |
|---|---|---|
Any /key request | Authorization header missing or not a Bearer token | 401, no body |
Any /key request | Access token invalid or expired | 403, no body |
POST /register | Username or password missing | 400 { message } |
POST /register | Username already taken | 409, no body |
POST /auth | Username or password missing | 400 { message } |
POST /auth | Unknown user | 401 { message } |
POST /auth | Wrong password | 401, no body |
GET /refresh | No jwt cookie | 401 { message } |
GET /refresh | Refresh token invalid | 403, no body |
GET /key/:assessmentID | You are not the owner or a shared user | 204, no body |
PUT /key/:assessmentID | updateAccess, username or password missing | 400 { message } |
PUT /key/:assessmentID | Wrong password | 204, no body |
PUT /key/:assessmentID | Assessment not found | 200 { message: 'record not found' } |
PUT /key/:assessmentID | You are not the owner | 200 { message: 'Only owners can share keys.' } |
DELETE /key/:assessmentID/access/:user | Assessment not found, or the user does not have access | 404 { message } |
DELETE /key/:assessmentID/access/:user | You are not the owner | 403 { message: 'Only owners can revoke access.' } |
POST /key | assessmentID already exists | No response is sent, so the request times out |
GET /key/:assessmentID | Assessment does not exist | No response is sent, so the request times out |
Do not rely on the status code alone. Some failed requests, such as PUT /key with an unknown username,
return 200 with a fake key like { "key": "66f3126940345643f01dd" }. Treat a response as real only if it
contains assessmentID. Also set a request timeout, because a few errors never get a response.