'n BFF vir baie huurders, en die een versoek wat sonder koekie aankom
Enkelbladtoepassings is geleer om hul eie tokens te hou. Die toepassing loop die OAuth-vloei in die blaaier, kry 'n toegangstoken en gewoonlik 'n verfrissingstoken terug, hou dit êrens in JavaScript, en heg dit aan elke API-oproep. Dit is goed gedokumenteer, dit is wat die meeste tutoriale wys, en dit plaas jou langslewende geloofsbrief op die een plek wat jy nie kan verdedig nie: 'n looptyd wat met graagte enigiets sal uitvoer wat sy pad na die bladsy vind. Een geslaagde inspuiting, een gekompromitteerde afhanklikheid êrens in jou bou, en niemand hoef iemand uit te vis nie. Hulle lees die token en vertrek, en dit bly werk totdat dit verval.
Die backend-for-frontend-patroon skuif die geloofsbrief buite bereik. Die blaaier praat met 'n klein bediener wat aan jou toepassing behoort, en daardie bediener is die vertroulike OAuth-kliënt. Dit loop die kode-uitruil, dit hou die toegangs- en verfrissingstokens, en dit gee die blaaier niks anders as 'n ondeursigtige koekie nie. Dit is 'n beskrywing van hoe ons ons eie gebou het, en van die een deel wat werklik interessant geblyk het: om dit meer as een huurder te laat bedien.
Waarmee die blaaier eindig
'n Koekie genaamd __Host-agbff, gemerk HttpOnly, SameSite=Lax, met bestek oor die hele pad, sonder vervaldatum sodat dit saam met die blaaiersessie sterf. Sy waarde is 256 bisse van willekeurigheid en niks anders nie. Dit is nie 'n token nie, dit dekodeer nie in enigiets nie, en om dit van die draad af te steel is nie iets wat jy aan 'n __Host--koekie oor TLS kan doen nie.
Alles wat werklik is, sit bedienerkant, in 'n sessierekord in 'n verspreide kas: die toegangstoken, die verfrissingstoken, die id-token, wanneer die toegangstoken verval, en aan watter huurder die sessie behoort. Die aanmelding self is 'n gewone magtigingskode-vloei met PKCE, geloop deur 'n vertroulike kliënt wat hom by die token-eindpunt met sy geheim staaf. Die id-token word behoorlik gevalideer op pad terug in: die uitreiker, die gehoor, die handtekening teen die gepubliseerde sleutels, die leeftyd, en dan 'n konstante-tyd-vergelyking van die nonce teen die waarde wat voor die herleiding weggebêre is. Eers ná dit alles bestaan 'n sessie en word 'n koekie gestel.
'n Kopstuk waarvan net die bestaan nagegaan word, en niks anders nie
Die blaaier roep die BFF vir sy eie gebruikersinligting en om by die aangestuurde API uit te kom, en daardie oproepe dra 'n pasgemaakte kopstuk, x-authagonal-bff. Sy waarde is irrelevant. Die bestaan is die hele toets.
Dit lyk lui, en dit is dit nie. Die hele klas van kruistuiste-versoekvervalsing steun op 'n vorm-plasing, 'n beeldetiket, of 'n navigasie wat 'n ander oorsprong kan sneller terwyl jou koekie saamry, en niks daarvan kan 'n pasgemaakte kopstuk stel nie. Die oomblik wat aanvallende JavaScript probeer om een by te voeg, hou dit op om 'n eenvoudige versoek te wees en word 'n voorafgekeurde een, wat jou CORS-beleid weier. Die kopstuk is nie 'n geheim om te raai nie, dit is bewys dat die versoek van kode gekom het eerder as van opmerktaal.
Dit word op die geskripte oproepe vereis en doelbewus nie op dié wat van nature top-vlak-navigasies is nie: om 'n aanmelding te begin, om van die identiteitsverskaffer af terug te kom, om 'n afmeldskakel te volg. Om 'n pasgemaakte kopstuk op 'n blaaiernavigasie te eis, sou net die navigasie breek.
Verfris, presies een keer
Elke aangestuurde versoek kan ontdek dat die toegangstoken op die punt is om te verval, en 'n besige bladsy maak verskeie versoeke gelyktydig. Verfris hulle almal naïef en jy kry 'n klein ramp: verskeie gelyktydige verfrissings met 'n roterende verfrissingstoken, elkeen wat die ander ongeldig maak, en dit eindig met die gebruiker wat deur sy eie verkeer afgemeld word.
Daarom is verfrissing enkelvlug, per sessie. Die eerste versoek deur neem 'n hek wat op die sessie-id gesleutel is en almal anders wag. Die subtiele deel is wat die wagtes doen wanneer hulle inkom: hulle herlees die sessie uit die stoor voordat hulle enigiets besluit, want die versoek wat die hek eerste gehou het, het heel waarskynlik reeds verfris, en die waarde wat hulle vasgehou het toe hulle tou gestaan het, is verouderd. Verfris een keer, dan gebruik almal die uitslag.
Dit is ook rotasie-bewus in die vervelige, noodsaaklike sin. As die token-eindpunt 'n nuwe verfrissingstoken teruggee, vervang dit die gestoorde een. As verfrissing met 'n tokenfout misluk, word die sessie geskrap en die gebruiker afgemeld, want 'n verfrissingstoken wat verwerp is, gaan nie by die volgende poging aanvaar word nie, en om weer te probeer is net 'n stadiger manier om afgemeld te word.
Een BFF, baie huurders
'n Enkelhuurder-BFF los alles uit die konfigurasie op tydens opstart: een gesag, een kliënt-id, een geheim. Om baie huurders vanaf een ontplooiing te bedien breek dit, want nou moet die BFF uitwerk aan watter uitreiker 'n aanmelding behoort, en dit moet dit doen voordat enige sessie bestaan.
Keuse gebeur by die aanmeld-eindpunt, deur 'n navraagparameter wat jy mag benoem, sodat dit ?slug= of ?org= kan wees of wat ook al jou produk reeds 'n klant noem. 'n Oplosser verander daardie sleutel in 'n huurderkonfigurasie: gesag, kliënt-id, geheim, bestekke. As dit na niks oplos nie, word die aanmelding daar en dan geweier.
Dan moet die sleutel 'n rondrit na 'n identiteitsverskaffer oorleef wat niks daarvan weet nie, en terugkom. Daar is nog geen sessie om dit te hou nie, so dit ry in die korrelasiekoekie, die kortlewende geënkripteerde koekie wat reeds die PKCE-verifieerder, die toestand en die nonce om presies hierdie rede dra. Vyftien minute, een per aanmeldpoging. Op pad terug word die koekie gedekripteer, die huurder word weer uit die sleutel daarin opgelos, en die kode word teen daardie huurder se token-eindpunt uitgeruil. Eers dan word die huurdersleutel in die sessie geskryf, waar dit die blywende antwoord word.
Van daardie punt af los elke pad weer op uit die sessie eerder as uit enigiets wat die blaaier gesê het: verfrissing, afmelding, die instaner. En wanneer 'n huurder ophou oplos, omdat dit geskrap of gedeaktiveer is, word die sessie vernietig eerder as om stilweg na een of ander verstek-huurder terug te val. 'n Huurder wat nie opgelos kan word nie, is 'n afmelding, nie 'n skouerophaling nie.
Die versoek wat sonder koekie aankom
Terugkanaal-afmelding is waar hierdie ontwerp sy loon verdien. Die identiteitsverskaffer POST 'n afmeldtoken reguit na jou bediener wanneer 'n sessie elders eindig. Geen koekie. Geen sessie. Geen blaaier wat hoegenaamd betrokke is nie. In 'n enkelhuurder-BFF is dit onopmerklik, aangesien daar net een uitreiker is waarvandaan dit kan kom. In 'n meerhuurder-een moet jy "vir watter huurder is dit?" antwoord voordat jy "is dit eers werklik?" kan antwoord, en die enigste materiaal beskikbaar is die token self, wat jy nie geverifieer het nie en daarom nie kan vertrou nie.
Die ordening wat dit veilig maak, is die moeite werd om presies te stel. Lees die uitreiker-eis uit die ongeverifieerde token, en gebruik dit vir presies een ding: om te kies watter huurder se konfigurasie om te laai. Verifieer dan die token se handtekening teen daardie huurder se gepubliseerde sleutels en sy kliënt-id as die gehoor. 'n Vervalste uitreiker kies die huurder wat dit voorgee om te wees, en misluk dan verifikasie teen daardie huurder se sleutels, want die aanvaller het hulle nie. Niks is ooit op die sterkte van die onvertroude eis aanvaar nie. Die eis het net besluit wie dit mag beoordeel, en die beoordelaar is 'n vertrouensanker wat ons reeds gehad het.
Daarna kom die toetse wat 'n afmeldtoken 'n afmeldtoken maak: dit moet nie 'n nonce dra nie, aangesien dit niemand staaf nie; dit moet die terugkanaal-afmeldgebeurtenis dra; en dit moet óf 'n spesifieke sessie óf 'n subjek identifiseer. 'n Sessie-id maak een sessie dood. 'n Subjek maak elke sessie dood wat daardie gebruiker het, wat die vorm is wat ons uitreik, want "meld my oral af" is wat mense werklik bedoel wanneer hulle op oral afmeld klik. Dis waarom die sessiestoor 'n indeks volgens sessie-id en nog een volgens subjek hou, eerder as net volgens koekiewaarde.
Enkelhuurder bly vervelig
Dit alles is inert vir die algemene geval. Die verstek-oplosser gee dieselfde konfigurasie terug ongeag watter sleutel aan dit gegee word, en sy oplos-volgens-uitreiker gee daardie konfigurasie onvoorwaardelik terug, want daar is net een. 'n Enkelhuurder-BFF gedra hom presies soos dit voor die naat bestaan het: geen huurderparameter, niks ekstra in die koekie, geen nuwe falingsmodusse nie. Die meerhuurder-masjinerie is opsioneel en onsigbaar totdat jy 'n navraagparameter benoem.
Skei keuse van vertroue
Wanneer jy iets meerhuurder maak, is die oefening wat die moeite werd is om te doen, om elke toegangspunt op te noem en te vra wat die huurder op elkeen identifiseer. Blaaiernavigasies dra 'n koekie wat jy uitgereik het en beheer. Herleidingsterugroepe dra toestand wat jy geteken het. Bediener-tot-bediener-terugroepe dra net wat die oproeper gekies het om te stuur, en die oproeper lieg dalk.
Die ongemaklike toegangspunt, die een met niks betroubaars op nie, is die een waarvan die ontwerp besluit of die hele stelsel gesond is, en dit is gewoonlik die een waaraan niemand dink tot laat nie. Die patroon wat ons daardeur gebring het, veralgemeen: skei keuse van vertroue. Dit is reg om op onvertroude data te roeteer solank die roetering net kies watter vertrouensanker toegelaat word om die finale oproep te maak, en die finale oproep gemaak word deur iets wat die aanvaller nie kan vervals nie.
As jy dit alles liewer nie self wil skryf nie, verskaf Authagonal die BFF vir .NET en vir Node, en die portaal sal die vertroulike kliënt wat dit nodig het voorsien, herleidings-URI, terugkanaal-afmeldeindpunt en alles, in een klik.