← All posts

Ons aanmelding het presies 10 sekondes gehang. Ons eie sekuriteitskoptekste het dit gedoen.

Authagonal·July 23, 2026
oidcssocspfrontenddebuggingwar-story

Vir omtrent 'n week het terugkerende gebruikers wat ons portaal oopgemaak het, gesien hoe die woord "Laai tans…" tien sekondes lank op die skerm bly voordat die aanmeldbladsy verskyn het. Nie soms nie. Nie ongeveer nie. Tien sekondes, elke keer, en dan het alles perfek gewerk. Eerste-keer-besoekers het dit nooit gesien nie. Net mense wat voorheen aangemeld het.

'n Fout wat 'n willekeurige hoeveelheid tyd neem, is 'n prestasieprobleem. 'n Fout wat presies tien sekondes neem, is 'n bekentenis. Niks in 'n gesonde webversoek rond homself af tot 'n skoon mag van tien nie. Daardie getal is nie die som van werklike werk nie; dit is die plafon van 'n tydvertraging, en 'n tydvertraging beteken dat iets, êrens, geduldig wag vir iets wat nooit gaan opdaag nie.

Dit is die verhaal van waarvoor dit gewag het, en waarom die ding waarvoor dit gewag het, geblokkeer is deur 'n sekuriteitskopteks waarop ons trots was.

Wat die portaal doen wanneer dit laai

Ons portaal is 'n enkelbladsy-toepassing. Wanneer dit laai, voordat dit vir jou enigiets wys, probeer dit een vraag beantwoord: is jy reeds aangemeld? Die beleefde manier om dit met OIDC te doen, is 'n stil kontrole. Die toepassing vra die identiteitsverskaffer "as hierdie blaaier reeds 'n sessie het, gee my 'n vars token sonder om die gebruiker te pla." Ons kliëntbiblioteek, oidc-client-ts, stel dit beskikbaar as signinSilent(), en ons het dit met laai vanuit 'n renewSession()-hulpprogram geroep.

Daar is twee maniere waarop daardie stil kontrole kan loop. As die toepassing 'n refresh token vashou, doen die biblioteek 'n stil terugkanaal-uitruiling, geen UI betrokke nie, en jy is in. As dit nie 'n refresh token vashou nie, val die biblioteek terug op die ouer meganisme: dit maak 'n verborge iframe oop wat na die identiteitsverskaffer se authorize-eindpunt wys met prompt=none, en wag dat daardie iframe 'n resultaat terugplaas. Die hele punt van die iframe is dat dit onsigbaar is. Jy is veronderstel om dit nooit te sien nie, en met 'n gesonde opstelling sien jy dit nooit nie, want dit los in millisekondes op.

Ons s'n het glad nie opgelos nie.

Die muur wat ons self gebou het

Die iframe laai die auth-gasheer. En die auth-gasheer, soos elke gasheer wat ons bedryf wat enigsins ernstig opgeneem behoort te word, stuur twee koptekste wie se hele taak dit is om te sê "jy mag my nie in 'n raam sit nie":

  • X-Frame-Options: DENY
  • Content-Security-Policy: frame-ancestors 'none'

Dit is klikkaping-verdedigings, en hulle is korrek. 'n Aanvaller wat jou aanmeldbladsy in 'n iframe kan plaas, kan dit onder 'n lokmiddel laat sweef, 'n gebruiker mislei om regte geloofsbriewe in iets te tik wat onskadelik lyk, en hulle oes. frame-ancestors 'none' is die moderne instruksie dat geen oorsprong, nie eens ons eie nie, hierdie bladsy mag inbed nie. Ons het dit doelbewus aangeskakel. Dit is presies die soort ding waarna 'n sekuriteitsoorsig soek en wat dit beloon.

Toe oidc-client-ts dus sy verborge iframe teen daardie gasheer oopgemaak het, het die blaaier presies gedoen wat ons dit beveel het: dit het geweier om die bladsy in 'n raam te vertoon. En hier is die wrede deel. 'n Geweierde raam gooi nie 'n fout nie. Daar is geen foutgebeurtenis vir die biblioteek om te vang nie, geen verwerpte belofte nie, geen konsolereël nie. Die iframe sit net daar, leeg, onbepaald. Vanuit die biblioteek se oogpunt het nog niks gebeur nie, dus doen dit die enigste ding wat dit kan. Dit wag vir sy tydvertraging. Daardie tydvertraging, die verstek silentRequestTimeout, is tien sekondes.

Tien sekondes van 'n verborge iframe wat na 'n leë muur staar, dan gee die biblioteek op, die belofte verwerp uiteindelik, die toepassing trek sy skouers op en herlei jou na die regte aanmeldbladsy, en alles werk. Die hang was nooit 'n mislukking nie. Dit was 'n sukses wat die skilderagtige roete deur 'n gedoemde iframe geneem het.

Twee korrekte dinge, een slegte naat

Wat dit werklik moeilik gemaak het om te sien, is dat niks stukkend was nie. Die sekuriteitskoptekste was reg. Die stil-hernuwing-terugval was reg, 'n wettige en wyd gebruikte OIDC-patroon. Elke komponent het presies opgetree soos ontwerp en presies soos enige beoordelaar sou wou. Die tien-sekonde-hang het nie binne enigeen van hulle gewoon nie. Dit het in die ruimte tussen hulle gewoon, in die aanname wat elkeen oor die ander gemaak het. Die SSO-biblioteek het aangeneem dat dit die identiteitsverskaffer in 'n raam kon plaas. Die identiteitsverskaffer het aangeneem dat niemand ooit toegelaat moet word om dit in 'n raam te plaas nie. Beide aannames was verdedigbaar. Hulle was eenvoudig onversoenbaar, en geen enkele lêer het die weerspreking bevat nie.

Waarom dit hoegenaamd na die iframe gegryp het

Dit het steeds 'n vraag gelaat. Die vinnige pad, die refresh token-uitruiling, sou die iframe heeltemal oorgeslaan het. Waarom het terugkerende gebruikers op die stadige pad beland? Omdat hulle geen refresh token gehad het om vas te hou nie. En hulle het geen refresh token gehad nie, want ons eie portaal-OAuth-kliënte is voorsien sonder AllowOfflineAccess, die vlag wat 'n kliënt magtig om een uitgereik te word. Geen aflyntoegang nie, geen refresh token nie, geen vinnige pad nie, en elke terugkerende gebruiker is na die iframe gestoot wat nooit kon laai nie.

Dit was die werklike gebrek, en dit was 'n dataprobleem wat oor elke huurder versprei was, nie 'n eenreël-kodeverandering wat ons een keer kon stuur nie. Die herstel is dus 'n versoeningsdiens wat AllowOfflineAccess met opstart weer op die portaalkliënte van elke huurder toepas, wat die hele vloot met die volgende ontplooiing regstel sonder dat iemand 'n huurder met die hand aanraak. Refresh tokens het weer begin vloei, en die vinnige pad het vanself weer tot lewe gekom.

Die oplossing, en die les

Die versoeningsdiens het die oorsaak reggemaak. Maar 'n aanmelding behoort nie tien sekondes te talm nie, selfs wanneer dit wel op die stadige pad beland, so ons het die naat ook verstewig. renewSession() inspekteer nou eers die gestoorde gebruiker: as daar geen refresh token byderhand is nie, kortsluit dit en gee dadelik niks terug nie, slaan die iframe oor wat dit reeds weet gedoem is, en stuur die gebruiker reguit na interaktiewe aanmelding. Die refresh token-vinnige pad bly onaangeraak. En as 'n terugval vir enige agtergrondhernuwing wat steeds 'n iframe oopmaak, het ons die tydvertraging van tien sekondes na vyf gesny, sodat die ergste geval half so sleg is.

Die les wat ons werklik behou het, gaan oor 'n kategorie van fout, nie hierdie een geval nie. 'n Hang is 'n fout, al gee dit geen fout, geen uitsondering, geen rooi reël in 'n log af nie. Die enigste bewys wat dit laat, is verstreke tyd. En wanneer daardie tyd 'n skoon ronde getal is, moenie op jag gaan na stadige werk om te optimeer nie. Gaan jag na 'n tydvertraging, en vind dan die ding aan die ander kant daarvan wat stil, permanent, nooit gaan antwoord nie. Ons s'n was 'n iframe wat beleefd geklop het aan 'n deur wat ons doelbewus toegegrendel het.

As jy eerder wil hê dat jou aanmeldvloei reeds weet dat 'n toegesnoerde auth-gasheer en 'n stil-hernuwing-iframe nie meng nie, dit is 'n naat waarteen ons reeds vasgeloop het sodat jy dit nooit hoef te doen nie. Authagonal lewer die SSO-loodgietery en die sekuriteitskoptekste as een stelsel wat saam getoets is, nie as twee korrekte helftes wat jy mag ontdek onversoenbaar is teen tien sekondes per bladsy-laai nie.