Verifying Tokens
API token verification is the public M2M entry point for validating a token against the IAM and checking if it is allowed to perform a given privilege.
If using the service a caller must send the raw API key in the x-api-key header and request a privilege scope through the privilege query parameter. The service then validates the token structure.
If using the library, you call verifyApiKey with the appropriate parameters, depending on your needs and use case.
Verifying a token
Using the route
The verification route is available at:
GET /api/public/verify?privilege=<privilege> HTTP/1.1
Send the token in the x-api-key header with the privilege you defined at the creation step of the token.
On success the response will be:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"ok": true,
"date": "current date",
"data": {
"name": "mytoken",
"tokenId": 12,
"userId": 42,
"createdAt": "2026-04-30T10:00:00.000Z",
"expiresAt": "2026-04-30T11:00:00.000Z",
"lastUsed": "2026-04-30T10:12:00.000Z",
"usageCount": 7,
"providedPrivilege": "verified privilege"
}
}
Failed responses
If the x-api-key header is missing or malformed, the route returns:
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
{
"ok": false,
"date": "current date",
"reason": "No api key provided"
}
If the server cannot resolve the caller IP, the route returns:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"ok": false,
"date": "current date",
"reason": "Bad Request"
}
If the query parameter does not match the allowed privilege schema, the route returns:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"ok": false,
"date": "current date",
"reason": "Bad Request"
}
The input is detected as an XSS attempt, the service bans the request:
HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8
{
"banned": true
}
The token is malformed, expired, revoked, missing, or has the wrong privilege, the route returns:
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
{
"ok": false,
"date": "current date",
"reason": "Invalid key"
}
Rate limits:
HTTP/1.1 429
Content-Type: application/json; charset=utf-8
Retry-After: number
{
"error": "Too many requests",
"retry": "number",
}
Rate limits
The route is not behind any authentication or middlewares. its protected solely by rate limiters that differentiate between "probing" (failures) and "usage" (success):
rate_limiters.apiTokensLimiters.consumptionRateLimiter- The main limiter for the failure mode. It will consume apointon Every failed attempt of verification. On success it will be restarted, which means the client, whose not been rate limited by it, could continue send valid verified requests. The limiter enforces 10 requests in a 1 minute window, that will ban for 1 hour if this threshold is reached. No consecutive attempts is provided, which means violating this rule will result in a permanent ban.rate_limiters.apiTokensLimiters.generalUnionLimiter- AburstLimiterand aslowLimiterunion limiter. Enforces no more than one request per second, and only 50 per minute. No Consecutive triggers in this limiter, triggering it again will result in a permanent ban. It is disabled by default in this endpoint and if enabled, it will run on every request and will not be restarted on successful verification.burstLimiter- Will block the client for 15 minutes.slowLimiter- Will block for 1 hour.
Using the library
When using the service as a library, you verify tokens with verifyApiKey:
import { verifyApiKey } from '@riavzon/auth'
const result = await verifyApiKey(
rawKey,
false,
'restricted',
false,
false,
ipAddress
)
if (result.ok) {
console.log(result.data.name)
}
The parameters you pass to it, controls the security and configuration of the verification process:
Parameters
| Field | Type | Description |
|---|---|---|
rawKey | string | The Raw, un hashed API token to verify |
skipCountUpdates | boolean | If true, the token is verified without incrementing usage counters usageCount and lastUsed |
providedPrivilege | demo | restricted | protected | full | custom | The privilege level that must match the stored token privilege |
byPassIpCheck | boolean | If true, the IP restriction check is skipped |
isInternalHash | boolean | If true, the input is already treated as an internal hash skipping checksum and signature verification. Defaults to false |
ipAddress | string | undefined | The caller IP address used for restriction checks |
Signature
async function verifyApiKey(
rawKey: string,
skipCountUpdates: boolean,
providedPrivilege: 'demo' | 'restricted' | 'protected' | 'full' | 'custom',
byPassIpCheck: boolean,
isInternalHash?: boolean,
ipAddress?: string
): Promise<Results<VerifySuccessResponse>>
If the verification succeeded, you get back the following object:
{
ok: true,
date: new Date().toISOString(),
data: {
name: 'mytoken',
tokenId: 12,
userId: 1234,
createdAt: "2026-04-30T10:00:00.000Z",
expiresAt: "2026-04-30T11:00:00.000Z",
lastUsed: "2026-04-30T10:12:00.000Z",
usageCount: 1234,
providedPrivilege // Validated privilege
}
}
In any case the token is invalid, the checksum is invalid, or if the token haves insufficient privileges, you will get the following response:
{
ok: false,
date: new Date().toISOString(),
reason: 'Invalid key'
}
If ip restrictions is configured the reason will be 'Invalid Host'.
When the token have an expiration date and it expired, the token will be marked as invalid, and can't be used again. In such cases the reason will be "Token expired". In any case of database errors the reason will result in 'Server error validating token.'.
When debugging or to get more information why may a verification is failing use the Logs with a level of info under the branch of branch: 'api_tokens', and type of type: 'verify'.
Verification Process
When a request is made to verify a token, either via the public API route or internally using the verifyApiKey function, the system executes a multi-step validation sequence. This process is designed to reject invalid payloads as fast as possible to save database resources, while ensuring secure, usage tracking for valid keys.
Fast Validation
Before touching the database, the system validates the structural integrity of the raw API key.
- Unless the token is explicitly flagged as an internal hash (
isInternalHash), it splits the key into its three components: the prefix, the random payload, and the checksum. - If any component is missing, it is immediately rejected.
- The system recalculates the expected checksum from the random payload and performs a timing comparison against the provided checksum. If they do not match, the token is counterfeit or corrupted, and the request is aborted.
Cryptographic Hashing
Once the structure is validated, the raw token is hashed with sha-256. The database never compares raw tokens; it interacts with these hashed representations to prevent exposure in the event of a database leak.
Database Lookup & Privilege Check
The system opens a database transaction and queries the api_tokens table for the hashed key.
- It explicitly looks for a token that is actively valid (
valid = 1) and matches the exactprovidedPrivilege. - If the token does not exist, was previously revoked, or lacks the requested privilege, the transaction is rolled back and the request is rejected with an
Invalid keyerror.
skipCountUpdates is enabled, this query uses a FOR UPDATE lock. This prevents race conditions, ensuring that concurrent requests using the same token safely queue to update the usage counters accurately.IP Restriction
If the caller requires IP validation (byPassIpCheck is false) and the token owner configured an IP whitelist (restricted_to_ip_address):
- The system verifies that an IP address was actually provided in the request.
- It checks if the provided IP exists within the token's configured whitelist array.
- If either check fails, the transaction is rolled back, and the system logs an unauthorized access attempt, returning an
Invalid Hosterror.
Expiration
The system checks if the token has an expires_at timestamp.
- If an expiration date exists and the current time has surpassed it, the token is permanently invalidated in the database (
valid = 0). - The system commits this invalidation state, logs the expiration, and returns a
Token expirederror to the caller.
Usage Tracking & Metadata
If the token successfully passes all security, network, and lifecycle checks:
- Unless
skipCountUpdatesis active, the system increments the token'susage_countby 1 and updates thelast_usedtimestamp to the current UTC time. - The transaction is committed.
- A success payload described above, is returned to the caller, containing rich metadata about the token, including its configured name, internal IDs, and current usage metrics.
References
Configuration
All verification behavior is controlled under apiTokens and rate_limiters.apiTokensLimiters in the configuration passed to configuration().
apiTokens
| Option | Type | Default | Description |
|---|---|---|---|
rateLimitOnSuccessfulRequest | boolean | false | When enabled, successful verification requests consume the general limiter |
Rate limiters
| Limiter | Description |
|---|---|
rate_limiters.apiTokensLimiters.consumptionRateLimiter | The main rate limiter for token verification, that limits only failed attempts |
rate_limiters.apiTokensLimiters.generalUnionLimiter | General burst limiter, that if enabled, will run on every request |
Metadata returned on success
| Field | Description |
|---|---|
name | Friendly token name defined in the creation step |
tokenId | Database identifier |
userId | Owner of the token |
createdAt | Creation timestamp |
expiresAt | Expiration timestamp, if set |
lastUsed | Last successful usage time |
usageCount | Number of successful uses |
providedPrivilege | Privilege level that was verified |