Przejdź do głównej zawartości

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).

┌──────────┐ ┌──────────┐
│ 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 │
│────────────────────────────────────────>│
  1. Pobranie challenge — POST /auth/challenge zwraca losowy ciag challenge i timestamp w milisekundach (timestampMs). Endpoint nie wymaga autoryzacji.

  2. 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.

  3. Wyslanie zaszyfrowanego tokenu — POST /auth/ksef-token z cielem zawierajacym challenge, encryptedToken i identyfikator NIP. Serwer zwraca referenceNumber i tymczasowy authenticationToken.

  4. Polling statusu — GET /auth/{referenceNumber} odpytywany jest cyklicznie (co ~1s, max 30 prob) do momentu, gdy serwer zwroci status.code === 200. Serwer weryfikuje certyfikat (OCSP/CRL) po swojej stronie.

  5. Wymiana na tokeny dostepu — POST /auth/token/redeem z authenticationToken w naglowku Authorization zwraca accessToken (krotkotrwaly) i refreshToken (dlugotrwaly).

  6. Uzycie tokenow — accessToken uzywany jest w naglowku Authorization: Bearer do wszystkich zapytan API. Gdy zbliza sie wygasniecie, klient automatycznie odswierza go przy uzyciu refreshToken.

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 szyfrowania
import { 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 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
})

Po pomyslnej autentykacji klient otrzymuje dwa tokeny:

TokenCzas zyciaUzycie
accessTokenKrotki (minuty)Autoryzacja zapytan API
refreshTokenDluzszyOdnowienie accessToken bez re-autentykacji

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
}

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.

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()
}
}
Kod bleduZnaczenie
INVALID_CHALLENGEOdpowiedz /auth/challenge nie zawiera pola challenge
INVALID_CHALLENGE_TIMESTAMPPole timestampMs jest nieprawidlowe lub brakujace
AUTH_FAILEDWeryfikacja serwera zakonczona bledem (np. nieprawidlowy NIP lub token)
AUTH_POLL_TIMEOUTPolling /auth/{ref} przekroczyl 30 prob (30 sekund)
NO_PUBLIC_KEYEndpoint /security/public-key-certificates zwrocil pusta liste
NO_TOKEN_ENCRYPTION_KEYBrak certyfikatu z usage: 'KsefTokenEncryption'