Authentication via messengers instead of SMS — no per-message costs. Every authenticated user becomes reachable for notifications by phone number through the same bots. The system picks the right messenger automatically; you control the priority.
The token field in the confirmed session response is a signed JWT (RS256). Profile data (name, username) is taken directly from the messenger at the time of authentication and is not stored on the server. Verify the token using the public key available at the JWKS endpoint.
| Field | Description |
|---|---|
| _id | User ID (24 hex chars) |
| user_id | Messenger user ID (Telegram / MAX / WhatsApp numeric ID) |
| type | Messenger type: "telegram", "max" or "whatsapp" |
| phone | User phone number (e.g. "+79001234567") |
| first_name | First name from messenger profile (not stored, taken at auth time) |
| last_name | Last name (may be null, not stored) |
| username | Messenger username (may be null, not stored) |
| app_id | Application ID the token was issued for |
| iss | Issuer (Antirius Auth Cloud server URL) |
| iat | Issued at (Unix timestamp) |
| exp | Expiration (Unix timestamp) |
The public key for verifying JWT tokens is available at the standard JWKS endpoint:
https://id.antirius.com/.well-known/jwks.jsonVerify the signature using the JWKS public key (RS256), then check that app_id in the token matches your application ID:
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const APP_ID = 'YOUR_APP_ID';
const ISSUER = 'https://id.antirius.com';
const JWKS_URI = 'https://id.antirius.com/.well-known/jwks.json';
const jwks = jwksClient({ jwksUri: JWKS_URI });
function verifyAuthToken(token) {
return new Promise((resolve, reject) => {
jwt.verify(
token,
(header, cb) => {
jwks.getSigningKey(header.kid, (err, key) => cb(err, key?.getPublicKey()));
},
{ algorithms: ['RS256'], issuer: ISSUER },
(err, decoded) => {
if (err) return reject(err);
if (decoded.app_id !== APP_ID) return reject(new Error('APP_ID_MISMATCH'));
resolve(decoded);
},
);
});
}
export async function authMiddleware(ctx, next) {
const auth = ctx.headers.authorization || '';
if (!auth.startsWith('Bearer ')) {
ctx.status = 401;
ctx.body = { error: 'No token' };
return;
}
try {
ctx.state.user = await verifyAuthToken(auth.slice(7));
await next();
} catch (e) {
ctx.status = 401;
ctx.body = { error: e.message || 'Invalid token' };
}
}import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const APP_ID = 'YOUR_APP_ID';
const ISSUER = 'https://id.antirius.com';
const JWKS_URI = 'https://id.antirius.com/.well-known/jwks.json';
const jwks = jwksClient({ jwksUri: JWKS_URI });
function verifyAuthToken(token) {
return new Promise((resolve, reject) => {
jwt.verify(
token,
(header, cb) => {
jwks.getSigningKey(header.kid, (err, key) => cb(err, key?.getPublicKey()));
},
{ algorithms: ['RS256'], issuer: ISSUER },
(err, decoded) => {
if (err) return reject(err);
if (decoded.app_id !== APP_ID) return reject(new Error('APP_ID_MISMATCH'));
resolve(decoded);
},
);
});
}
async function authPreHandler(request, reply) {
const auth = request.headers.authorization || '';
if (!auth.startsWith('Bearer ')) {
return reply.code(401).send({ error: 'No token' });
}
try {
request.user = await verifyAuthToken(auth.slice(7));
} catch (e) {
return reply.code(401).send({ error: e.message || 'Invalid token' });
}
}You can also validate the authentication by calling the session status endpoint directly:
GET https://id.antirius.com/api/v1/auth/session/SESSION_ID?type=full
// If confirmed → response includes token + user:
{
"status": "confirmed",
"poll_type": "full",
"token": "eyJhbGciOi...",
"user": { "_id": "...", "phone": "+79001234567", ... },
"app_id": "YOUR_APP_ID"
}
// Progress check without JWT (repeatable):
GET https://id.antirius.com/api/v1/auth/session/SESSION_ID?type=statusCall with type=full. If the response contains "status": "confirmed" and a "token" field — the user is authenticated. Use type=status only to check progress without consuming the session.