@syadem/evc-js
v0.1.5
Published
A JavaScript library for parsing and verifying EVC (European Vaccination Card) tokens. Supports both JWT and CWT (CBOR Web Token) formats.
Downloads
183
Readme
evc-js
A JavaScript library for parsing and verifying EVC (European Vaccination Card) tokens. Supports both JWT and CWT (CBOR Web Token) formats.
Installation
npm install @syadem/evc-jsUsage
Parse a JWT EVC token
import { parseJwtEvc } from '@syadem/evc-js';
import { importSPKI } from "jose";
const jwt = "eyJhbGciOiJFUzI1NiJ9.eyJkb2IiOiIxOTkwLTAx...";
const publicKey = await importSPKI(
`-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEd26RtDbKuFnLvRshkJdY3t4kDCTQ
J3P4EXio3jcFFoAbus2k5bjc+0Q//Oyy8/4tDyGZ89U/me/pGUORg3+F+Q==
-----END PUBLIC KEY-----`,
"ES256"
);
const { evc, validation } = await parseJwtEvc(jwt, publicKey);
console.log("Certificate:", evc);
console.log("Signature verified:", validation.signatureVerified);Summary of the JWT EVC format
parseJwtEvc validates and parses a compact JWT profile used by Syadem frontends.
Header/crypto expectations:
- Signature must verify and algorithm must be
ES256 - Typical protected header fields are
alg, optionalkid, optionaltyp
Payload shape:
- Required:
dob,nam,ver - Optional:
iss,iat,exp,ext,metadata,v dob: ISO date string (YYYY-MM-DD)nam: object{ fn, gn }metadata:sex:MorFhp: map of integer-like string keys to values (true, number, orYYYY-MM-DD)boosters: array of positive integer vaccination IDs- additional custom keys are preserved
v: compact vaccination encoding grouped by country code:- type:
Record<string, number[][]> - each group format:
[nuvaCode, daysOffset1, actId1, daysOffset2, actId2, ...] daysOffsetis computed fromdob(days since date of birth)
- type:
Example:
{
"dob": "2018-04-12",
"nam": { "fn": "Emma", "gn": "Martin" },
"ver": "1.0.0",
"iss": "SYADEM",
"iat": 1735689600,
"exp": 1767225600,
"ext": "PAT-00078421",
"v": {
"FRA": [
[66, 60, 10001, 121, 10002],
[949, 365, 10003]
],
"BEL": [
[188, 121, 20001, 730, 20002]
]
},
"metadata": {
"sex": "F",
"hp": { "3": true, "8": "2024-01-01" },
"boosters": [10003, 20002]
}
}Reading one group example:
FRA: [66, 60, 10001, 121, 10002]66is the NUVA code shared by that group- then
(60, 10001)and(121, 10002)are(daysOffset, actId)pairs - with
dob = 2018-04-12, offsets map to2018-06-11and2018-08-11
Grouping behavior in serializeEvcTokenToJwt:
- first level key:
countryCode - second level grouping: same
nuvaCodewithin that country - each group appends pairs in encounter order:
daysOffset, actId
Additional v examples:
Single vaccination act in one country:
{
"dob": "2013-02-01",
"v": {
"FRA": [
[523, 4383, 710798]
]
}
}Interpretation:
- NUVA code
523 - one act:
daysOffset=4383,id=710798 - performed on
2013-02-01 + 4383 days = 2025-02-01
Two acts for the same NUVA code (the 5-number pattern):
{
"dob": "2013-02-01",
"v": {
"FRA": [
[523, 4686, 558263, 4383, 710798]
]
}
}Interpretation:
523is the group NUVA code(4686, 558263)= first act (2025-12-01)(4383, 710798)= second act (2025-02-01)
Realistic multi-group country payload:
{
"dob": "2013-02-01",
"v": {
"FRA": [
[523, 4686, 558263, 4383, 710798],
[141, 4686, 538108],
[495, 4611, 794682]
]
}
}Interpretation:
- group
523-> acts558263(2025-12-01),710798(2025-02-01) - group
141-> act538108(2025-12-01) - group
495-> act794682(2025-09-17)
How this maps to the parsed Evc object:
issuer <- ississuedAt <- iatexpiresAt <- expexternalId <- extname.firstName <- nam.fn,name.lastName <- nam.gn- each
ventry becomes one vaccination act with:nuvaCodefrom the group first valuecountryCodefrom the country key (FRA,BEL, ...)performedOnfromdob + daysOffsetidfromactIdboostertrue whenidis listed inmetadata.boosters
This compact JWT profile differs from the official EVC schema, especially for the vaccination representation (v).
Parse a CWT EVC token
import { parseCwtEvc, getKey } from '@syadem/evc-js';
const cwt = "6BFOXN*TS0BI$ZDZRH..."; // Base45-encoded CWT
const { evc, validation } = await parseCwtEvc(cwt, getKey);
console.log("Certificate:", evc);Generate a JWT EVC token
import { serializeEvcTokenToJwt } from '@syadem/evc-js';
import { importPKCS8 } from "jose";
const evc = {
dateOfBirth: new Date("1990-01-01"),
name: { firstName: "John", lastName: "Doe" },
version: "1.0.0",
issuer: "SYADEM",
vaccinations: [
{ nuvaCode: 12345, countryCode: "FRA", performedOn: new Date("2023-06-15"), id: 67890 }
]
};
const privateKey = await importPKCS8(`-----BEGIN EC PRIVATE KEY-----...`, "ES256");
const jwt = await serializeEvcTokenToJwt(evc, privateKey);Technical Documentation
For details on CBOR encoding, COSE signatures, JWK key management, and the full token processing pipeline, see EVC_PARSING.md.
References
- EVC Schema Specification - Official payload structure
- EVC Documentation - Full documentation
- EUVABECO Project - Project homepage
- EUVABECO Trust Keys - Public keys for signature verification
