Skip to content

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

PropertyValue
FormatURL-safe base64 (no padding), 32 random bytes → 43 characters
Entropy256 bits — unguessable
BindingOne token = one unlocked .p12 private key
TTL15 minutes from creation
RefreshOpen a fresh session; no rotation endpoint (by design — simpler, zero-state)
SensitivityCredential-equivalent to the .p12 password. Redact from all logs.

When to re-authenticate

Your client should re-open a session when:

  • expiresAt is 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):

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 get also 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_EVICTED is the authoritative answer.
  • No session-binding to IP or user-agent. The bearer itself is the binding. Treat it that way.