Jeden mały błąd między frontendem, backendem i Azure, który psuje produkcję
Czasem nie psuje systemu wielki błąd architektoniczny, tylko źle odczytany host albo scheme. W efekcie frontend, backend i Azure zaczynają działać według różnych wersji tej samej rzeczywistości.
Poniżej znajduje się pełna treść artykułu. Quizy i symulacje wymagają włączenia JavaScript.
Jeden mały błąd między frontendem, backendem i Azure, który psuje produkcję
W nowoczesnych projektach technologicznych bardzo łatwo uwierzyć, że największe ryzyko leży w dużych decyzjach architektonicznych. Zespół rozmawia o wzorcach, chmurze, bezpieczeństwie, skalowalności, separacji odpowiedzialności i kontraktach między usługami. To wszystko jest ważne. Problem polega na tym, że produkcja bardzo często nie psuje się przez wielkie decyzje. Produkcja psuje się przez małe błędy, które na początku wyglądają jak drobiazg.
I właśnie dlatego nawet dobre zespoły, doświadczeni developerzy i architekci popełniają ten sam błąd: zakładają, że jeśli frontend działa lokalnie, backend odpowiada poprawnie, a deployment przeszedł bez czerwonych błędów, to system jest gotowy. Nie jest. Między „działa” a „działa dobrze na produkcji” jest ogromna różnica.
Jednym z najbardziej zdradliwych przypadków jest sytuacja, w której frontend, backend i infrastruktura w Azure zaczynają działać według różnych wersji tej samej rzeczywistości. Każda warstwa widzi request trochę inaczej. Każda interpretuje host, scheme, origin, cookies i ścieżkę ruchu po swojemu. I wtedy cały system, mimo że formalnie działa, zaczyna sprawiać wrażenie niestabilnego, niespójnego i niebezpiecznego w utrzymaniu.
Dlaczego ten problem jest tak groźny
Najgorsze w takich awariach jest to, że bardzo rzadko objawiają się one jednym, czytelnym komunikatem. Zamiast tego pojawiają się rozproszone symptomy:
- logowanie działa tylko czasami,
- po odświeżeniu użytkownik nagle traci sesję,
- frontend dostaje 401 mimo wcześniejszego zalogowania,
- redirect po autoryzacji wraca na zły adres,
- cookie są widoczne, ale nie wracają w kolejnych żądaniach,
- CORS w jednym flow działa, a w drugim zaczyna się sypać,
- na lokalnym środowisku wszystko wygląda dobrze, ale na Azure problem wraca w najmniej przewidywalnym momencie.
To jest właśnie ten typ problemu, który rozwala zaufanie do systemu. Użytkownik nie widzi, że źle został przekazany host albo że backend błędnie interpretuje schemat po reverse proxy. Użytkownik widzi tylko jedno: system nie zachowuje się stabilnie.
Jak ten problem wygląda w realnym projekcie
Załóżmy bardzo typowy układ:
- Angular jako frontend panelu administracyjnego,
- ASP.NET Core jako backend API,
- Azure App Service jako hosting aplikacji,
- Azure Front Door albo Application Gateway jako warstwa wejściowa,
- własna domena,
- logowanie oparte o cookies, sesję albo callback po autoryzacji.
Na diagramie wszystko wygląda dojrzale. Na lokalnym środowisku jest w porządku. Na stagingu też pozornie nie widać dużych problemów. I właśnie wtedy zaczyna się najgorsza pułapka: zespół uznaje, że architektura jest dobra, bo testy podstawowe przeszły.
Produkcja bardzo szybko pokazuje, że to nie wystarczy.
W praktyce wystarczy jeden mały błąd:
- backend źle odczytuje
X-Forwarded-Proto, - aplikacja interpretuje request jako
http, chociaż użytkownik wszedł pohttps, - host publiczny i host wewnętrzny są traktowane jak to samo,
- frontend buduje adresy API na podstawie nie tego źródła, co trzeba,
- cookies mają politykę, która nie odpowiada realnemu przepływowi,
- callback jest generowany na podstawie błędnej domeny.
Każda z tych rzeczy osobno może wyglądać niewinnie. Razem tworzą system, który działa pozornie, ale architektonicznie jest już pęknięty.
Największy błąd zespołów: leczenie objawów zamiast źródła
W wielu projektach reakcja wygląda podobnie. Frontend dostaje wyjątek w interceptorze.
Backend dostaje kolejnego if-a.
DevOps dopina regułę w infrastrukturze. Ktoś ręcznie ustawia URL w konfiguracji.
Ktoś inny chwilowo luzuje politykę bezpieczeństwa, żeby odblokować release.
To jest bardzo niebezpieczny moment.
Bo zespół zaczyna mieć poczucie, że „coś zostało poprawione”, ale w rzeczywistości poprawiane są tylko objawy. Źródło problemu dalej istnieje. System dalej nie ma wspólnej definicji prawdy o requestach. I to oznacza, że przy kolejnym wdrożeniu, nowym flow albo zmianie domeny problem wróci w innej formie.
Taka architektura bardzo szybko zamienia się w konstrukcję z obejść. Niby działa, ale nikt rozsądny nie chce jej dotykać bez stresu.
Realne zadania, które pokazują, gdzie system naprawdę się sypie
Najlepiej widać ten problem nie na diagramach, tylko na realnych zadaniach biznesowych i technicznych.
1. Logowanie do panelu administracyjnego
To powinno być proste. Użytkownik wpisuje login, hasło, przechodzi autoryzację i trafia do panelu. W praktyce właśnie tu najczęściej wychodzi, że system nie rozumie, jaka jest publiczna domena i czy ruch naprawdę jest zabezpieczony przez HTTPS.
Objawy:
- cookie zostaje zapisane, ale nie wraca przy kolejnym requestcie,
- frontend uważa, że użytkownik jest zalogowany, a backend zwraca 401,
- po odświeżeniu panelu sesja znika,
- redirect po logowaniu trafia na zły adres lub zły host.
To nie jest drobna niedogodność. To jest bezpośrednie uderzenie w zaufanie do produktu.
2. Publikacja treści w panelu admina
Panel redakcyjny bardzo często korzysta z osobnej ścieżki API, dodatkowych polityk autoryzacji i innego zestawu requestów niż część publiczna. I właśnie tu wychodzą niespójności w polityce cookies, hostach i CORS.
Objawy:
- zapis draftu działa lokalnie, ale nie działa na produkcji,
- publikacja zwraca błąd mimo poprawnych danych,
- frontend wysyła request poprawnie, ale backend nie widzi użytkownika w kontekście sesji,
- część endpointów admina działa, a część sprawia wrażenie losowo niestabilnych.
To jest klasyczny sygnał, że problem nie leży w samym formularzu, tylko w tym, jak warstwy interpretują request i autoryzację.
3. Callback po integracji z zewnętrznym dostawcą
W przypadku OAuth, płatności, integracji z providerami albo zewnętrznymi systemami problem staje się jeszcze bardziej widoczny. Jeżeli backend generuje callback na podstawie błędnej domeny albo schematu, cała integracja zaczyna się rozjeżdżać.
Objawy:
- powrót z zewnętrznego systemu trafia na zły adres,
- request wraca na HTTP zamiast HTTPS,
- provider uznaje callback za nieprawidłowy,
- flow działa na jednym środowisku, a na drugim nie.
Tu już nie ma miejsca na „może zadziała”. Albo architektura ma jedną prawdę o ruchu, albo integracje będą sprawiały problemy.
4. Głębokie linki i odświeżenie widoków
W aplikacjach SPA problem często wychodzi przy wejściu bezpośrednio na konkretną podstronę albo po odświeżeniu widoku. Nagle okazuje się, że routing, autoryzacja i backend nie widzą tego samego kontekstu. Frontend próbuje odtworzyć stan, backend uznaje brak sesji albo błędny origin, a użytkownik dostaje zachowanie, które wygląda jak chaotyczne. To jest szczególnie niebezpieczne, bo bardzo często przechodzi w prostych testach klikanych ręcznie, a sypie się dopiero przy normalnym użyciu.
Przykład realnego problemu, który ostatnio rozwiązywaliśmy
Jednym z bardzo realnych problemów, które wychodzą dokładnie w takim układzie, jest sytuacja,
w której frontend buduje adresy API z bieżącego originu,
a backend zbyt późno ustawia obsługę
ForwardedHeaders.
Lokalnie to może działać. Za proxy i pod domeną produkcyjną zaczyna się chaos.
const apiUrl = `${window.location.origin}/api`;
Na lokalnym środowisku wygląda to wygodnie. Problem zaczyna się wtedy, gdy:
- panel działa pod jedną domeną,
- API działa pod inną,
- ruch przechodzi przez proxy,
- publiczny host nie jest tym samym co host wewnętrzny.
export interface AppEnvironment {
production: boolean;
apiBaseUrl: string;
}
export const environment: AppEnvironment = {
production: true,
apiBaseUrl: 'https://api.twojadomena.pl'
};
@Injectable({ providedIn: 'root' })
export class ApiConfigService {
readonly baseUrl = environment.apiBaseUrl.replace(/\/+$/, '');
}
Interceptor, który nie zgaduje, tylko normalizuje adres
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
export const apiBaseUrlInterceptor: HttpInterceptorFn = (req, next) => {
if (/^https?:\/\//i.test(req.url)) {
return next(req);
}
const config = inject(ApiConfigService);
const normalizedUrl = `${config.baseUrl}/${req.url.replace(/^\/+/, '')}`;
return next(
req.clone({
url: normalizedUrl,
withCredentials: true
})
);
};
Druga część problemu była po backendzie.
Jeżeli obsługa UseForwardedHeaders()
pojawi się za późno, dalszy pipeline pracuje już na błędnym obrazie requestu.
using Microsoft.AspNetCore.HttpOverrides;
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto |
ForwardedHeaders.XForwardedHost;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
var app = builder.Build();
app.UseForwardedHeaders();
app.UseCors("AdminCors");
app.UseAuthentication();
app.UseAuthorization();
To jest właśnie dobry przykład problemu, który wygląda niepozornie, a w praktyce rozwala:
- logowanie,
- cookies,
- redirecty,
- callbacki,
- generowanie linków,
- spójność między frontendem a backendem.
Co naprawdę trzeba zrobić dobrze
Poprawna naprawa nie polega na dopisywaniu kolejnych wyjątków. Poprawna naprawa polega na zbudowaniu jednej wspólnej prawdy dla całego przepływu.
- Frontend nie może zgadywać, gdzie jest API. Musi mieć jasną konfigurację środowiskową.
- Backend musi poprawnie rozumieć host, scheme i fakt, że stoi za proxy.
- Azure musi być traktowany jako realna warstwa architektury, a nie neutralna rura transportowa.
- Cookies muszą być zgodne z rzeczywistym przepływem, a nie z lokalnym wyobrażeniem o nim.
- CORS musi być ustawiony precyzyjnie dla publicznych domen, a nie „żeby działało”.
- Logi muszą pokazywać, co system naprawdę widzi, a nie co zespół zakłada.
Dopiero wtedy można mówić o dojrzałości produkcyjnej.
Najbardziej krytyczna lekcja
Architektura nie psuje się tylko przez wielkie, spektakularne decyzje. Bardzo często psuje się przez drobne, źle zrozumiane założenia:
- zły host,
- zły scheme,
- zła kolejność middleware,
- błędna polityka cookies,
- fałszywe przekonanie, że Azure niczego nie zmienia,
- zbyt duża wiara w to, że skoro lokalnie działa, to produkcja też zadziała.
I właśnie dlatego ten typ problemu jest tak groźny. Nie wygląda jak wielka awaria architektoniczna. Na początku wygląda jak mała anomalia. Dopiero po czasie okazuje się, że steruje logowaniem, routingiem, callbackami, bezpieczeństwem i stabilnością całej platformy.
Podsumowanie
Frontend, backend i Azure mogą być osobno zaprojektowane poprawnie, a mimo to razem stworzyć układ niestabilny. Produkcja nie nagradza tylko dobrego kodu. Produkcja nagradza spójność.
Jeżeli system ma zachowywać się dojrzale, wszystkie warstwy muszą mówić jednym językiem prawdy. Ten sam host. Ten sam scheme. Te same zasady zaufania. Ta sama semantyka bezpieczeństwa. Ten sam obraz requestu.
Bo czasem właśnie od jednego małego błędu zależy, czy użytkownik zobaczy profesjonalną platformę, czy system, który wygląda dobrze tylko do pierwszego trudniejszego wdrożenia.