Session authentication
fiscaliacore uses session-scoped bearer tokens instead of persistent API-side .p12 storage. This page is the reference for why and how.
Why session tokens
A .p12 signing certificate is the digital equivalent of a signature stamp — whoever holds it can sign invoices as the cert's named representative. Holding tens of thousands of those at rest in one database is a high-value target. Our design goal: a compromise of fiscaliacore's server yields, at most, 15 minutes of active signing keys.
The trade-off is a small per-session handshake: your product posts the .p12 + password once per 15 minutes per end-customer. We unlock the key in memory, hand you an opaque token, and the key lives in JVM memory until the session TTL or process restart — whichever comes first.
Lifecycle
<.p12> + password
your product ──POST /api/v1/sessions──► fiscaliacore
│
[unlock .p12 in JVM heap;
mint 32-byte opaque token;
put in ConcurrentHashMap
keyed by token, TTL 15m]
│
your product ◄──201 { token, expiresAt }──────┘
your product ──POST /api/v1/acecf/issue──► fiscaliacore
Authorization: Bearer <token> [look up session;
sign payload with
in-memory key;
submit to DGII]
│
your product ◄──200 { trackId, signedXml, ... }────────────┘
your product ──DELETE /api/v1/sessions/current──► fiscaliacore
Authorization: Bearer <token> [evict session +
DGII bearers]
│
your product ◄──204────────────────────────────────────────┘Token properties
| Property | Value |
|---|---|
| Format | URL-safe base64 (no padding), 32 random bytes → 43 characters |
| Entropy | 256 bits — unguessable |
| Binding | One token = one unlocked .p12 private key |
| TTL | 15 minutes from creation |
| Refresh | Open a fresh session; no rotation endpoint (by design — simpler, zero-state) |
| Sensitivity | Credential-equivalent to the .p12 password. Redact from all logs. |
When to re-authenticate
Your client should re-open a session when:
expiresAtis within 60 seconds. Proactively open a new session to avoid the 401-retry round-trip on the next call.- Any endpoint returns
401 SESSION_EVICTED. The process restarted, or your clock drifted past TTL, or someone explicitly closed the session. Open a fresh one and retry the call once. - You've finished the logical unit of work. Prefer short-lived sessions when you can; one session per request is overkill but one session per hour per customer is fine too.
The mandatory retry pattern
Every business call must handle 401 SESSION_EVICTED. Example (TypeScript):
async function withSession<T>(
client: FiscaliacoreClient,
call: (bearer: string) => Promise<Response>,
): Promise<T> {
let bearer = await client.token();
let res = await call(bearer);
if (res.status === 401) {
client.invalidateToken();
bearer = await client.token();
res = await call(bearer);
}
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
return res.json() as Promise<T>;
}Key rules:
- Evict once, not in a loop. A second 401 means something is wrong beyond stale session.
- Cache per end-customer, not globally. If you serve many RNCs, each keeps its own session.
- Don't share bearers across replicas unless you also share an encrypted bearer store. Two replicas with independent in-memory caches is fine — the first 401 just triggers a re-auth.
Session cache behavior on our side
- Storage:
ConcurrentHashMap<String, SigningSession>scoped to the JVM. No disk, no DB. - Eviction: Scheduled sweep every 60s removes expired entries; every
getalso checks expiry to avoid eviction-lag. - Restart: A process restart drops every session. Clients handle this transparently via the retry pattern above.
- Memory: Roughly 5 KB per active session (one RSA-2048 key + a typical 3-cert chain). 10,000 simultaneous sessions = ~50 MB — negligible on our VM.
What we don't do
- No refresh tokens. We're intentionally simple: if you want a fresh TTL, re-post the
.p12. - No introspection endpoint. If you want to know whether a token is still live, make a real call.
401 SESSION_EVICTEDis the authoritative answer. - No session-binding to IP or user-agent. The bearer itself is the binding. Treat it that way.