Autentykacja KSeF 2.0
Autentykacja w KSeF 2.0 opiera sie na protokole challenge-response z szyfrowaniem asymetrycznym. Klient szyfruje token KSeF kluczem publicznym Ministerstwa Finansow, a serwer weryfikuje tozsamosc i zwraca tokeny dostepu (access + refresh).
Schemat przeplywu
Dział zatytułowany „Schemat przeplywu”┌──────────┐ ┌──────────┐│ Klient │ │ KSeF API │└────┬─────┘ └────┬─────┘ │ │ │ 1. POST /auth/challenge │ │────────────────────────────────────────>│ │ { challenge, timestampMs } │ │<────────────────────────────────────────│ │ │ │ 2. RSA-OAEP(SHA-256) encrypt │ │ "{ksefToken}|{timestampMs}" │ │ kluczem publicznym MF │ │ │ │ 3. POST /auth/ksef-token │ │ { challenge, encryptedToken, NIP } │ │────────────────────────────────────────>│ │ { referenceNumber, authToken } │ │<────────────────────────────────────────│ │ │ │ 4. GET /auth/{referenceNumber} │ │ (polling do status 200) │ │────────────────────────────────────────>│ │ { status: { code: 200 } } │ │<────────────────────────────────────────│ │ │ │ 5. POST /auth/token/redeem │ │ Authorization: Bearer authToken │ │────────────────────────────────────────>│ │ { accessToken, refreshToken } │ │<────────────────────────────────────────│ │ │ │ 6. Zapytania z accessToken │ │ Authorization: Bearer accessToken │ │────────────────────────────────────────>│Kroki autentykacji
Dział zatytułowany „Kroki autentykacji”-
Pobranie challenge —
POST /auth/challengezwraca losowy ciagchallengei timestamp w milisekundach (timestampMs). Endpoint nie wymaga autoryzacji. -
Szyfrowanie tokenu — klient laczy token KSeF z timestampem w formacie
"{ksefToken}|{timestampMs}"i szyfruje algorytmem RSA-OAEP z SHA-256, uzywajac klucza publicznego MF. Wynik kodowany jest w base64. -
Wyslanie zaszyfrowanego tokenu —
POST /auth/ksef-tokenz cielem zawierajacymchallenge,encryptedTokeni identyfikator NIP. Serwer zwracareferenceNumberi tymczasowyauthenticationToken. -
Polling statusu —
GET /auth/{referenceNumber}odpytywany jest cyklicznie (co ~1s, max 30 prob) do momentu, gdy serwer zwrocistatus.code === 200. Serwer weryfikuje certyfikat (OCSP/CRL) po swojej stronie. -
Wymiana na tokeny dostepu —
POST /auth/token/redeemzauthenticationTokenw naglowku Authorization zwracaaccessToken(krotkotrwaly) irefreshToken(dlugotrwaly). -
Uzycie tokenow —
accessTokenuzywany jest w naglowkuAuthorization: Bearerdo wszystkich zapytan API. Gdy zbliza sie wygasniecie, klient automatycznie odswierza go przy uzyciurefreshToken.
Szyfrowanie RSA-OAEP
Dział zatytułowany „Szyfrowanie RSA-OAEP”Klucz publiczny MF pobierany jest automatycznie z endpointu GET /security/public-key-certificates przy pierwszym wywolaniu initSession(). Klient szuka certyfikatu z usage: 'KsefTokenEncryption' i aktywna data waznosci.
// Wewnetrzna implementacja szyfrowaniaimport { constants, createPublicKey, publicEncrypt } from 'node:crypto'
function rsaOaepEncrypt(plaintext: string, publicKeyPem: string): string { const key = createPublicKey(publicKeyPem) const data = Buffer.from(plaintext, 'utf8') const encrypted = publicEncrypt( { key, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }, data, ) return encrypted.toString('base64')}
// Format: "{ksefToken}|{timestampMs}"const encryptedToken = rsaOaepEncrypt( `${ksefToken}|${timestampMs}`, publicKeyPem,)Klucz publiczny MF
Dział zatytułowany „Klucz publiczny MF”Klucz pobierany jest raz i cache’owany na czas zycia klienta. Mozesz tez podac go recznie:
const adapter = createHttpAdapter({ nip: '5213456784', token: process.env.KSEF_TOKEN!, environment: 'production', // publicKeyPem pominiete -- auto-fetch z /security/public-key-certificates})import { readFileSync } from 'node:fs'
const publicKeyPem = readFileSync('./mf-public-key.pem', 'utf8')
const adapter = createHttpAdapter({ nip: '5213456784', token: process.env.KSEF_TOKEN!, environment: 'production', publicKeyPem, // klucz dostarczony recznie})Tokeny sesji
Dział zatytułowany „Tokeny sesji”Po pomyslnej autentykacji klient otrzymuje dwa tokeny:
| Token | Czas zycia | Uzycie |
|---|---|---|
accessToken | Krotki (minuty) | Autoryzacja zapytan API |
refreshToken | Dluzszy | Odnowienie accessToken bez re-autentykacji |
Automatyczne odswiezanie
Dział zatytułowany „Automatyczne odswiezanie”Klient sprawdza czas wygasniecia accessToken przed kazdym zapytaniem. Jesli do wygasniecia zostalo mniej niz 60 sekund, automatycznie wywoluje POST /auth/token/refresh z refreshToken:
function shouldRefresh(session: ActiveSession, now: Date = new Date()): boolean { const bufferMs = 60_000 // 60 sekund return session.accessExpiresAt.getTime() - now.getTime() <= bufferMs}Zamykanie sesji
Dział zatytułowany „Zamykanie sesji”Po zakonczeniu pracy sesje nalezy zamknac. Adapter robi to automatycznie w closeSession():
const adapter = createHttpAdapter({ nip: '5213456784', token: process.env.KSEF_TOKEN!, environment: 'production',})
const ksefnik = createKsefnik({ nip: '5213456784', adapter })
try { await ksefnik.invoices.sync({ dateFrom: '2026-03-01', dateTo: '2026-03-31', })} finally { await adapter.closeSession()}Zamykanie sesji wywoluje DELETE /auth/sessions/current. Bledy przy zamykaniu sa swiadomie ignorowane — niepowodzenie revokacji nie powinno maskowac wyniku poprzednich operacji.
Pelny przyklad
Dział zatytułowany „Pelny przyklad”import { createHttpAdapter } from '@ksefnik/http'import { createKsefnik } from '@ksefnik/core'
async function main() { const adapter = createHttpAdapter({ nip: '5213456784', token: process.env.KSEF_TOKEN!, environment: 'demo', // uzyj 'production' na produkcji client: { timeoutMs: 30_000, retry: { maxAttempts: 3 }, }, })
const ksefnik = createKsefnik({ nip: '5213456784', adapter, })
try { // initSession() wywolywane automatycznie -- challenge, szyfrowanie, polling, redeem await ksefnik.invoices.sync({ dateFrom: '2026-03-01', dateTo: '2026-03-31', })
const invoices = await ksefnik.invoices.getAll() console.log(`Pobrano ${invoices.length} faktur`) } finally { await adapter.closeSession() }}Bledy autentykacji
Dział zatytułowany „Bledy autentykacji”| Kod bledu | Znaczenie |
|---|---|
INVALID_CHALLENGE | Odpowiedz /auth/challenge nie zawiera pola challenge |
INVALID_CHALLENGE_TIMESTAMP | Pole timestampMs jest nieprawidlowe lub brakujace |
AUTH_FAILED | Weryfikacja serwera zakonczona bledem (np. nieprawidlowy NIP lub token) |
AUTH_POLL_TIMEOUT | Polling /auth/{ref} przekroczyl 30 prob (30 sekund) |
NO_PUBLIC_KEY | Endpoint /security/public-key-certificates zwrocil pusta liste |
NO_TOKEN_ENCRYPTION_KEY | Brak certyfikatu z usage: 'KsefTokenEncryption' |