[{"data":1,"prerenderedAt":3119},["ShallowReactive",2],{"navigation":3,"/features/browser-sessions":283,"/features/browser-sessions-surround":3114},[4,16,20,68,128,168,179,259,263,267,271,275,279],{"title":5,"path":6,"stem":7,"children":8},"Getting Started","/getting-started","1.getting-started",[9,12],{"title":10,"path":6,"stem":11},"Introduction","1.getting-started/index",{"title":13,"path":14,"stem":15},"Installation","/getting-started/installation","1.getting-started/1.installation",{"title":17,"path":18,"stem":19},"Upgrading","/upgrading","10.upgrading",{"title":21,"path":22,"stem":23,"children":24},"Essentials","/essentials","2.essentials",[25,28,32,36,40,44,48,52,56,60,64],{"title":26,"path":22,"stem":27},"Core concepts","2.essentials/index",{"title":29,"path":30,"stem":31},"Routing","/essentials/routing","2.essentials/1.routing",{"title":33,"path":34,"stem":35},"Typed Requests & Responses","/essentials/typed-io","2.essentials/10.typed-io",{"title":37,"path":38,"stem":39},"Authentication","/essentials/authentication","2.essentials/2.authentication",{"title":41,"path":42,"stem":43},"Identity & User Store","/essentials/identity","2.essentials/3.identity",{"title":45,"path":46,"stem":47},"Requests & Responses","/essentials/requests-responses","2.essentials/4.requests-responses",{"title":49,"path":50,"stem":51},"Controllers","/essentials/controllers","2.essentials/5.controllers",{"title":53,"path":54,"stem":55},"Database","/essentials/database","2.essentials/6.database",{"title":57,"path":58,"stem":59},"Validation","/essentials/validation","2.essentials/7.validation",{"title":61,"path":62,"stem":63},"Migrations","/essentials/migrations","2.essentials/8.migrations",{"title":65,"path":66,"stem":67},"ORM Models","/essentials/orm","2.essentials/9.orm",{"title":69,"path":70,"stem":71,"children":72},"Features","/features","3.features",[73,76,80,84,88,92,96,100,104,108,112,116,120,124],{"title":74,"path":70,"stem":75},"Built‑in Capabilities","3.features/index",{"title":77,"path":78,"stem":79},"API Keys & Scopes","/features/api-keys","3.features/api-keys",{"title":81,"path":82,"stem":83},"Browser Sessions (Cookie Auth)","/features/browser-sessions","3.features/browser-sessions",{"title":85,"path":86,"stem":87},"Caching","/features/caching","3.features/caching",{"title":89,"path":90,"stem":91},"CORS & CSRF","/features/cors-csrf","3.features/cors-csrf",{"title":93,"path":94,"stem":95},"Distributed Locks","/features/distributed-locks","3.features/distributed-locks",{"title":97,"path":98,"stem":99},"Events & Listeners","/features/events","3.features/events",{"title":101,"path":102,"stem":103},"Field Selection","/features/field-selection","3.features/field-selection",{"title":105,"path":106,"stem":107},"File Uploads","/features/file-uploads","3.features/file-uploads",{"title":109,"path":110,"stem":111},"Notifications","/features/notifications","3.features/notifications",{"title":113,"path":114,"stem":115},"Queues & Jobs","/features/queues-jobs","3.features/queues-jobs",{"title":117,"path":118,"stem":119},"Rate Limiting","/features/rate-limiting","3.features/rate-limiting",{"title":121,"path":122,"stem":123},"Task Scheduling","/features/scheduling","3.features/scheduling",{"title":125,"path":126,"stem":127},"Webhooks","/features/webhooks","3.features/webhooks",{"title":129,"path":130,"stem":131,"children":132},"Advanced","/advanced","4.advanced",[133,136,140,144,148,152,156,160,164],{"title":134,"path":130,"stem":135},"Techniques and Patterns","4.advanced/index",{"title":137,"path":138,"stem":139},"Configuration","/advanced/configuration","4.advanced/configuration",{"title":141,"path":142,"stem":143},"Dependency Injection","/advanced/dependency-injection","4.advanced/dependency-injection",{"title":145,"path":146,"stem":147},"Middleware","/advanced/middleware","4.advanced/middleware",{"title":149,"path":150,"stem":151},"OpenAPI Generation","/advanced/openapi","4.advanced/openapi",{"title":153,"path":154,"stem":155},"Performance","/advanced/performance","4.advanced/performance",{"title":157,"path":158,"stem":159},"Repositories","/advanced/repositories","4.advanced/repositories",{"title":161,"path":162,"stem":163},"Service Providers","/advanced/service-providers","4.advanced/service-providers",{"title":165,"path":166,"stem":167},"Testing","/advanced/testing","4.advanced/testing",{"title":169,"path":170,"stem":171,"children":172},"Overview","/tooling","5.tooling",[173,175],{"title":169,"path":170,"stem":174},"5.tooling/index",{"title":176,"path":177,"stem":178},"AI Skills","/tooling/ai-skills","5.tooling/1.ai-skills",{"title":180,"path":181,"stem":182,"children":183},"Cookbook","/cookbook","6.cookbook",[184,187,191,195,199,203,207,211,215,219,223,227,231,235,239,243,247,251,255],{"title":185,"path":181,"stem":186},"Recipes & How‑Tos","6.cookbook/index",{"title":188,"path":189,"stem":190},"Routing Recipes","/cookbook/routing","6.cookbook/1.routing",{"title":192,"path":193,"stem":194},"Caching Recipes","/cookbook/caching","6.cookbook/10.caching",{"title":196,"path":197,"stem":198},"Queue Infrastructure","/cookbook/queues-and-jobs","6.cookbook/11.queues-and-jobs",{"title":200,"path":201,"stem":202},"Notification Channels & Templates","/cookbook/notifications","6.cookbook/13.notifications",{"title":204,"path":205,"stem":206},"Storage","/cookbook/storage","6.cookbook/15.storage",{"title":208,"path":209,"stem":210},"Image Processing Examples","/cookbook/image-processing","6.cookbook/16.image-processing",{"title":212,"path":213,"stem":214},"Permissions and Authorization","/cookbook/permissions-and-authorization","6.cookbook/18.permissions-and-authorization",{"title":216,"path":217,"stem":218},"Session Analytics","/cookbook/sessions-analytics","6.cookbook/19.sessions-analytics",{"title":220,"path":221,"stem":222},"Writing Middleware","/cookbook/middleware","6.cookbook/2.middleware",{"title":224,"path":225,"stem":226},"API Metrics","/cookbook/api-metrics","6.cookbook/20.api-metrics",{"title":228,"path":229,"stem":230},"Performance Recipes","/cookbook/performance","6.cookbook/21.performance",{"title":232,"path":233,"stem":234},"Memory Management","/cookbook/memory-management","6.cookbook/22.memory-management",{"title":236,"path":237,"stem":238},"Writing Console Commands","/cookbook/console-commands","6.cookbook/23.console-commands",{"title":240,"path":241,"stem":242},"Glueful Extensions","/cookbook/extensions","6.cookbook/25.extensions",{"title":244,"path":245,"stem":246},"Error Handling","/cookbook/error-handling","6.cookbook/4.error-handling",{"title":248,"path":249,"stem":250},"Security Guide","/cookbook/security","6.cookbook/5.security",{"title":252,"path":253,"stem":254},"Service Options Resolver","/cookbook/configuration","6.cookbook/6.configuration",{"title":256,"path":257,"stem":258},"Logging","/cookbook/logging","6.cookbook/7.logging",{"title":260,"path":261,"stem":262},"API Reference","/api-reference","7.api-reference",{"title":264,"path":265,"stem":266},"CLI Reference","/cli-reference","8.cli-reference",{"title":268,"path":269,"stem":270},"Extensions","/extensions","9.extensions",{"title":272,"path":273,"stem":274},"Contributing","/contributing","contributing",{"title":276,"path":277,"stem":278},"Release Notes","/releases","releases",{"title":280,"path":281,"stem":282},"Release Archive","/releases-archive","releases-archive",{"id":284,"title":81,"body":285,"description":3108,"extension":3109,"links":3110,"meta":3111,"navigation":844,"path":82,"seo":3112,"stem":83,"__hash__":3113},"docs/3.features/browser-sessions.md",{"type":286,"value":287,"toc":3089},"minimark",[288,305,313,318,363,366,370,386,576,590,603,607,612,670,683,688,705,726,730,742,928,954,1013,1016,1020,1037,1063,1313,1338,1389,1404,1408,1426,1607,1621,1625,1628,1687,1700,1704,1711,1759,1762,1802,1808,1812,2438,2455,2459,2476,2554,2621,2624,2924,2934,2937,2940,2955,2973,2977,2995,3008,3021,3039,3053,3060,3064,3085],[289,290,291,292,296,297,300,301,304],"p",{},"Browser sessions are an opt-in cookie transport for first-party web apps. The browser keeps the access and refresh credentials in ",[293,294,295],"code",{},"HttpOnly"," cookies that page scripts can't read, so an XSS bug can't steal them. API clients keep sending ",[293,298,299],{},"Authorization: Bearer"," exactly as before. Both transports reach the same ",[293,302,303],{},"auth"," middleware, the same providers and the same server-side sessions.",[289,306,307,308,312],{},"The transport is ",[309,310,311],"strong",{},"off by default",". While it's disabled, the session routes aren't registered and nothing in the request path changes.",[314,315,317],"h2",{"id":316},"how-it-works","How It Works",[319,320,321,337,350,357],"ol",{},[322,323,324,325,328,329,332,333,336],"li",{},"The browser calls ",[293,326,327],{},"POST /auth/login"," with ",[293,330,331],{},"\"auth_transport\": \"cookie\"",". The login sets two cookies (through ",[293,334,335],{},"SessionCookieIssuer",") and returns a session-bound CSRF token.",[322,338,339,340,343,344,346,347,349],{},"On later requests, the ",[293,341,342],{},"session_cookie"," middleware reads the access cookie and turns it into the ",[293,345,299],{}," header that ",[293,348,303],{}," already understands.",[322,351,352,353,356],{},"When the access cookie expires, the browser calls ",[293,354,355],{},"POST /auth/session/refresh",". The refresh cookie is only ever sent to that path, and the endpoint rotates both cookies.",[322,358,359,362],{},[293,360,361],{},"POST /auth/session/logout"," revokes the server session and clears both cookies.",[289,364,365],{},"Tokens never appear in a response body on the cookie path.",[314,367,369],{"id":368},"enabling-it","Enabling It",[371,372,377],"pre",{"className":373,"code":374,"language":375,"meta":376,"style":376},"language-env shiki shiki-themes material-theme-lighter github-light github-dark monokai","SESSION_COOKIE_ENABLED=true\n","env","",[293,378,379],{"__ignoreMap":376},[380,381,384],"span",{"class":382,"line":383},"line",1,[380,385,374],{},[387,388,389,409],"table",{},[390,391,392],"thead",{},[393,394,395,399,406],"tr",{},[396,397,398],"th",{},"Variable",[396,400,401,402,405],{},"Config key (",[293,403,404],{},"auth.session_cookie.*",")",[396,407,408],{},"Default",[410,411,412,430,447,464,482,499,517,532,549],"tbody",{},[393,413,414,420,425],{},[415,416,417],"td",{},[293,418,419],{},"SESSION_COOKIE_ENABLED",[415,421,422],{},[293,423,424],{},"enabled",[415,426,427],{},[293,428,429],{},"false",[393,431,432,437,442],{},[415,433,434],{},[293,435,436],{},"SESSION_COOKIE_ACCESS_NAME",[415,438,439],{},[293,440,441],{},"access_name",[415,443,444],{},[293,445,446],{},"gf_session",[393,448,449,454,459],{},[415,450,451],{},[293,452,453],{},"SESSION_COOKIE_REFRESH_NAME",[415,455,456],{},[293,457,458],{},"refresh_name",[415,460,461],{},[293,462,463],{},"gf_refresh",[393,465,466,471,476],{},[415,467,468],{},[293,469,470],{},"SESSION_COOKIE_REFRESH_TTL",[415,472,473],{},[293,474,475],{},"refresh_ttl",[415,477,478,481],{},[293,479,480],{},"2592000"," (30 days)",[393,483,484,489,494],{},[415,485,486],{},[293,487,488],{},"SESSION_COOKIE_PATH",[415,490,491],{},[293,492,493],{},"path",[415,495,496],{},[293,497,498],{},"/",[393,500,501,506,511],{},[415,502,503],{},[293,504,505],{},"SESSION_COOKIE_REFRESH_PATH",[415,507,508],{},[293,509,510],{},"refresh_path",[415,512,513,514],{},"unset: ",[293,515,516],{},"api_prefix() . '/auth/session'",[393,518,519,524,529],{},[415,520,521],{},[293,522,523],{},"SESSION_COOKIE_DOMAIN",[415,525,526],{},[293,527,528],{},"domain",[415,530,531],{},"unset: host-only cookie",[393,533,534,539,544],{},[415,535,536],{},[293,537,538],{},"SESSION_COOKIE_SECURE",[415,540,541],{},[293,542,543],{},"secure",[415,545,546],{},[293,547,548],{},"true",[393,550,551,556,561],{},[415,552,553],{},[293,554,555],{},"SESSION_COOKIE_SAMESITE",[415,557,558],{},[293,559,560],{},"same_site",[415,562,563,566,567,569,570,569,573,405],{},[293,564,565],{},"lax"," (",[293,568,565],{},", ",[293,571,572],{},"strict",[293,574,575],{},"none",[289,577,578,579,582,583,569,585,569,587,589],{},"Change the cookie names if several Glueful apps share a parent domain, so their cookies don't collide. ",[293,580,581],{},"SameSite"," values other than ",[293,584,565],{},[293,586,572],{},[293,588,575],{}," or empty throw when a cookie is issued.",[289,591,592,595,596,598,599,602],{},[293,593,594],{},"SESSION_COOKIE_SECURE=true"," means browsers only send the cookies over HTTPS. For plain-HTTP local development, set it to ",[293,597,429],{}," in ",[293,600,601],{},".env"," and nowhere else.",[314,604,606],{"id":605},"the-cookies","The Cookies",[289,608,609,611],{},[293,610,335],{}," is the only place cookie attributes are set:",[387,613,614,630],{},[390,615,616],{},[393,617,618,621,624,627],{},[396,619,620],{},"Cookie",[396,622,623],{},"Holds",[396,625,626],{},"Path",[396,628,629],{},"Lifetime",[410,631,632,651],{},[393,633,634,638,641,645],{},[415,635,636],{},[293,637,446],{},[415,639,640],{},"Access token",[415,642,643],{},[293,644,498],{},[415,646,647,648],{},"The session's ",[293,649,650],{},"expires_in",[393,652,653,657,660,666],{},[415,654,655],{},[293,656,463],{},[415,658,659],{},"Refresh token",[415,661,662,665],{},[293,663,664],{},"/api/v1/auth/session"," (derived)",[415,667,668],{},[293,669,475],{},[289,671,672,673,569,675,678,679,682],{},"Both cookies are ",[293,674,295],{},[293,676,677],{},"Secure"," (per config) and ",[293,680,681],{},"SameSite=Lax"," (per config), and host-only unless you set a domain. Because of its path, the browser attaches the refresh cookie only to the refresh and logout endpoints, never to ordinary API calls.",[684,685,687],"h3",{"id":686},"refresh-cookie-path-191","Refresh cookie path (1.91)",[289,689,690,691,694,695,569,698,701,702,704],{},"The derived path follows your API prefix: ",[293,692,693],{},"rtrim(api_prefix(), '/') . '/auth/session'",". With the default ",[293,696,697],{},"API_USE_PREFIX=true",[293,699,700],{},"API_PREFIX=/api"," and versioning in the path, that's ",[293,703,664],{},". Issuing and clearing use the same path, so logout always removes the cookie that refresh set.",[706,707,711],"u-alert",{"color":708,"icon":709,"variant":710},"warning","i-lucide-triangle-alert","subtle",[289,712,713,714,717,718,721,722,725],{},"Before 1.91 the default was a bare ",[293,715,716],{},"/auth/session",". The session routes live under the API prefix, so browsers never sent the refresh cookie to them. If you set ",[293,719,720],{},"SESSION_COOKIE_REFRESH_PATH=/auth/session"," to work around that, ",[309,723,724],{},"remove the line"," so the derived path applies. Set it only if your session routes really are served somewhere else.",[314,727,729],{"id":728},"accepting-cookies-on-routes","Accepting Cookies on Routes",[289,731,732,733,735,736,735,739,741],{},"Add ",[293,734,342],{}," ",[309,737,738],{},"before",[293,740,303],{}," on every route that should accept the cookie:",[371,743,747],{"className":744,"code":745,"language":746,"meta":376,"style":376},"language-php shiki shiki-themes material-theme-lighter github-light github-dark monokai","$router->get('/account', [AccountController::class, 'show'])\n    ->middleware(['session_cookie', 'auth']);\n\n// Public pages that must survive a lapsed session: a bad or expired\n// cookie degrades to anonymous instead of a 401.\n$router->get('/feed', [FeedController::class, 'index'])\n    ->middleware(['session_cookie:optional', 'auth:optional']);\n","php",[293,748,749,810,839,846,853,859,901],{"__ignoreMap":376},[380,750,751,755,759,763,767,770,774,778,780,783,786,790,793,797,799,802,805,807],{"class":382,"line":383},[380,752,754],{"class":753},"swvn1","$",[380,756,758],{"class":757},"ss--_","router",[380,760,762],{"class":761},"sGXK2","->",[380,764,766],{"class":765},"sD0ED","get",[380,768,769],{"class":753},"(",[380,771,773],{"class":772},"siCPE","'",[380,775,777],{"class":776},"sLACW","/account",[380,779,773],{"class":772},[380,781,782],{"class":753},",",[380,784,785],{"class":753}," [",[380,787,789],{"class":788},"s_MOj","AccountController",[380,791,792],{"class":761},"::",[380,794,796],{"class":795},"shWJe","class",[380,798,782],{"class":753},[380,800,801],{"class":772}," '",[380,803,804],{"class":776},"show",[380,806,773],{"class":772},[380,808,809],{"class":753},"])\n",[380,811,813,816,819,822,824,826,828,830,832,834,836],{"class":382,"line":812},2,[380,814,815],{"class":761},"    ->",[380,817,818],{"class":765},"middleware",[380,820,821],{"class":753},"([",[380,823,773],{"class":772},[380,825,342],{"class":776},[380,827,773],{"class":772},[380,829,782],{"class":753},[380,831,801],{"class":772},[380,833,303],{"class":776},[380,835,773],{"class":772},[380,837,838],{"class":753},"]);\n",[380,840,842],{"class":382,"line":841},3,[380,843,845],{"emptyLinePlaceholder":844},true,"\n",[380,847,849],{"class":382,"line":848},4,[380,850,852],{"class":851},"ss7Ak","// Public pages that must survive a lapsed session: a bad or expired\n",[380,854,856],{"class":382,"line":855},5,[380,857,858],{"class":851},"// cookie degrades to anonymous instead of a 401.\n",[380,860,862,864,866,868,870,872,874,877,879,881,883,886,888,890,892,894,897,899],{"class":382,"line":861},6,[380,863,754],{"class":753},[380,865,758],{"class":757},[380,867,762],{"class":761},[380,869,766],{"class":765},[380,871,769],{"class":753},[380,873,773],{"class":772},[380,875,876],{"class":776},"/feed",[380,878,773],{"class":772},[380,880,782],{"class":753},[380,882,785],{"class":753},[380,884,885],{"class":788},"FeedController",[380,887,792],{"class":761},[380,889,796],{"class":795},[380,891,782],{"class":753},[380,893,801],{"class":772},[380,895,896],{"class":776},"index",[380,898,773],{"class":772},[380,900,809],{"class":753},[380,902,904,906,908,910,912,915,917,919,921,924,926],{"class":382,"line":903},7,[380,905,815],{"class":761},[380,907,818],{"class":765},[380,909,821],{"class":753},[380,911,773],{"class":772},[380,913,914],{"class":776},"session_cookie:optional",[380,916,773],{"class":772},[380,918,782],{"class":753},[380,920,801],{"class":772},[380,922,923],{"class":776},"auth:optional",[380,925,773],{"class":772},[380,927,838],{"class":753},[289,929,930,931,934,935,938,939,942,943,945,946,949,950,953],{},"The middleware decides which credential the request is using and records it in the ",[293,932,933],{},"auth_transport"," request attribute (",[293,936,937],{},"cookie"," or ",[293,940,941],{},"bearer","). It doesn't set identity attributes itself; ",[293,944,303],{}," populates ",[293,947,948],{},"user"," and ",[293,951,952],{},"auth.user"," right afterwards.",[387,955,956,966],{},[390,957,958],{},[393,959,960,963],{},[396,961,962],{},"Request carries",[396,964,965],{},"Result",[410,967,968,982,993,1003],{},[393,969,970,973],{},[415,971,972],{},"Cookie only",[415,974,975,976,569,979],{},"Cookie becomes ",[293,977,978],{},"Authorization: Bearer …",[293,980,981],{},"auth_transport=cookie",[393,983,984,987],{},[415,985,986],{},"Bearer header only",[415,988,989,990],{},"Untouched, ",[293,991,992],{},"auth_transport=bearer",[393,994,995,998],{},[415,996,997],{},"Both, same identity",[415,999,1000,1001],{},"Bearer wins, ",[293,1002,992],{},[393,1004,1005,1008],{},[415,1006,1007],{},"Both, different identities",[415,1009,1010],{},[293,1011,1012],{},"401 Conflicting credentials.",[289,1014,1015],{},"The middleware never refreshes on its own: the refresh cookie isn't sent to ordinary routes, so it can't. Clients call the refresh endpoint explicitly.",[314,1017,1019],{"id":1018},"issuing-cookies-at-login","Issuing Cookies at Login",[289,1021,1022,1023,1025,1026,1028,1029,1032,1033,1036],{},"The built-in ",[293,1024,327],{}," serves both transports. Add ",[293,1027,331],{}," to the body and, while ",[293,1030,1031],{},"SESSION_COOKIE_ENABLED=true",", a successful password login sets the session cookies and returns ",[309,1034,1035],{},"no tokens"," in the body:",[371,1038,1042],{"className":1039,"code":1040,"language":1041,"meta":376,"style":376},"language-http shiki shiki-themes material-theme-lighter github-light github-dark monokai","POST /api/v1/auth/login\nContent-Type: application/json\n\n{\"username\": \"ada@example.com\", \"password\": \"…\", \"auth_transport\": \"cookie\"}\n","http",[293,1043,1044,1049,1054,1058],{"__ignoreMap":376},[380,1045,1046],{"class":382,"line":383},[380,1047,1048],{},"POST /api/v1/auth/login\n",[380,1050,1051],{"class":382,"line":812},[380,1052,1053],{},"Content-Type: application/json\n",[380,1055,1056],{"class":382,"line":841},[380,1057,845],{"emptyLinePlaceholder":844},[380,1059,1060],{"class":382,"line":848},[380,1061,1062],{},"{\"username\": \"ada@example.com\", \"password\": \"…\", \"auth_transport\": \"cookie\"}\n",[371,1064,1068],{"className":1065,"code":1066,"language":1067,"meta":376,"style":376},"language-json shiki shiki-themes material-theme-lighter github-light github-dark monokai","{\n  \"success\": true,\n  \"message\": \"Login successful\",\n  \"data\": {\n    \"expires_in\": 900,\n    \"session_uuid\": \"…\",\n    \"user\": { \"…\": \"…\" },\n    \"csrf_token\": {\n      \"token\": \"…\",\n      \"header\": \"X-CSRF-Token\",\n      \"field\": \"_token\",\n      \"expires_at\": 1793113391\n    }\n  }\n}\n","json",[293,1069,1070,1075,1098,1121,1135,1152,1172,1203,1217,1238,1259,1280,1295,1301,1307],{"__ignoreMap":376},[380,1071,1072],{"class":382,"line":383},[380,1073,1074],{"class":753},"{\n",[380,1076,1077,1081,1085,1088,1091,1095],{"class":382,"line":812},[380,1078,1080],{"class":1079},"saDeg","  \"",[380,1082,1084],{"class":1083},"sEff5","success",[380,1086,1087],{"class":1079},"\"",[380,1089,1090],{"class":753},":",[380,1092,1094],{"class":1093},"sMTiH"," true",[380,1096,1097],{"class":753},",\n",[380,1099,1100,1102,1105,1107,1109,1113,1117,1119],{"class":382,"line":841},[380,1101,1080],{"class":1079},[380,1103,1104],{"class":1083},"message",[380,1106,1087],{"class":1079},[380,1108,1090],{"class":753},[380,1110,1112],{"class":1111},"sh1VR"," \"",[380,1114,1116],{"class":1115},"sINAO","Login successful",[380,1118,1087],{"class":1111},[380,1120,1097],{"class":753},[380,1122,1123,1125,1128,1130,1132],{"class":382,"line":848},[380,1124,1080],{"class":1079},[380,1126,1127],{"class":1083},"data",[380,1129,1087],{"class":1079},[380,1131,1090],{"class":753},[380,1133,1134],{"class":753}," {\n",[380,1136,1137,1140,1142,1144,1146,1150],{"class":382,"line":855},[380,1138,1139],{"class":1079},"    \"",[380,1141,650],{"class":788},[380,1143,1087],{"class":1079},[380,1145,1090],{"class":753},[380,1147,1149],{"class":1148},"sYThS"," 900",[380,1151,1097],{"class":753},[380,1153,1154,1156,1159,1161,1163,1165,1168,1170],{"class":382,"line":861},[380,1155,1139],{"class":1079},[380,1157,1158],{"class":788},"session_uuid",[380,1160,1087],{"class":1079},[380,1162,1090],{"class":753},[380,1164,1112],{"class":1111},[380,1166,1167],{"class":1115},"…",[380,1169,1087],{"class":1111},[380,1171,1097],{"class":753},[380,1173,1174,1176,1178,1180,1182,1185,1187,1190,1192,1194,1196,1198,1200],{"class":382,"line":903},[380,1175,1139],{"class":1079},[380,1177,948],{"class":788},[380,1179,1087],{"class":1079},[380,1181,1090],{"class":753},[380,1183,1184],{"class":753}," {",[380,1186,1112],{"class":1079},[380,1188,1167],{"class":1189},"sTC9v",[380,1191,1087],{"class":1079},[380,1193,1090],{"class":753},[380,1195,1112],{"class":1111},[380,1197,1167],{"class":1115},[380,1199,1087],{"class":1111},[380,1201,1202],{"class":753}," },\n",[380,1204,1206,1208,1211,1213,1215],{"class":382,"line":1205},8,[380,1207,1139],{"class":1079},[380,1209,1210],{"class":788},"csrf_token",[380,1212,1087],{"class":1079},[380,1214,1090],{"class":753},[380,1216,1134],{"class":753},[380,1218,1220,1223,1226,1228,1230,1232,1234,1236],{"class":382,"line":1219},9,[380,1221,1222],{"class":1079},"      \"",[380,1224,1225],{"class":1189},"token",[380,1227,1087],{"class":1079},[380,1229,1090],{"class":753},[380,1231,1112],{"class":1111},[380,1233,1167],{"class":1115},[380,1235,1087],{"class":1111},[380,1237,1097],{"class":753},[380,1239,1241,1243,1246,1248,1250,1252,1255,1257],{"class":382,"line":1240},10,[380,1242,1222],{"class":1079},[380,1244,1245],{"class":1189},"header",[380,1247,1087],{"class":1079},[380,1249,1090],{"class":753},[380,1251,1112],{"class":1111},[380,1253,1254],{"class":1115},"X-CSRF-Token",[380,1256,1087],{"class":1111},[380,1258,1097],{"class":753},[380,1260,1262,1264,1267,1269,1271,1273,1276,1278],{"class":382,"line":1261},11,[380,1263,1222],{"class":1079},[380,1265,1266],{"class":1189},"field",[380,1268,1087],{"class":1079},[380,1270,1090],{"class":753},[380,1272,1112],{"class":1111},[380,1274,1275],{"class":1115},"_token",[380,1277,1087],{"class":1111},[380,1279,1097],{"class":753},[380,1281,1283,1285,1288,1290,1292],{"class":382,"line":1282},12,[380,1284,1222],{"class":1079},[380,1286,1287],{"class":1189},"expires_at",[380,1289,1087],{"class":1079},[380,1291,1090],{"class":753},[380,1293,1294],{"class":1148}," 1793113391\n",[380,1296,1298],{"class":382,"line":1297},13,[380,1299,1300],{"class":753},"    }\n",[380,1302,1304],{"class":382,"line":1303},14,[380,1305,1306],{"class":753},"  }\n",[380,1308,1310],{"class":382,"line":1309},15,[380,1311,1312],{"class":753},"}\n",[289,1314,1315,569,1318,949,1321,1324,1325,1327,1328,1330,1331,498,1334,1337],{},[293,1316,1317],{},"access_token",[293,1319,1320],{},"refresh_token",[293,1322,1323],{},"token_type"," are stripped from the body; they live only in the ",[293,1326,295],{}," cookies. The ",[293,1329,1210],{}," is bound to the new session, so the SPA can send it on its first cookie-authenticated write. The cookie-transport response doesn't fire the ",[293,1332,1333],{},"LoginResponseBuilding",[293,1335,1336],{},"LoginResponseBuilt"," events, so a listener can't put tokens back into it.",[1339,1340,1341,1350,1356,1365],"ul",{},[322,1342,1343,1344,1346,1347,1349],{},"Without the field, or with any other value, the login is the bearer path: tokens in the body, no cookies. Bearer login responses also include ",[293,1345,1158],{}," and a session-bound ",[293,1348,1210],{},".",[322,1351,1352,1353,1355],{},"While the transport is disabled, ",[293,1354,331],{}," is ignored and the bearer path runs.",[322,1357,1358,1359,949,1361,1364],{},"Only username/password logins honour it. ",[293,1360,1225],{},[293,1362,1363],{},"api_key"," provider logins are always shaped as before.",[322,1366,1367,1368,1371,1372,1375,1376,1379,1380,949,1382,1384,1385,1388],{},"A login that needs a second factor returns the ",[293,1369,1370],{},"two_factor_required"," challenge and sets no cookies: there is no session yet. Finish it at ",[293,1373,1374],{},"POST /2fa/verify"," with the ",[293,1377,1378],{},"challenge_token",", the ",[293,1381,293],{},[293,1383,331],{}," again; that response sets the cookies (",[293,1386,1387],{},"glueful/users"," 2.5.1+).",[706,1390,1393],{"color":1391,"icon":1392,"variant":710},"info","i-lucide-info",[289,1394,1395,1398,1399,328,1401,1403],{},[309,1396,1397],{},"Since 1.91:"," if you wrote your own cookie login controller only because the core login couldn't set cookies, you can switch the SPA to ",[293,1400,327],{},[293,1402,331],{}," and remove it.",[684,1405,1407],{"id":1406},"custom-login-flows","Custom login flows",[289,1409,1410,1411,1414,1415,1417,1418,1421,1422,1425],{},"For a login the core endpoint doesn't cover, use the same building blocks it does: ",[293,1412,1413],{},"LoginOrchestrator"," (the credential check and two-factor gate) and ",[293,1416,335],{},". ",[293,1419,1420],{},"issue()"," accepts only an ",[293,1423,1424],{},"AuthenticatedSession",", and a login still waiting for its second factor can't produce one, so setting cookies for an unfinished login can't be written at all:",[371,1427,1429],{"className":744,"code":1428,"language":746,"meta":376,"style":376},"$outcome = $this->orchestrator->login(['username' => $username, 'password' => $password]);\n\nif ($outcome->isAuthenticated()) {\n    return $this->issuer->issue(\n        new JsonResponse(['success' => true, 'user' => $outcome->session()->user]),\n        $outcome->session(),\n    );\n}\n",[293,1430,1431,1489,1493,1515,1535,1584,1598,1603],{"__ignoreMap":376},[380,1432,1433,1435,1438,1441,1445,1447,1450,1452,1455,1457,1459,1462,1464,1467,1470,1472,1474,1476,1479,1481,1483,1485,1487],{"class":382,"line":383},[380,1434,754],{"class":753},[380,1436,1437],{"class":757},"outcome ",[380,1439,1440],{"class":761},"=",[380,1442,1444],{"class":1443},"sSBr1"," $this",[380,1446,762],{"class":761},[380,1448,1449],{"class":757},"orchestrator",[380,1451,762],{"class":761},[380,1453,1454],{"class":765},"login",[380,1456,821],{"class":753},[380,1458,773],{"class":772},[380,1460,1461],{"class":776},"username",[380,1463,773],{"class":772},[380,1465,1466],{"class":761}," =>",[380,1468,1469],{"class":753}," $",[380,1471,1461],{"class":757},[380,1473,782],{"class":753},[380,1475,801],{"class":772},[380,1477,1478],{"class":776},"password",[380,1480,773],{"class":772},[380,1482,1466],{"class":761},[380,1484,1469],{"class":753},[380,1486,1478],{"class":757},[380,1488,838],{"class":753},[380,1490,1491],{"class":382,"line":812},[380,1492,845],{"emptyLinePlaceholder":844},[380,1494,1495,1499,1502,1505,1507,1510,1513],{"class":382,"line":841},[380,1496,1498],{"class":1497},"sRxSC","if",[380,1500,1501],{"class":753}," ($",[380,1503,1504],{"class":757},"outcome",[380,1506,762],{"class":761},[380,1508,1509],{"class":765},"isAuthenticated",[380,1511,1512],{"class":753},"())",[380,1514,1134],{"class":753},[380,1516,1517,1520,1522,1524,1527,1529,1532],{"class":382,"line":848},[380,1518,1519],{"class":1497},"    return",[380,1521,1444],{"class":1443},[380,1523,762],{"class":761},[380,1525,1526],{"class":757},"issuer",[380,1528,762],{"class":761},[380,1530,1531],{"class":765},"issue",[380,1533,1534],{"class":753},"(\n",[380,1536,1537,1540,1543,1545,1547,1549,1551,1553,1555,1557,1559,1561,1563,1565,1567,1569,1571,1574,1577,1579,1581],{"class":382,"line":855},[380,1538,1539],{"class":795},"        new",[380,1541,1542],{"class":788}," JsonResponse",[380,1544,821],{"class":753},[380,1546,773],{"class":772},[380,1548,1084],{"class":776},[380,1550,773],{"class":772},[380,1552,1466],{"class":761},[380,1554,1094],{"class":1093},[380,1556,782],{"class":753},[380,1558,801],{"class":772},[380,1560,948],{"class":776},[380,1562,773],{"class":772},[380,1564,1466],{"class":761},[380,1566,1469],{"class":753},[380,1568,1504],{"class":757},[380,1570,762],{"class":761},[380,1572,1573],{"class":765},"session",[380,1575,1576],{"class":753},"()",[380,1578,762],{"class":761},[380,1580,948],{"class":757},[380,1582,1583],{"class":753},"]),\n",[380,1585,1586,1589,1591,1593,1595],{"class":382,"line":861},[380,1587,1588],{"class":753},"        $",[380,1590,1504],{"class":757},[380,1592,762],{"class":761},[380,1594,1573],{"class":765},[380,1596,1597],{"class":753},"(),\n",[380,1599,1600],{"class":382,"line":903},[380,1601,1602],{"class":753},"    );\n",[380,1604,1605],{"class":382,"line":1205},[380,1606,1312],{"class":753},[289,1608,1609,1610,1613,1614,1616,1617,1620],{},"Register such a controller in your provider's ",[293,1611,1612],{},"services()",", route it without ",[293,1615,303],{},", and add the login rate limit (",[293,1618,1619],{},"rate_limit:5,60",").",[314,1622,1624],{"id":1623},"refresh-and-logout","Refresh and Logout",[289,1626,1627],{},"Both endpoints are registered under the API prefix only when the transport is enabled:",[387,1629,1630,1643],{},[390,1631,1632],{},[393,1633,1634,1637,1640],{},[396,1635,1636],{},"Endpoint",[396,1638,1639],{},"Does",[396,1641,1642],{},"Responses",[410,1644,1645,1667],{},[393,1646,1647,1651,1654],{},[415,1648,1649],{},[293,1650,355],{},[415,1652,1653],{},"Rotates both cookies using the refresh cookie (never a body field). Rate limited to 30 per minute",[415,1655,1656,569,1659,1662,1663,1666],{},[293,1657,1658],{},"200",[293,1660,1661],{},"401 Session expired."," (and both cookies cleared when the refresh token is dead), ",[293,1664,1665],{},"403"," if the origin isn't accepted",[393,1668,1669,1673,1676],{},[415,1670,1671],{},[293,1672,361],{},[415,1674,1675],{},"Revokes the server session and expires both cookies",[415,1677,1678,569,1680,1682,1683,1686],{},[293,1679,1658],{},[293,1681,1665],{}," if the origin isn't accepted, ",[293,1684,1685],{},"500 Logout incomplete."," if revocation failed",[289,1688,1689,1690,1693,1694,949,1697,1349],{},"If server-side revocation fails, logout still clears the cookies but returns ",[293,1691,1692],{},"500",". A logout that leaves the server session alive isn't reported as a success. Bearer clients keep using ",[293,1695,1696],{},"POST /auth/refresh-token",[293,1698,1699],{},"POST /auth/logout",[684,1701,1703],{"id":1702},"origin-check","Origin check",[289,1705,1706,1707,1710],{},"These endpoints authenticate with nothing but a cookie, so they can't depend on a CSRF token. Instead they require proof of where the request came from (",[293,1708,1709],{},"SameOriginGuard","):",[1339,1712,1713,1750,1756],{},[322,1714,1715,1716,1719,1720,1723,1724,1727,1728,1731,1732,1735,1736,1739,1740,1743,1744,569,1747,1749],{},"If ",[293,1717,1718],{},"Sec-Fetch-Site"," is present, ",[293,1721,1722],{},"same-origin"," passes. ",[293,1725,1726],{},"same-site"," (a sibling subdomain) passes ",[309,1729,1730],{},"only"," when the request's ",[293,1733,1734],{},"Origin"," is an explicitly allowed CORS origin and credentials are enabled: an exact ",[293,1737,1738],{},"CORS_ALLOWED_ORIGINS"," entry or pattern match, ",[293,1741,1742],{},"CORS_SUPPORTS_CREDENTIALS=true",", no wildcard. Any other value (",[293,1745,1746],{},"cross-site",[293,1748,575],{},") is rejected.",[322,1751,1752,1753,1755],{},"Otherwise ",[293,1754,1734],{}," must exactly equal the request's own scheme, host and port.",[322,1757,1758],{},"A request with neither header is rejected.",[289,1760,1761],{},"Two deployments work:",[1339,1763,1764,1778],{},[322,1765,1766,1769,1770,1773,1774,1777],{},[309,1767,1768],{},"Single origin (simplest)."," Serve the SPA at ",[293,1771,1772],{},"https://app.example.com"," and proxy ",[293,1775,1776],{},"https://app.example.com/api/*"," to Glueful.",[322,1779,1780,1783,1784,1786,1787,1790,1791,1796,1797,1799,1800,1349],{},[309,1781,1782],{},"Split subdomains."," SPA on ",[293,1785,1772],{},", API on ",[293,1788,1789],{},"https://api.example.com",". Allow the SPA's exact origin with credentials (see ",[1792,1793,1795],"a",{"href":1794},"#cors","CORS","); then refresh and logout accept its ",[293,1798,1726],{}," requests. Without that, they return ",[293,1801,1665],{},[289,1803,1804,1805,1807],{},"A page on a different site (",[293,1806,1746],{},") is always refused.",[314,1809,1811],{"id":1810},"using-it-from-a-spa","Using It from a SPA",[371,1813,1817],{"className":1814,"code":1815,"language":1816,"meta":376,"style":376},"language-javascript shiki shiki-themes material-theme-lighter github-light github-dark monokai","const api = (path, init = {}) =>\n  fetch(`/api/v1${path}`, { credentials: 'include', ...init });\n\n// Log in with the cookie transport; keep the session-bound CSRF token\nconst login = await api('/auth/login', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ username, password, auth_transport: 'cookie' }),\n});\nlet csrfToken = (await login.json()).data.csrf_token?.token;\n\n// Reads: on 401, refresh once and retry\nasync function get(path) {\n  let res = await api(path);\n  if (res.status === 401) {\n    const refreshed = await api('/auth/session/refresh', { method: 'POST' });\n    if (!refreshed.ok) throw new Error('Session expired');\n    res = await api(path);\n  }\n  return res;\n}\n\n// Writes: send the CSRF token, and don't replay them automatically\nawait api('/posts', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken },\n  body: JSON.stringify(post),\n});\n\nawait api('/auth/session/logout', { method: 'POST' });\n","javascript",[293,1818,1819,1850,1906,1910,1915,1943,1959,1987,2035,2043,2081,2085,2090,2110,2132,2158,2203,2246,2266,2271,2281,2286,2291,2297,2317,2332,2370,2388,2397,2402],{"__ignoreMap":376},[380,1820,1821,1825,1829,1832,1834,1837,1839,1842,1844,1847],{"class":382,"line":383},[380,1822,1824],{"class":1823},"srJo8","const",[380,1826,1828],{"class":1827},"sh7YU"," api",[380,1830,1831],{"class":761}," =",[380,1833,566],{"class":753},[380,1835,493],{"class":1836},"sQgqH",[380,1838,782],{"class":753},[380,1840,1841],{"class":1836}," init",[380,1843,1831],{"class":761},[380,1845,1846],{"class":753}," {})",[380,1848,1849],{"class":1823}," =>\n",[380,1851,1852,1855,1857,1860,1863,1867,1869,1872,1874,1876,1878,1882,1884,1886,1889,1891,1893,1896,1899,1901,1903],{"class":382,"line":812},[380,1853,1854],{"class":765},"  fetch",[380,1856,769],{"class":757},[380,1858,1859],{"class":772},"`",[380,1861,1862],{"class":776},"/api/v1",[380,1864,1866],{"class":1865},"slF1C","${",[380,1868,493],{"class":757},[380,1870,1871],{"class":1865},"}",[380,1873,1859],{"class":772},[380,1875,782],{"class":753},[380,1877,1184],{"class":753},[380,1879,1881],{"class":1880},"squCx"," credentials",[380,1883,1090],{"class":753},[380,1885,801],{"class":772},[380,1887,1888],{"class":776},"include",[380,1890,773],{"class":772},[380,1892,782],{"class":753},[380,1894,1895],{"class":761}," ...",[380,1897,1898],{"class":757},"init ",[380,1900,1871],{"class":753},[380,1902,405],{"class":757},[380,1904,1905],{"class":753},";\n",[380,1907,1908],{"class":382,"line":841},[380,1909,845],{"emptyLinePlaceholder":844},[380,1911,1912],{"class":382,"line":848},[380,1913,1914],{"class":851},"// Log in with the cookie transport; keep the session-bound CSRF token\n",[380,1916,1917,1919,1923,1925,1928,1930,1932,1934,1937,1939,1941],{"class":382,"line":855},[380,1918,1824],{"class":1823},[380,1920,1922],{"class":1921},"s91G_"," login",[380,1924,1831],{"class":761},[380,1926,1927],{"class":1497}," await",[380,1929,1828],{"class":765},[380,1931,769],{"class":757},[380,1933,773],{"class":772},[380,1935,1936],{"class":776},"/auth/login",[380,1938,773],{"class":772},[380,1940,782],{"class":753},[380,1942,1134],{"class":753},[380,1944,1945,1948,1950,1952,1955,1957],{"class":382,"line":861},[380,1946,1947],{"class":1880},"  method",[380,1949,1090],{"class":753},[380,1951,801],{"class":772},[380,1953,1954],{"class":776},"POST",[380,1956,773],{"class":772},[380,1958,1097],{"class":753},[380,1960,1961,1964,1966,1968,1970,1974,1976,1978,1980,1983,1985],{"class":382,"line":903},[380,1962,1963],{"class":1880},"  headers",[380,1965,1090],{"class":753},[380,1967,1184],{"class":753},[380,1969,801],{"class":772},[380,1971,1973],{"class":1972},"sJhdN","Content-Type",[380,1975,773],{"class":772},[380,1977,1090],{"class":753},[380,1979,801],{"class":772},[380,1981,1982],{"class":776},"application/json",[380,1984,773],{"class":772},[380,1986,1202],{"class":753},[380,1988,1989,1992,1994,1997,1999,2002,2004,2007,2010,2012,2015,2017,2020,2022,2024,2026,2028,2031,2033],{"class":382,"line":1205},[380,1990,1991],{"class":1880},"  body",[380,1993,1090],{"class":753},[380,1995,1996],{"class":1921}," JSON",[380,1998,1349],{"class":753},[380,2000,2001],{"class":765},"stringify",[380,2003,769],{"class":757},[380,2005,2006],{"class":753},"{",[380,2008,2009],{"class":757}," username",[380,2011,782],{"class":753},[380,2013,2014],{"class":757}," password",[380,2016,782],{"class":753},[380,2018,2019],{"class":1880}," auth_transport",[380,2021,1090],{"class":753},[380,2023,801],{"class":772},[380,2025,937],{"class":776},[380,2027,773],{"class":772},[380,2029,2030],{"class":753}," }",[380,2032,405],{"class":757},[380,2034,1097],{"class":753},[380,2036,2037,2039,2041],{"class":382,"line":1219},[380,2038,1871],{"class":753},[380,2040,405],{"class":757},[380,2042,1905],{"class":753},[380,2044,2045,2048,2051,2053,2055,2058,2060,2062,2064,2066,2068,2070,2072,2074,2077,2079],{"class":382,"line":1240},[380,2046,2047],{"class":1823},"let",[380,2049,2050],{"class":757}," csrfToken ",[380,2052,1440],{"class":761},[380,2054,566],{"class":757},[380,2056,2057],{"class":1497},"await",[380,2059,1922],{"class":757},[380,2061,1349],{"class":753},[380,2063,1067],{"class":765},[380,2065,1512],{"class":757},[380,2067,1349],{"class":753},[380,2069,1127],{"class":757},[380,2071,1349],{"class":753},[380,2073,1210],{"class":757},[380,2075,2076],{"class":753},"?.",[380,2078,1225],{"class":757},[380,2080,1905],{"class":753},[380,2082,2083],{"class":382,"line":1261},[380,2084,845],{"emptyLinePlaceholder":844},[380,2086,2087],{"class":382,"line":1282},[380,2088,2089],{"class":851},"// Reads: on 401, refresh once and retry\n",[380,2091,2092,2096,2099,2102,2104,2106,2108],{"class":382,"line":1297},[380,2093,2095],{"class":2094},"sTNss","async",[380,2097,2098],{"class":1823}," function",[380,2100,2101],{"class":765}," get",[380,2103,769],{"class":753},[380,2105,493],{"class":1836},[380,2107,405],{"class":753},[380,2109,1134],{"class":753},[380,2111,2112,2115,2118,2120,2122,2124,2126,2128,2130],{"class":382,"line":1303},[380,2113,2114],{"class":1823},"  let",[380,2116,2117],{"class":757}," res",[380,2119,1831],{"class":761},[380,2121,1927],{"class":1497},[380,2123,1828],{"class":765},[380,2125,769],{"class":1880},[380,2127,493],{"class":757},[380,2129,405],{"class":1880},[380,2131,1905],{"class":753},[380,2133,2134,2137,2139,2142,2144,2147,2150,2153,2156],{"class":382,"line":1309},[380,2135,2136],{"class":1497},"  if",[380,2138,566],{"class":1880},[380,2140,2141],{"class":757},"res",[380,2143,1349],{"class":753},[380,2145,2146],{"class":757},"status",[380,2148,2149],{"class":761}," ===",[380,2151,2152],{"class":1148}," 401",[380,2154,2155],{"class":1880},") ",[380,2157,1074],{"class":753},[380,2159,2161,2164,2167,2169,2171,2173,2175,2177,2180,2182,2184,2186,2189,2191,2193,2195,2197,2199,2201],{"class":382,"line":2160},16,[380,2162,2163],{"class":1823},"    const",[380,2165,2166],{"class":1921}," refreshed",[380,2168,1831],{"class":761},[380,2170,1927],{"class":1497},[380,2172,1828],{"class":765},[380,2174,769],{"class":1880},[380,2176,773],{"class":772},[380,2178,2179],{"class":776},"/auth/session/refresh",[380,2181,773],{"class":772},[380,2183,782],{"class":753},[380,2185,1184],{"class":753},[380,2187,2188],{"class":1880}," method",[380,2190,1090],{"class":753},[380,2192,801],{"class":772},[380,2194,1954],{"class":776},[380,2196,773],{"class":772},[380,2198,2030],{"class":753},[380,2200,405],{"class":1880},[380,2202,1905],{"class":753},[380,2204,2206,2209,2211,2214,2217,2219,2222,2224,2227,2230,2233,2235,2237,2240,2242,2244],{"class":382,"line":2205},17,[380,2207,2208],{"class":1497},"    if",[380,2210,566],{"class":1880},[380,2212,2213],{"class":761},"!",[380,2215,2216],{"class":757},"refreshed",[380,2218,1349],{"class":753},[380,2220,2221],{"class":757},"ok",[380,2223,2155],{"class":1880},[380,2225,2226],{"class":1497},"throw",[380,2228,2229],{"class":761}," new",[380,2231,2232],{"class":765}," Error",[380,2234,769],{"class":1880},[380,2236,773],{"class":772},[380,2238,2239],{"class":776},"Session expired",[380,2241,773],{"class":772},[380,2243,405],{"class":1880},[380,2245,1905],{"class":753},[380,2247,2249,2252,2254,2256,2258,2260,2262,2264],{"class":382,"line":2248},18,[380,2250,2251],{"class":757},"    res",[380,2253,1831],{"class":761},[380,2255,1927],{"class":1497},[380,2257,1828],{"class":765},[380,2259,769],{"class":1880},[380,2261,493],{"class":757},[380,2263,405],{"class":1880},[380,2265,1905],{"class":753},[380,2267,2269],{"class":382,"line":2268},19,[380,2270,1306],{"class":753},[380,2272,2274,2277,2279],{"class":382,"line":2273},20,[380,2275,2276],{"class":1497},"  return",[380,2278,2117],{"class":757},[380,2280,1905],{"class":753},[380,2282,2284],{"class":382,"line":2283},21,[380,2285,1312],{"class":753},[380,2287,2289],{"class":382,"line":2288},22,[380,2290,845],{"emptyLinePlaceholder":844},[380,2292,2294],{"class":382,"line":2293},23,[380,2295,2296],{"class":851},"// Writes: send the CSRF token, and don't replay them automatically\n",[380,2298,2300,2302,2304,2306,2308,2311,2313,2315],{"class":382,"line":2299},24,[380,2301,2057],{"class":1497},[380,2303,1828],{"class":765},[380,2305,769],{"class":757},[380,2307,773],{"class":772},[380,2309,2310],{"class":776},"/posts",[380,2312,773],{"class":772},[380,2314,782],{"class":753},[380,2316,1134],{"class":753},[380,2318,2320,2322,2324,2326,2328,2330],{"class":382,"line":2319},25,[380,2321,1947],{"class":1880},[380,2323,1090],{"class":753},[380,2325,801],{"class":772},[380,2327,1954],{"class":776},[380,2329,773],{"class":772},[380,2331,1097],{"class":753},[380,2333,2335,2337,2339,2341,2343,2345,2347,2349,2351,2353,2355,2357,2359,2361,2363,2365,2367],{"class":382,"line":2334},26,[380,2336,1963],{"class":1880},[380,2338,1090],{"class":753},[380,2340,1184],{"class":753},[380,2342,801],{"class":772},[380,2344,1973],{"class":1972},[380,2346,773],{"class":772},[380,2348,1090],{"class":753},[380,2350,801],{"class":772},[380,2352,1982],{"class":776},[380,2354,773],{"class":772},[380,2356,782],{"class":753},[380,2358,801],{"class":772},[380,2360,1254],{"class":1972},[380,2362,773],{"class":772},[380,2364,1090],{"class":753},[380,2366,2050],{"class":757},[380,2368,2369],{"class":753},"},\n",[380,2371,2373,2375,2377,2379,2381,2383,2386],{"class":382,"line":2372},27,[380,2374,1991],{"class":1880},[380,2376,1090],{"class":753},[380,2378,1996],{"class":1921},[380,2380,1349],{"class":753},[380,2382,2001],{"class":765},[380,2384,2385],{"class":757},"(post)",[380,2387,1097],{"class":753},[380,2389,2391,2393,2395],{"class":382,"line":2390},28,[380,2392,1871],{"class":753},[380,2394,405],{"class":757},[380,2396,1905],{"class":753},[380,2398,2400],{"class":382,"line":2399},29,[380,2401,845],{"emptyLinePlaceholder":844},[380,2403,2405,2407,2409,2411,2413,2416,2418,2420,2422,2424,2426,2428,2430,2432,2434,2436],{"class":382,"line":2404},30,[380,2406,2057],{"class":1497},[380,2408,1828],{"class":765},[380,2410,769],{"class":757},[380,2412,773],{"class":772},[380,2414,2415],{"class":776},"/auth/session/logout",[380,2417,773],{"class":772},[380,2419,782],{"class":753},[380,2421,1184],{"class":753},[380,2423,2188],{"class":1880},[380,2425,1090],{"class":753},[380,2427,801],{"class":772},[380,2429,1954],{"class":776},[380,2431,773],{"class":772},[380,2433,2030],{"class":753},[380,2435,405],{"class":757},[380,2437,1905],{"class":753},[289,2439,2440,2443,2444,2447,2448,2450,2451,2454],{},[293,2441,2442],{},"credentials: 'include'"," makes the browser send and store the cookies. A same-origin ",[293,2445,2446],{},"fetch()"," already does that with the default ",[293,2449,1722],{}," mode, but stating it is harmless and keeps working if you later split origins. Retry only ",[309,2452,2453],{},"safe reads"," after a refresh. Never replay an unsafe request on its own.",[314,2456,2458],{"id":2457},"csrf","CSRF",[289,2460,2461,2462,2465,2466,735,2468,735,2471,949,2473,2475],{},"A cookie-authenticated write is exposed to CSRF, because the browser attaches the cookie to any request. A bearer request isn't: a cross-site page can't add an ",[293,2463,2464],{},"Authorization"," header. Attach ",[293,2467,2457],{},[309,2469,2470],{},"after",[293,2472,342],{},[293,2474,303],{}," on cookie-authenticated, state-changing routes:",[371,2477,2479],{"className":744,"code":2478,"language":746,"meta":376,"style":376},"$router->post('/posts', [PostController::class, 'store'])\n    ->middleware(['session_cookie', 'auth', 'csrf']);\n",[293,2480,2481,2522],{"__ignoreMap":376},[380,2482,2483,2485,2487,2489,2492,2494,2496,2498,2500,2502,2504,2507,2509,2511,2513,2515,2518,2520],{"class":382,"line":383},[380,2484,754],{"class":753},[380,2486,758],{"class":757},[380,2488,762],{"class":761},[380,2490,2491],{"class":765},"post",[380,2493,769],{"class":753},[380,2495,773],{"class":772},[380,2497,2310],{"class":776},[380,2499,773],{"class":772},[380,2501,782],{"class":753},[380,2503,785],{"class":753},[380,2505,2506],{"class":788},"PostController",[380,2508,792],{"class":761},[380,2510,796],{"class":795},[380,2512,782],{"class":753},[380,2514,801],{"class":772},[380,2516,2517],{"class":776},"store",[380,2519,773],{"class":772},[380,2521,809],{"class":753},[380,2523,2524,2526,2528,2530,2532,2534,2536,2538,2540,2542,2544,2546,2548,2550,2552],{"class":382,"line":812},[380,2525,815],{"class":761},[380,2527,818],{"class":765},[380,2529,821],{"class":753},[380,2531,773],{"class":772},[380,2533,342],{"class":776},[380,2535,773],{"class":772},[380,2537,782],{"class":753},[380,2539,801],{"class":772},[380,2541,303],{"class":776},[380,2543,773],{"class":772},[380,2545,782],{"class":753},[380,2547,801],{"class":772},[380,2549,2457],{"class":776},[380,2551,773],{"class":772},[380,2553,838],{"class":753},[1339,2555,2556,2563,2577,2590,2616],{},[322,2557,2558,2559,2562],{},"The token is bound to the session uuid. The cookie login returns one already bound to the new session (",[293,2560,2561],{},"data.csrf_token.token","), and it validates on the first authenticated write.",[322,2564,2565,2566,2569,2570,2573,2574,2576],{},"To fetch a fresh token later, ask from an ",[309,2567,2568],{},"authenticated"," request (below). ",[293,2571,2572],{},"GET /csrf-token"," has no ",[293,2575,303],{}," middleware, so its token binds to the anonymous fingerprint: it validates on anonymous requests, not on a session-authenticated write.",[322,2578,2579,2580,2582,2583,2586,2587,2589],{},"The client sends it as ",[293,2581,1254],{}," (or ",[293,2584,2585],{},"X-XSRF-Token","). After each successful AJAX write, the middleware echoes the current token in the ",[293,2588,1254],{}," response header.",[322,2591,2592,2593,498,2595,2598,2599,2602,2603,346,2605,2607,2608,2611,2612,2615],{},"Cookie-authenticated writes always get the ",[293,2594,1734],{},[293,2596,2597],{},"Referer"," check. ",[293,2600,2601],{},"skip_for_bearer_auth"," relaxes it only for a real bearer request with neither header, never for an ",[293,2604,2464],{},[293,2606,342],{}," injected from the cookie. A cookie write with neither header gets ",[293,2609,2610],{},"403 CSRF_ORIGIN_MISMATCH"," (unless ",[293,2613,2614],{},"allow_missing_origin"," is on). It never skips token validation.",[322,2617,2618,2620],{},[293,2619,681],{}," is defence in depth, not the main control. It stops the browser sending the cookie on cross-site POSTs, but the session-bound token is what authorizes the write.",[289,2622,2623],{},"A small endpoint behind the cookie session hands the SPA its token:",[371,2625,2627],{"className":744,"code":2626,"language":746,"meta":376,"style":376},"use Glueful\\Controllers\\BaseController;\nuse Glueful\\Http\\Response;\nuse Glueful\\Routing\\Middleware\\CSRFMiddleware;\nuse Symfony\\Component\\HttpFoundation\\Request;\n\nfinal class SessionCsrfController extends BaseController\n{\n    public function show(Request $request): Response\n    {\n        // The instance that shares the `csrf` route middleware's token store\n        $csrf = CSRFMiddleware::forContext($this->getContext());\n\n        return Response::success($csrf->getTokenData($request)); // data.token, data.header, ...\n    }\n}\n\n// routes\n$router->get('/session/csrf', [SessionCsrfController::class, 'show'])\n    ->middleware(['session_cookie', 'auth']);\n",[293,2628,2629,2651,2669,2690,2714,2718,2737,2741,2767,2772,2777,2807,2811,2843,2847,2851,2855,2860,2900],{"__ignoreMap":376},[380,2630,2631,2634,2637,2641,2643,2645,2649],{"class":382,"line":383},[380,2632,2633],{"class":795},"use",[380,2635,2636],{"class":1921}," Glueful",[380,2638,2640],{"class":2639},"sv8o3","\\",[380,2642,49],{"class":1921},[380,2644,2640],{"class":2639},[380,2646,2648],{"class":2647},"seZir","BaseController",[380,2650,1905],{"class":753},[380,2652,2653,2655,2657,2659,2662,2664,2667],{"class":382,"line":812},[380,2654,2633],{"class":795},[380,2656,2636],{"class":1921},[380,2658,2640],{"class":2639},[380,2660,2661],{"class":1921},"Http",[380,2663,2640],{"class":2639},[380,2665,2666],{"class":2647},"Response",[380,2668,1905],{"class":753},[380,2670,2671,2673,2675,2677,2679,2681,2683,2685,2688],{"class":382,"line":841},[380,2672,2633],{"class":795},[380,2674,2636],{"class":1921},[380,2676,2640],{"class":2639},[380,2678,29],{"class":1921},[380,2680,2640],{"class":2639},[380,2682,145],{"class":1921},[380,2684,2640],{"class":2639},[380,2686,2687],{"class":2647},"CSRFMiddleware",[380,2689,1905],{"class":753},[380,2691,2692,2694,2697,2699,2702,2704,2707,2709,2712],{"class":382,"line":848},[380,2693,2633],{"class":795},[380,2695,2696],{"class":1921}," Symfony",[380,2698,2640],{"class":2639},[380,2700,2701],{"class":1921},"Component",[380,2703,2640],{"class":2639},[380,2705,2706],{"class":1921},"HttpFoundation",[380,2708,2640],{"class":2639},[380,2710,2711],{"class":2647},"Request",[380,2713,1905],{"class":753},[380,2715,2716],{"class":382,"line":855},[380,2717,845],{"emptyLinePlaceholder":844},[380,2719,2720,2723,2726,2730,2733],{"class":382,"line":861},[380,2721,2722],{"class":2094},"final",[380,2724,2725],{"class":1823}," class",[380,2727,2729],{"class":2728},"sKvfc"," SessionCsrfController",[380,2731,2732],{"class":2094}," extends",[380,2734,2736],{"class":2735},"sW3Pz"," BaseController\n",[380,2738,2739],{"class":382,"line":903},[380,2740,1074],{"class":753},[380,2742,2743,2746,2748,2751,2753,2755,2757,2760,2762,2764],{"class":382,"line":1205},[380,2744,2745],{"class":2094},"    public",[380,2747,2098],{"class":1823},[380,2749,2750],{"class":765}," show",[380,2752,769],{"class":753},[380,2754,2711],{"class":788},[380,2756,1469],{"class":753},[380,2758,2759],{"class":757},"request",[380,2761,405],{"class":753},[380,2763,1090],{"class":761},[380,2765,2766],{"class":788}," Response\n",[380,2768,2769],{"class":382,"line":1219},[380,2770,2771],{"class":753},"    {\n",[380,2773,2774],{"class":382,"line":1240},[380,2775,2776],{"class":851},"        // The instance that shares the `csrf` route middleware's token store\n",[380,2778,2779,2781,2784,2786,2789,2791,2794,2796,2799,2801,2804],{"class":382,"line":1261},[380,2780,1588],{"class":753},[380,2782,2783],{"class":757},"csrf ",[380,2785,1440],{"class":761},[380,2787,2788],{"class":788}," CSRFMiddleware",[380,2790,792],{"class":761},[380,2792,2793],{"class":765},"forContext",[380,2795,769],{"class":753},[380,2797,2798],{"class":1443},"$this",[380,2800,762],{"class":761},[380,2802,2803],{"class":765},"getContext",[380,2805,2806],{"class":753},"());\n",[380,2808,2809],{"class":382,"line":1282},[380,2810,845],{"emptyLinePlaceholder":844},[380,2812,2813,2816,2819,2821,2823,2826,2828,2830,2833,2835,2837,2840],{"class":382,"line":1297},[380,2814,2815],{"class":1497},"        return",[380,2817,2818],{"class":788}," Response",[380,2820,792],{"class":761},[380,2822,1084],{"class":765},[380,2824,2825],{"class":753},"($",[380,2827,2457],{"class":757},[380,2829,762],{"class":761},[380,2831,2832],{"class":765},"getTokenData",[380,2834,2825],{"class":753},[380,2836,2759],{"class":757},[380,2838,2839],{"class":753},"));",[380,2841,2842],{"class":851}," // data.token, data.header, ...\n",[380,2844,2845],{"class":382,"line":1303},[380,2846,1300],{"class":753},[380,2848,2849],{"class":382,"line":1309},[380,2850,1312],{"class":753},[380,2852,2853],{"class":382,"line":2160},[380,2854,845],{"emptyLinePlaceholder":844},[380,2856,2857],{"class":382,"line":2205},[380,2858,2859],{"class":851},"// routes\n",[380,2861,2862,2864,2866,2868,2870,2872,2874,2877,2879,2881,2883,2886,2888,2890,2892,2894,2896,2898],{"class":382,"line":2248},[380,2863,754],{"class":753},[380,2865,758],{"class":757},[380,2867,762],{"class":761},[380,2869,766],{"class":765},[380,2871,769],{"class":753},[380,2873,773],{"class":772},[380,2875,2876],{"class":776},"/session/csrf",[380,2878,773],{"class":772},[380,2880,782],{"class":753},[380,2882,785],{"class":753},[380,2884,2885],{"class":788},"SessionCsrfController",[380,2887,792],{"class":761},[380,2889,796],{"class":795},[380,2891,782],{"class":753},[380,2893,801],{"class":772},[380,2895,804],{"class":776},[380,2897,773],{"class":772},[380,2899,809],{"class":753},[380,2901,2902,2904,2906,2908,2910,2912,2914,2916,2918,2920,2922],{"class":382,"line":2268},[380,2903,815],{"class":761},[380,2905,818],{"class":765},[380,2907,821],{"class":753},[380,2909,773],{"class":772},[380,2911,342],{"class":776},[380,2913,773],{"class":772},[380,2915,782],{"class":753},[380,2917,801],{"class":772},[380,2919,303],{"class":776},[380,2921,773],{"class":772},[380,2923,838],{"class":753},[289,2925,2926,2927,2929,2930,2933],{},"See ",[1792,2928,89],{"href":90}," for the ",[293,2931,2932],{},"security.csrf"," settings, exempt routes and error codes.",[314,2935,1795],{"id":2936},"cors",[289,2938,2939],{},"On the single-origin setup, the browser doesn't apply CORS at all. If the SPA calls the API from another origin, credentialed requests need:",[371,2941,2943],{"className":373,"code":2942,"language":375,"meta":376,"style":376},"CORS_ALLOWED_ORIGINS=https://app.example.com   # the exact SPA origin, never *\nCORS_SUPPORTS_CREDENTIALS=true\n",[293,2944,2945,2950],{"__ignoreMap":376},[380,2946,2947],{"class":382,"line":383},[380,2948,2949],{},"CORS_ALLOWED_ORIGINS=https://app.example.com   # the exact SPA origin, never *\n",[380,2951,2952],{"class":382,"line":812},[380,2953,2954],{},"CORS_SUPPORTS_CREDENTIALS=true\n",[289,2956,2957,2958,2961,2962,2965,2966,2968,2969,2972],{},"The framework never sends ",[293,2959,2960],{},"Access-Control-Allow-Credentials: true"," with a wildcard origin. It echoes the specific allowed origin with ",[293,2963,2964],{},"Vary: Origin",". The same two settings let refresh and logout accept a sibling subdomain's ",[293,2967,1726],{}," request (see ",[1792,2970,1703],{"href":2971},"#origin-check","). A cross-site origin is still refused by those endpoints.",[314,2974,2976],{"id":2975},"troubleshooting","Troubleshooting",[289,2978,2979,2988,2989,2991,2992,2994],{},[309,2980,2981,2984,2985,2987],{},[293,2982,2983],{},"404"," on ",[293,2986,2179],{},"?"," The transport is disabled (",[293,2990,419],{}," isn't ",[293,2993,548],{},"), or you're calling it without the API prefix.",[289,2996,2997,3002,3003,3005,3006,1349],{},[309,2998,2999,3000,2987],{},"Refresh always returns ",[293,3001,1661],{}," The browser isn't sending ",[293,3004,463],{},". Check that the cookie's path matches where the route is served, and remove any stale ",[293,3007,505],{},[289,3009,3010,3015,3016,3018,3019,1349],{},[309,3011,3012,3013,2987],{},"Refresh or logout returns ",[293,3014,1665],{}," The request isn't same-origin, and its origin isn't an allowed credentialed CORS origin. Serve the page and the API from one origin, or set ",[293,3017,1738],{}," to the SPA's exact origin with ",[293,3020,1742],{},[289,3022,3023,3028,3029,938,3031,3033,3034,2984,3036,3038],{},[309,3024,3025,3026,2987],{},"Cookie writes return ",[293,3027,2610],{}," The request carried no ",[293,3030,1734],{},[293,3032,2597],{},", or one that isn't allowed. Browsers send ",[293,3035,1734],{},[293,3037,2446],{}," writes; check proxies that strip it.",[289,3040,3041,735,3044,3046,3047,3050,3051,1349],{},[309,3042,3043],{},"Cookies never stored in local development?",[293,3045,677],{}," cookies need HTTPS. Use HTTPS locally or set ",[293,3048,3049],{},"SESSION_COOKIE_SECURE=false"," in your local ",[293,3052,601],{},[289,3054,3055,3059],{},[309,3056,3057,2987],{},[293,3058,1012],{}," The request sent a bearer token and a cookie for different users. Drop one of them.",[314,3061,3063],{"id":3062},"next-steps","Next Steps",[1339,3065,3066,3071,3076],{},[322,3067,3068,3070],{},[1792,3069,37],{"href":38}," - Bearer login, refresh and logout",[322,3072,3073,3075],{},[1792,3074,89],{"href":90}," - Origins, credentials and CSRF tokens",[322,3077,3078,3081,3082,3084],{},[1792,3079,3080],{"href":42},"Identity"," - What ",[293,3083,952],{}," carries",[3086,3087,3088],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .s_MOj, html code.shiki .s_MOj{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .shWJe, html code.shiki .shWJe{--shiki-light:#F76D47;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html pre.shiki code .saDeg, html code.shiki .saDeg{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sEff5, html code.shiki .sEff5{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sMTiH, html code.shiki .sMTiH{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sh1VR, html code.shiki .sh1VR{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#CFCFC2}html pre.shiki code .sINAO, html code.shiki .sINAO{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#CFCFC2}html pre.shiki code .sYThS, html code.shiki .sYThS{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sTC9v, html code.shiki .sTC9v{--shiki-light:#F76D47;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sSBr1, html code.shiki .sSBr1{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#FD971F}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sh7YU, html code.shiki .sh7YU{--shiki-light:#90A4AE;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sQgqH, html code.shiki .sQgqH{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit;--shiki-sepia:#FD971F;--shiki-sepia-font-style:italic}html pre.shiki code .slF1C, html code.shiki .slF1C{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#F92672}html pre.shiki code .squCx, html code.shiki .squCx{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .sJhdN, html code.shiki .sJhdN{--shiki-light:#E53935;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sTNss, html code.shiki .sTNss{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sv8o3, html code.shiki .sv8o3{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .seZir, html code.shiki .seZir{--shiki-light:#90A4AE;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sKvfc, html code.shiki .sKvfc{--shiki-light:#E2931D;--shiki-light-text-decoration:inherit;--shiki-default:#6F42C1;--shiki-default-text-decoration:inherit;--shiki-dark:#B392F0;--shiki-dark-text-decoration:inherit;--shiki-sepia:#A6E22E;--shiki-sepia-text-decoration:underline}html pre.shiki code .sW3Pz, html code.shiki .sW3Pz{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-light-text-decoration:inherit;--shiki-default:#6F42C1;--shiki-default-font-style:inherit;--shiki-default-text-decoration:inherit;--shiki-dark:#B392F0;--shiki-dark-font-style:inherit;--shiki-dark-text-decoration:inherit;--shiki-sepia:#A6E22E;--shiki-sepia-font-style:italic;--shiki-sepia-text-decoration:underline}",{"title":376,"searchDepth":383,"depth":812,"links":3090},[3091,3092,3093,3096,3097,3100,3103,3104,3105,3106,3107],{"id":316,"depth":812,"text":317},{"id":368,"depth":812,"text":369},{"id":605,"depth":812,"text":606,"children":3094},[3095],{"id":686,"depth":841,"text":687},{"id":728,"depth":812,"text":729},{"id":1018,"depth":812,"text":1019,"children":3098},[3099],{"id":1406,"depth":841,"text":1407},{"id":1623,"depth":812,"text":1624,"children":3101},[3102],{"id":1702,"depth":841,"text":1703},{"id":1810,"depth":812,"text":1811},{"id":2457,"depth":812,"text":2458},{"id":2936,"depth":812,"text":1795},{"id":2975,"depth":812,"text":2976},{"id":3062,"depth":812,"text":3063},"Authenticate browser clients with HttpOnly session cookies instead of tokens in JavaScript","md",null,{},{"title":81,"description":3108},"l7xtBK7grNgiDSc1HsYbM4VKQzMpHQeL0QydQwUw-fI",[3115,3117],{"title":77,"path":78,"stem":79,"description":3116,"children":-1},"Issue, rotate and revoke scoped API keys, and protect routes with",{"title":85,"path":86,"stem":87,"description":3118,"children":-1},"Speed up your application with Redis, Memcached, or file caching",1793709785204]