[{"data":1,"prerenderedAt":796},["ShallowReactive",2],{"navigation":3,"\u002Fguides\u002Factive-sessions":219,"\u002Fguides\u002Factive-sessions-surround":791},[4,43,110,118,193],{"title":5,"path":6,"stem":7,"children":8,"icon":42},"Get started","\u002Fgetting-started","1.getting-started\u002F1.index",[9,12,17,22,27,32,37],{"title":10,"path":6,"stem":7,"icon":11},"Introduction","i-lucide-house",{"title":13,"path":14,"stem":15,"icon":16},"Install","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-lucide-download",{"title":18,"path":19,"stem":20,"icon":21},"Quick start","\u002Fgetting-started\u002Fquick-start","1.getting-started\u002F3.quick-start","i-lucide-play",{"title":23,"path":24,"stem":25,"icon":26},"Choose your setup","\u002Fgetting-started\u002Fchoose-a-sign-in-stack","1.getting-started\u002F4.choose-a-sign-in-stack","i-lucide-signpost",{"title":28,"path":29,"stem":30,"icon":31},"Browser clients","\u002Fgetting-started\u002Fbrowser-clients","1.getting-started\u002F5.browser-clients","i-lucide-globe",{"title":33,"path":34,"stem":35,"icon":36},"Coding agents","\u002Fgetting-started\u002Fai-agents","1.getting-started\u002F6.ai-agents","i-lucide-bot",{"title":38,"path":39,"stem":40,"icon":41},"Upgrade from 3.x","\u002Fgetting-started\u002Fupgrading-to-4","1.getting-started\u002F8.upgrading-to-4","i-lucide-arrow-up-right","i-lucide-rocket",{"title":44,"icon":45,"path":46,"stem":47,"children":48,"page":109},"Guides","i-lucide-waypoints","\u002Fguides","2.guides",[49,54,59,64,69,74,79,84,89,94,99,104],{"title":50,"path":51,"stem":52,"icon":53},"Register users","\u002Fguides\u002Fregistration","2.guides\u002F02.registration","i-lucide-user-plus",{"title":55,"path":56,"stem":57,"icon":58},"Sign users in","\u002Fguides\u002Fsign-in","2.guides\u002F03.sign-in","i-lucide-log-in",{"title":60,"path":61,"stem":62,"icon":63},"Sign users out","\u002Fguides\u002Fsign-out","2.guides\u002F04.sign-out","i-lucide-log-out",{"title":65,"path":66,"stem":67,"icon":68},"Two-factor auth","\u002Fguides\u002Ftwo-factor","2.guides\u002F05.two-factor","i-lucide-smartphone",{"title":70,"path":71,"stem":72,"icon":73},"Email-code sign-in","\u002Fguides\u002Femail-code","2.guides\u002F06.email-code","i-lucide-mail",{"title":75,"path":76,"stem":77,"icon":78},"Reset a password","\u002Fguides\u002Freset-password","2.guides\u002F07.reset-password","i-lucide-key-round",{"title":80,"path":81,"stem":82,"icon":83},"Manage account","\u002Fguides\u002Fmanage-account","2.guides\u002F08.manage-account","i-lucide-user-cog",{"title":85,"path":86,"stem":87,"icon":88},"Manage passkeys","\u002Fguides\u002Fmanage-passkeys","2.guides\u002F09.manage-passkeys","i-lucide-scan-face",{"title":90,"path":91,"stem":92,"icon":93},"Link accounts","\u002Fguides\u002Flink-external-accounts","2.guides\u002F10.link-external-accounts","i-lucide-link",{"title":95,"path":96,"stem":97,"icon":98},"Require step-up","\u002Fguides\u002Fstep-up","2.guides\u002F11.step-up","i-lucide-shield-check",{"title":100,"path":101,"stem":102,"icon":103},"No public sign-up","\u002Fguides\u002Finternal-app","2.guides\u002F12.internal-app","i-lucide-building-2",{"title":105,"path":106,"stem":107,"icon":108},"List and revoke signed-in sessions","\u002Fguides\u002Factive-sessions","2.guides\u002F13.active-sessions","i-lucide-monitor-smartphone",false,{"title":111,"path":112,"stem":113,"children":114,"icon":117},"Composables","\u002Fcomposables","3.composables\u002F1.index",[115],{"title":116,"path":112,"stem":113,"icon":117},"Build a custom stack","i-lucide-blocks",{"title":119,"icon":120,"path":121,"stem":122,"children":123,"page":109},"Reference","i-lucide-book-open","\u002Fmodules","4.modules",[124,129,133,138,142,147,152,157,161,166,170,175,179,184,189],{"title":125,"path":126,"stem":127,"icon":128},"Endpoint reference","\u002Fmodules\u002Fendpoints","4.modules\u002F01.endpoints","i-lucide-route",{"title":130,"path":131,"stem":132,"icon":78},"Password auth","\u002Fmodules\u002Fpassword-auth","4.modules\u002F02.password-auth",{"title":134,"path":135,"stem":136,"icon":137},"Composition table","\u002Fmodules\u002Fcomposition","4.modules\u002F03.composition","i-lucide-table",{"title":139,"path":140,"stem":141,"icon":83},"Identity module","\u002Fmodules\u002Fidentity-management","4.modules\u002F04.identity-management",{"title":143,"path":144,"stem":145,"icon":146},"Cookie sign-in","\u002Fmodules\u002Fcookie-auth","4.modules\u002F05.cookie-auth","i-lucide-cookie",{"title":148,"path":149,"stem":150,"icon":151},"Identity bearer","\u002Fmodules\u002Fbearer-auth","4.modules\u002F06.bearer-auth","i-lucide-key",{"title":153,"path":154,"stem":155,"icon":156},"JWT","\u002Fmodules\u002Fjwt","4.modules\u002F07.jwt","i-lucide-fingerprint",{"title":158,"path":159,"stem":160,"icon":88},"Passkeys","\u002Fmodules\u002Fpasskeys","4.modules\u002F08.passkeys",{"title":162,"path":163,"stem":164,"icon":165},"Step-up ReAuth","\u002Fmodules\u002Freauth","4.modules\u002F09.reauth","i-lucide-shield-alert",{"title":167,"path":168,"stem":169,"icon":58},"External OAuth","\u002Fmodules\u002Fexternal-oauth","4.modules\u002F10.external-oauth",{"title":171,"path":172,"stem":173,"icon":174},"Configuration","\u002Fmodules\u002Fconfiguration","4.modules\u002F11.configuration","i-lucide-settings",{"title":176,"path":177,"stem":178,"icon":98},"CSRF rules","\u002Fmodules\u002Fcsrf","4.modules\u002F12.csrf",{"title":180,"path":181,"stem":182,"icon":183},"Responses","\u002Fmodules\u002Ferrors","4.modules\u002F13.errors","i-lucide-circle-alert",{"title":185,"path":186,"stem":187,"icon":188},"Rate-limit policies","\u002Fmodules\u002Frate-limits","4.modules\u002F14.rate-limits","i-lucide-gauge",{"title":190,"path":191,"stem":192,"icon":73},"Email codes","\u002Fmodules\u002Femail-code","4.modules\u002F15.email-code",{"title":194,"icon":195,"path":196,"stem":197,"children":198,"page":109},"Concepts","i-lucide-lightbulb","\u002Fconcepts","5.concepts",[199,204,209,214],{"title":200,"path":201,"stem":202,"icon":203},"Comparison","\u002Fconcepts\u002Fcompare","5.concepts\u002F1.compare","i-lucide-git-compare",{"title":205,"path":206,"stem":207,"icon":208},"Security model","\u002Fconcepts\u002Fsecurity-model","5.concepts\u002F2.security-model","i-lucide-shield",{"title":210,"path":211,"stem":212,"icon":213},"Production","\u002Fconcepts\u002Fproduction","5.concepts\u002F3.production","i-lucide-factory",{"title":215,"path":216,"stem":217,"icon":218},"FAQ","\u002Fconcepts\u002Ffaq","5.concepts\u002F4.faq","i-lucide-circle-help",{"id":220,"title":105,"body":221,"description":784,"extension":785,"faq":109,"links":786,"meta":787,"navigation":788,"path":106,"seo":789,"stem":107,"__hash__":790},"docs\u002F2.guides\u002F13.active-sessions.md",{"type":222,"value":223,"toc":777},"minimark",[224,228,233,559,562,566,659,677,689,693,728,732,751,757,761,773],[225,226,227],"p",{},"Active sessions are off by default. When you turn them on, every successful sign-in (password, emailed code, passkey, two-factor, or GitHub or Google) records one session. A user can list their sessions and sign out any of them. Cookie and JWT sessions are supported. Identity bearer tokens are not, and the app fails at startup if it maps them while active sessions are on.",[229,230,232],"h2",{"id":231},"turn-on-active-sessions","Turn on active sessions",[234,235,236,359,507,549],"ol",{},[237,238,239,240,244,245,248,249,252,253],"li",{},"Add the session table to your ",[241,242,243],"code",{},"DbContext",". With JWT, call ",[241,246,247],{},"UseRefreshToken()"," first so that refresh tokens get a ",[241,250,251],{},"SessionId"," column.",[254,255,260],"pre",{"className":256,"code":257,"language":258,"meta":259,"style":259},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","protected override void OnModelCreating(ModelBuilder builder)\n{\n    base.OnModelCreating(builder);\n    builder.UseRefreshToken();   \u002F\u002F JWT only\n    builder.UseActiveSessions();\n}\n","csharp","",[241,261,262,295,301,322,340,353],{"__ignoreMap":259},[263,264,267,271,274,278,282,285,289,292],"span",{"class":265,"line":266},"line",1,[263,268,270],{"class":269},"spNyl","protected",[263,272,273],{"class":269}," override",[263,275,277],{"class":276},"sMK4o"," void",[263,279,281],{"class":280},"s2Zo4"," OnModelCreating",[263,283,284],{"class":276},"(",[263,286,288],{"class":287},"sBMFI","ModelBuilder",[263,290,291],{"class":287}," builder",[263,293,294],{"class":276},")\n",[263,296,298],{"class":265,"line":297},2,[263,299,300],{"class":276},"{\n",[263,302,304,308,311,314,316,319],{"class":265,"line":303},3,[263,305,307],{"class":306},"sTEyZ","    base",[263,309,310],{"class":276},".",[263,312,313],{"class":280},"OnModelCreating",[263,315,284],{"class":276},[263,317,318],{"class":306},"builder",[263,320,321],{"class":276},");\n",[263,323,325,328,330,333,336],{"class":265,"line":324},4,[263,326,327],{"class":306},"    builder",[263,329,310],{"class":276},[263,331,332],{"class":280},"UseRefreshToken",[263,334,335],{"class":276},"();",[263,337,339],{"class":338},"sHwdD","   \u002F\u002F JWT only\n",[263,341,343,345,347,350],{"class":265,"line":342},5,[263,344,327],{"class":306},[263,346,310],{"class":276},[263,348,349],{"class":280},"UseActiveSessions",[263,351,352],{"class":276},"();\n",[263,354,356],{"class":265,"line":355},6,[263,357,358],{"class":276},"}\n",[237,360,361,362,365,366,369,370],{},"Register the services. With JWT, call this after ",[241,363,364],{},"AddJwtEndpoints"," (or ",[241,367,368],{},"AddPasswordAuth",") and after any custom refresh-token service or access-token generator.",[254,371,373],{"className":256,"code":372,"language":258,"meta":259,"style":259},"builder.Services.AddActiveSessions\u003CAppUser, AppDbContext>(options =>\n{\n    options.LastSeenUpdateInterval = TimeSpan.FromMinutes(5); \u002F\u002F default\n    options.RevalidationInterval = TimeSpan.FromSeconds(30); \u002F\u002F default\n    options.CleanupInterval = TimeSpan.FromHours(1);          \u002F\u002F default\n});\n",[241,374,375,410,414,447,474,502],{"__ignoreMap":259},[263,376,377,379,381,384,386,389,392,395,398,401,404,407],{"class":265,"line":266},[263,378,318],{"class":306},[263,380,310],{"class":276},[263,382,383],{"class":306},"Services",[263,385,310],{"class":276},[263,387,388],{"class":280},"AddActiveSessions",[263,390,391],{"class":276},"\u003C",[263,393,394],{"class":287},"AppUser",[263,396,397],{"class":276},",",[263,399,400],{"class":287}," AppDbContext",[263,402,403],{"class":276},">(",[263,405,406],{"class":287},"options",[263,408,409],{"class":276}," =>\n",[263,411,412],{"class":265,"line":297},[263,413,300],{"class":276},[263,415,416,419,421,424,427,430,432,435,437,441,444],{"class":265,"line":303},[263,417,418],{"class":306},"    options",[263,420,310],{"class":276},[263,422,423],{"class":306},"LastSeenUpdateInterval ",[263,425,426],{"class":276},"=",[263,428,429],{"class":306}," TimeSpan",[263,431,310],{"class":276},[263,433,434],{"class":280},"FromMinutes",[263,436,284],{"class":276},[263,438,440],{"class":439},"sbssI","5",[263,442,443],{"class":276},");",[263,445,446],{"class":338}," \u002F\u002F default\n",[263,448,449,451,453,456,458,460,462,465,467,470,472],{"class":265,"line":324},[263,450,418],{"class":306},[263,452,310],{"class":276},[263,454,455],{"class":306},"RevalidationInterval ",[263,457,426],{"class":276},[263,459,429],{"class":306},[263,461,310],{"class":276},[263,463,464],{"class":280},"FromSeconds",[263,466,284],{"class":276},[263,468,469],{"class":439},"30",[263,471,443],{"class":276},[263,473,446],{"class":338},[263,475,476,478,480,483,485,487,489,492,494,497,499],{"class":265,"line":342},[263,477,418],{"class":306},[263,479,310],{"class":276},[263,481,482],{"class":306},"CleanupInterval ",[263,484,426],{"class":276},[263,486,429],{"class":306},[263,488,310],{"class":276},[263,490,491],{"class":280},"FromHours",[263,493,284],{"class":276},[263,495,496],{"class":439},"1",[263,498,443],{"class":276},[263,500,501],{"class":338},"          \u002F\u002F default\n",[263,503,504],{"class":265,"line":355},[263,505,506],{"class":276},"});\n",[237,508,509,510],{},"Map the endpoints on the same prefix as your sign-in endpoints.",[254,511,513],{"className":256,"code":512,"language":258,"meta":259,"style":259},"app.MapGroup(\"\u002Fauth\").MapActiveSessionEndpoints\u003CAppUser>();\n",[241,514,515],{"__ignoreMap":259},[263,516,517,520,522,525,527,530,534,536,539,542,544,546],{"class":265,"line":266},[263,518,519],{"class":306},"app",[263,521,310],{"class":276},[263,523,524],{"class":280},"MapGroup",[263,526,284],{"class":276},[263,528,529],{"class":276},"\"",[263,531,533],{"class":532},"sfazB","\u002Fauth",[263,535,529],{"class":276},[263,537,538],{"class":276},").",[263,540,541],{"class":280},"MapActiveSessionEndpoints",[263,543,391],{"class":276},[263,545,394],{"class":287},[263,547,548],{"class":276},">();\n",[237,550,551,552,555,556,558],{},"Add a migration. It creates the ",[241,553,554],{},"AuthEndpointsActiveSessions"," table and, with JWT, the nullable ",[241,557,251],{}," column on refresh tokens.",[225,560,561],{},"Without these calls the model, the database, and the endpoints are unchanged.",[229,563,565],{"id":564},"endpoints","Endpoints",[567,568,569,588],"table",{},[570,571,572],"thead",{},[573,574,575,579,582,585],"tr",{},[576,577,578],"th",{},"Endpoint",[576,580,581],{},"Result",[576,583,584],{},"Re-authentication",[576,586,587],{},"CSRF",[589,590,591,613,636],"tbody",{},[573,592,593,599,608,611],{},[594,595,596],"td",{},[241,597,598],{},"GET \u002Fauth\u002Fsessions",[594,600,601,604,605],{},[241,602,603],{},"200"," with ",[241,606,607],{},"[{ id, userAgent, createdAt, lastSeenAt, current }]",[594,609,610],{},"No",[594,612,610],{},[573,614,615,620,630,633],{},[594,616,617],{},[241,618,619],{},"DELETE \u002Fauth\u002Fsessions\u002F{id}",[594,621,622,625,626,629],{},[241,623,624],{},"204",", or ",[241,627,628],{},"404"," when the session is not the caller's",[594,631,632],{},"Yes",[594,634,635],{},"Yes for cookie callers",[573,637,638,643,655,657],{},[594,639,640],{},[241,641,642],{},"POST \u002Fauth\u002Fsessions\u002FrevokeOthers",[594,644,645,604,647,650,651,654],{},[241,646,603],{},[241,648,649],{},"{ revoked }",". The current session keeps working. ",[241,652,653],{},"409"," when the caller has no current session, such as a JWT issued before sessions were turned on.",[594,656,632],{},[594,658,635],{},[225,660,661,662,665,666,669,670,673,674,310],{},"The list only contains the caller's sessions that are still active. ",[241,663,664],{},"current"," is ",[241,667,668],{},"true"," for the session that made the request. ",[241,671,672],{},"lastSeenAt"," is updated at most once per ",[241,675,676],{},"LastSeenUpdateInterval",[225,678,679,680,685,686,310],{},"Revoking needs a recent ",[681,682,684],"a",{"href":683},"\u002Fguides\u002Fstep-up\u002F","re-authentication",", like unlinking an external account. Without it the endpoints return ",[241,687,688],{},"403",[229,690,692],{"id":691},"what-happens-after-a-revoke","What happens after a revoke",[694,695,696,718],"ul",{},[237,697,698,702,703,706,707,710,711,713,714,717],{},[699,700,701],"strong",{},"Cookie."," The revoked browser's next request returns ",[241,704,705],{},"401",", and its cookie is removed. To avoid a database read on every request, a cookie's session is checked at most once per ",[241,708,709],{},"RevalidationInterval"," (30 seconds by default). A revoke on the same server applies right away; with several servers, a revoke can take up to ",[241,712,709],{}," to apply elsewhere. Set it to ",[241,715,716],{},"TimeSpan.Zero"," to check every request.",[237,719,720,723,724,727],{},[699,721,722],{},"JWT."," The session's refresh tokens are revoked, so the next ",[241,725,726],{},"POST \u002Fauth\u002Frefresh"," fails. An access token that was already issued stays valid until it expires, so keep access tokens short-lived.",[229,729,731],{"id":730},"when-sessions-end-on-their-own","When sessions end on their own",[694,733,734,741,748],{},[237,735,736,737,740],{},"Signing out (",[241,738,739],{},"POST \u002Fauth\u002Flogout",") ends the current session.",[237,742,743,744,747],{},"Setting or changing a password, or anything else that changes the security stamp, ends the user's other sessions. Cookie sessions end at their next security stamp check (",[241,745,746],{},"SecurityStampValidatorOptions.ValidationInterval","). JWT sessions end at their next refresh, or when the user lists their sessions.",[237,749,750],{},"A cookie or refresh token that expires ends its session.",[225,752,753,754,310],{},"A background job deletes expired and revoked session records every ",[241,755,756],{},"CleanupInterval",[229,758,760],{"id":759},"limits","Limits",[694,762,763,770],{},[237,764,765,766,769],{},"Identity bearer tokens (",[241,767,768],{},"AuthEndpointsSession.IdentityBearer",") are not supported.",[237,771,772],{},"Cookies issued before you turned sessions on get a session the first time they are used.",[774,775,776],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}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 pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}",{"title":259,"searchDepth":266,"depth":297,"links":778},[779,780,781,782,783],{"id":231,"depth":297,"text":232},{"id":564,"depth":297,"text":565},{"id":691,"depth":297,"text":692},{"id":730,"depth":297,"text":731},{"id":759,"depth":297,"text":760},"Let users see where they are signed in and sign out other browsers or devices, for cookie and JWT sessions.","md",null,{},{"icon":108},{"title":105,"description":784},"84wD3TlopB_NSv6L_qK9YBAojVh5-e-DqxHVqiTSUpY",[792,794],{"title":100,"path":101,"stem":102,"description":793,"icon":103,"children":-1},"Map password sign-in, recovery, and account management without a public register route.",{"title":116,"path":112,"stem":113,"description":795,"icon":117,"children":-1},"Compose Add and Map pairs on your prefixes. A custom host follows the rules and can copy a host for email-code, external, passkey, or JWT sign-in.",1793459627439]