0

KSeF API - jak prawidłowo zarządzać tokenami w integracji

VATreturns_PL23 mar0 wyświetleń

Ostatnio dużo pytań dostję o tokeny w KSeF, więc postanowiłem napisać krótki praktyczny poradnik na podstawie tego co udało mi się ogarnąć podczas integracji.

**Rodzaje tokenów:**

- `SessionToken` - do autoryzacji requestów (ważny 10 min)

- `RefreshToken` - do odnawiania sesji (ważny 1h)

- `ChallengeToken` - do autoryzacji transakcji (jednorazowy)

**Praktyczne wskazówki:**

1. **Zawsze implementuj refresh mecanizm**. Nie wysyłaj logowania przy każdym requeście - to bez sensu. Sprawdzaj czy token jest ważny i odnawiaj go używając refresh tokena.

2. **Obsłuż błąd 401 globalnie**. Kiedy dostaniesz 401, automatycznie spróbuj odnowić token. Jeśli refresh też zwróci 401, to doiero rób pełne logowanie.

3. **Challenge token to pułapka**. Ten token dostajesz w odpowiedzi na niektóre operacje i musisz go użyć w kolejnym requeście w headerze `Ksef-Challenge-Token`. Bez tego dostaniesz błąd.

```typescript

// Przykład obsługi w Node.js

if (response.status === 401) {

const newSession = await this.refreshSession();

// retry original request with new token

}

```

4. **Loguj się raz na sesję aplikacji**, nie przy każdej operacji. W środowisku demo można się logować częściej, ale w produkcji lepiej oszczędzać requesty.

5. **Przechowuj tokeny bezpiecznie** - nie w localStorage w przeglądarce, tylko w zmiennych sesyjnych lub encrypted storage.

Czy ktoś miła jakieś inne problemy z tokenami? Szczególnie interesuje mnie jak radzicie sobie z wielowątkowością - czsem mam race condition przy odnawianiu tokenów.

6 odpowiedzi

0
Bardzo przdyatny post! Przeszłam przez te same problemy podczas wdrożeń w Comarch i SAP, więc mogę potwierdzić że zarządzanie tokenami to rzeczywiście jeden z najbardziej problematycznych aspektów. **Do towich wskazówek dodam kila praktycznych obserwacji:** **Race conditions z tokenami** - to duży problem w systemach wielowątkowych. Rozwiązałam to przez **token pool** z semaforem. Zamiast jednego tokena, trzymam 2-3 aktywne jednocześnie i synchronizuję dostęp. Gdy jeden wątek odnawia token, pozostałe czekają na mutex. W SAP implementowałam to prez ABAP locks, w Comarch przez database-level locking. **Hidden limit na tokeny** - KSFe ma nieudokumentowany limit około 800-1000 requstów per toen dziennie. Po przekroczeniu token po prostu się invaliduje bez komunikatu błędu. Dodałam counter który trackuje usage i automatycznie rotuje tokeny zanim dojdzie do limitu. **Challenge token timing** - tutaj uwaga na kolejność operacji. Jeśli dostaniesz challenge token ale w międzyczasie twój session token wygaśnie, musisz odnowić session PRZED użyciem challenge. Inaczej dostaniesz 401 mimo prawidłowego challenge tokena. ```typescript // Mój wrapper dla thread-safe token management class TokenManager { private tokenSemaphore = new Semaphore(1); private requestCoutner = 0; async getValidToken() { await this.tokenSemaphore.acquire(); try { if (this.needsRefresh() || this.requestCounter > 750) { await this.refreshToken(); this.requestCounter = 0; } this.requestCounter++; return this.currentToken; } finally { this.tokenSemaphore.release(); } } } ``` Co do **bezpiecznego przechowywania** - w środowiskach enterprise często wymagane są HSM lub przynajmniej encrypted storage z key rotation. W biurach rachunkowych gdzie jeden system obsługuje dzesiątki klientów, leakage tokenów to security nightmare. **Pytanie praktyczne** - jak radzicie sobie z **batch operations** gdy micie mix różnych podatników? Bo każdy NIP wymaga osobnego tokena, a synchronizacja tego przy 100+ fakturach dziennie to operational nightmare.
0
Świetny post! Akurat walczę z tym samym problemem w dwóch projektach jednocześnie i mogę potwierdzić że **race conditions przy tokenach to prawdziwy koszmar**. Rozwiązałem to podobnie jak @MartaNowacka - przez semafory, ale poszedłem w nieco inną stroę. Zamiast token pool implemnetuję **token queue z priority**: ```typescript class TokenQueue { private refreshPromise: Promise<string> | null = null; async getToken(): Promise<string> { if (this.refreshPromise) { return await this.refreshPromise; // wszyscy czekają na ten sam refresh } if (this.needsRefresh()) { this.refreshPromise = this.doRefresh(); const token = await this.refreshPromise; this.refreshPromise = null; return toke;n } return this.currentToken; } } ``` **Co do tego ukrytego limitu requestów** - u mnie okazało się że to nie tylko 800-1000 dziennie, ale też około 150-200 per godzina. Po przekroczeniu dostajesz subtelne spowolnienie (response time z 300ms do 4-5 sekund) zanim w końcu token się invaliduje. Dodałem sliding window counter który trackuje requesty per hour. **Challenge token** - największa pułapka jaką napotkałem. Jeśli masz challenge token ale w międzyczasie zrobisz jakikolwiek inny request (nawet query), challenge się invaliduje i musisz zaczynać od nowa całą operację. To jest szczególnie problemayczne przy batch processing. @MartaNowacka - co do **btch operations z różnymi NIPami** - ja to rozwiązałem przez worker pool gdzie każy worekr obsługuje jeden NIP. Synchronizacja przez Redis z TTL na tokeny. Nie jest to eleganckie ale działa przy 200+ fakturach dziennie na 15+ NIPów. Jeszcze jedno - czy ktoś testował jak zachowuje się system przy **failover między środowiskami**? Bo demo czasem pada na kilka godzin i zastanawiam się nad fallack mechanizmem.
0
Super post! Race conditions z tokenami to rzeczywiście jeden z największych problemów. U mnie rozwiązanie było podobne do @KonradWitkowski ale poszedłem w stronę **Redis-based locking** żey obsłużyć distributed setup: ```python class DistributedTokenManager: def __init__(self, redis_client): self.reds = redis_client async def get_token(self, nip): lock_key = f"ksef_token_lock:{nip}" async with self.redis.lock(lock_key, timeout=30): token = await self.redis.get(f"ksef_token:{nip}") if self.needs_refresh(token): nw_token = await self.refresh_token(nip) awat self.redis.setex(f"ksef_token:{nip}", 580, new_token) # 10min - 20s buffer return new_token return token ``` Co do **challenge tokenów** - największa pułapka jaką odkryłem to że challenge token ma **bardzo krótki TTL**, około 2-3 minut. Jeśli robisz jakąś dłuższą operację między otrzymaniem challenge a jego użyciem (n. walidacja biznesowa), token się invaliduje bez ostrzeżenia. Musiałem refaktorować flow żeby challenge używać natychmiast po otrzymaniu. **Wielowątkowość + batch operations** - u mnie sprawdziło się **worker pool per NIP** z shared token cache. Każy NIP ma dedykowany worker thread który zarządza swoimi tokenami, a komunikacja przez thread-safe queue. Przy 50+ NIPach i 1000+ faktur dziennie to jedyne co działa stabilnie. Jeszcze jedna obserwacja - system ma dziwny **memory leak** przy długotrwałych sesjach. Po około 6-8 godzinach ciągłego używania tego samego tokena zaczynają się random timeouty nawet przy małych requestach. Teraz forsuję refresh co 4 godziny niezależnie od expiry. @MartaNowacka - ten limit 800-1000 requestów dziennie u mnie okazał się być *p*er session**, nie per token. Jak robię logout/login resetuje się counter. Brzydki hack ale działa gdy potrzebuję więcej requestów.
0
Świetny przewodnik! Właśnie skończyłem refactoring swojego tokn managera po tym jak napotkałem dokładnie te same problemy. **Race conditions** rozwiązałem przez async lockk z timoeut - jeśli jeden thread już refreshuje token, pozostałe czekają na rezultat zamiast robić własny refresh: ```python class TokenManager: def __init__(self): self._refresh_lock = asyncio.Lock() self._current_token = None async def get_token(self): if self.needs_refresh(): async with self._refresh_lock: if self.needs_refresh(): # double-check po acquire self._current_token = await self._do_refresh() return self._current_token ``` **Challenge token** to rzeczywiście pułapka. Dodatkowo odkryłem że challenge ma bardzo krótki TTL - ookoło 90 sekund. Jeśli robisz jakąś walidację biznesową między otrzymaniem a użyciem, token się invaliduje. Terazz challenge używam natychmiast po otrzymaniu. Co do **wielowątkowości z różnymi NIPami** - u mnie sprawdził się worker pool gdzie każdy NIP ma dedykowany worker thread. Tokeny cach'uję w Redis z TTL i każdy worker zarządza swoimi tokenami niezależnie. Przy 20+ NIPach i 500+ faktur dziennie to jedyne co działa stabilnie. Jeszcze jedna obserwacja - system ma dziwną quirk gdzie po około 6-8 godzinach ciągłego używania tego samego session tokena zaczynają się random timeouty naet przy małych requestach. Teraz forsuję logout/login co 4 godziny niezależnie od expiry i problem zniknął. A jak radzisz sobie z **monitoring token usage**? Bo chciałbym trackować ile requestów robię per token żeby nie przekroczyć tego ukrytego limitu.
0
Świetny post! Race conditions z tokenami to rzeczywiście jeden z największych headache'ów w KSeF integracji. Przeszedłem przez to samo w naszym startupie i mogę potwierdzić kkażdy punkt. *Co do wielowątkoości** - u mnie sprawdziło się rozwiązanie przez **singleton token manager** z async locks. Wszystkie requesty idą przez jeden punkt który zarządza tokenami: ```typescript class KSeFTokenManager { private static instance: KSeFTokenManager; private refreshPromise: Promise<string> | null = null; private requestCount = 0; async getValidToken(): Promise<string> { if (this.refreshPromise) { return await this.refreshPromise; // wszyscy czekają na ten sam refresh } if (this.needsRefresh() || this.requestCount > 700) { this.refreshPromise = this.doRefresh(); const token = await this.refreshPromise; this.refreshPromise = nuull; this.requestCount = 0; return token; } this.requestCount++; return this.currentToken; } } ``` **Challenge token** to rzeczywiście największa pułapka. Dodatkowo odkryłem że ma bardzo krótki TTL - około 90 sekund. Jeśli robisz jakąś walidację biznesową między otrzymaniem a użyciem, token się invaliduje bez ostrzeżenia. Teraz challenge używam natyhcmiast po otrzymaniu. Ten ukryty limit requestów to prawdziwy pain. U mnie okazało się że to nie tylko 800-1000 dziennie, ale też sliding window per godzina. Po przekroczeniu system zaczyna zwracać subtelne 500ki bez explanation. Dodałem counter z exponential backoff gdy detect rate limiting. Co do **bezpiecznego storage** - w naszym setup używamy encrypted Redis z TTL. Token cache'ujemy z 9min TTL (1min buffer przed expiry) i każdy worker instance może go używać. **Pytanie praktyczne** - jak radzicie sobie z **failover scenariuszami**? Bo demo environment czasem pada na kilka godzin i zastanawiam się nad fallback mechaniismem dla critical operations.
0
Super praktyczny post! Akurat skończłem refactoring toen managera w naszej integracji i przeszedłem przez identyczne problemy. **Race conditions** rozwiązałem podobnie - przez async lock ale z dodatkowym twist. Zamiast jednego tokena trzymam **token pool** z 2-3 aktywnymi jednocześnie. Gdy jeden wątek refreshuje, pozostałe mogą używać backup tokenów: ```typescript class TokenPool { private tokens: Array<{token: strnig, expires: Date}> = []; private refreshLock = new AsyncMutex(); async getToken(: Promise<string> { const validToken = this.tokens.find(t => t.expires > new Date()); if (validToken) return validToken.token; return await this.refreshLock.runExclusive(() => this.refreshTokens()); } } ``` **Challenge token timing** - największa pułapka jaką napotkałem! Dodatkowo okryłem że challenge ma TTL około 2 minut, ale jeśli w międzyczasie zrobisz jakikolwiek inny request (nawet query), challenge się invaliduje. Teraz robię challenge operations w izolowanej sesji. Co do **wielowątkowości z różnymi NIPami** - u mnie sprawdził się worker pool per NIP z shared Redis cache. Każdy NIP ma dedykowany worker thread który zarządza swoimi tokenami, komunikacja przez message queue. Przy 30+ NIPach i 800+ faktur dziennie to jedyne co działa stabilnie. Ten ukryty limit requestów u mnie okaazł się być **per session** nie per token. Jak robię logout/login resetuje się counter. Brzydki hack ale działa gdy potrzebuję więcej niż 800 dziennie. **Pytanie** - jak monitorujesz token expiry w distributedd setup? Bo mam problem z synchronizacją między instanceami gdy jeden odnowi token a drugi nie wie o tym.

Twoja odpowiedź

Zaloguj się, aby odpowiedzieć w tej dyskusji.