[{"data":1,"prerenderedAt":353},["ShallowReactive",2],{"blog-post-\u002Fblog\u002Fauthendpoints-spa-native":3,"blog-surround-\u002Fblog\u002Fauthendpoints-spa-native":342},{"id":4,"title":5,"authors":6,"badge":12,"body":14,"date":323,"description":324,"extension":325,"image":326,"meta":329,"navigation":123,"path":337,"seo":338,"stem":340,"__hash__":341},"posts\u002Fblog\u002Fauthendpoints-spa-native.md","ASP.NET Core auth for SPAs and native clients without the Identity boilerplate",[7],{"name":8,"to":9,"avatar":10},"Made Y","\u002F",{"src":11},"\u002Fimages\u002Fprofile2.jpg",{"label":13},"ASP.NET",{"type":15,"value":16,"toc":316},"minimark",[17,21,29,32,43,48,51,54,82,85,88,159,172,175,205,208,213,216,236,239,243,246,249,252,255,274,278,281,284,298,312],[18,19,20],"p",{},"I came into .NET as a Django developer. Relatively new to the stack. I started around .NET 5, when top-level statements landed in C# 9. .NET 6 brought minimal APIs soon after. That modern C# \u002F ASP.NET Core direction clicked for me. I have been a fan since.",[18,22,23,24,28],{},"ASP.NET Core still felt incomplete in places. Auth was the loudest gap. Django ships batteries-included auth. You get register, login, password reset, and session handling without standing up a second product. On ASP.NET Core I had Identity for membership, but the HTTP surface for a first-party web API plus SPA or native clients was thin. ",[25,26,27],"code",{},"MapIdentityApi"," (from .NET 8) covers a password-oriented slice now. You still wire a lot yourself for passkeys, step-up, and a shared SPA plus native shape.",[18,30,31],{},"AI coding was not a real option then. I googled articles and docs. Search results kept pointing at IdentityServer, Duende, OpenIddict, and OpenID Connect. Coming from that batteries-included expectation, that stack felt like a lot. I was still new to .NET. I needed register, login, and password reset for a web API and SPA. That material was hard to map onto a simple API plus SPA.",[18,33,34,35,42],{},"So I filled the gap myself. I kept a reusable set of auth endpoints so every new ASP.NET Core Web API did not start from an empty controller folder. That library became ",[36,37,41],"a",{"href":38,"rel":39},"https:\u002F\u002Fgithub.com\u002Fmadeyoga\u002FAuthEndpoints",[40],"nofollow","AuthEndpoints",".",[44,45,47],"h2",{"id":46},"authendpoints-as-the-identity-facade","AuthEndpoints as the Identity facade",[18,49,50],{},"AuthEndpoints is a NuGet package of ready-made Identity auth endpoints for web and mobile clients. It does not replace ASP.NET Core Identity. Identity still owns membership, lockout, password hashing, and the user store. AuthEndpoints is a composable facade on top of that.",[18,52,53],{},"Install:",[55,56,61],"pre",{"className":57,"code":58,"language":59,"meta":60,"style":60},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark monokai","dotnet add package AuthEndpoints\n","bash","",[25,62,63],{"__ignoreMap":60},[64,65,68,72,76,79],"span",{"class":66,"line":67},"line",1,[64,69,71],{"class":70},"sR7ES","dotnet",[64,73,75],{"class":74},"sLACW"," add",[64,77,78],{"class":74}," package",[64,80,81],{"class":74}," AuthEndpoints\n",[18,83,84],{},"Requires .NET 10, ASP.NET Core Identity, and EF Core.",[18,86,87],{},"Cookie facade (default):",[55,89,93],{"className":90,"code":91,"language":92,"meta":60,"style":60},"language-cs shiki shiki-themes material-theme-lighter github-light github-dark monokai","builder.Services.AddAuthEndpoints\u003CAppUser, AppDbContext>(o =>\n{\n    o.Passkeys.ServerDomain = \"example.com\"; \u002F\u002F required in Production\n});\n\nbuilder.Services.AddTransient\u003CIEmailSender\u003CAppUser>, MyEmailSender>();\n\nvar app = builder.Build();\n\napp.UseAuthEndpoints();\napp.MapAuthEndpoints\u003CAppUser>();\n","cs",[25,94,95,100,106,112,118,125,131,136,142,147,153],{"__ignoreMap":60},[64,96,97],{"class":66,"line":67},[64,98,99],{},"builder.Services.AddAuthEndpoints\u003CAppUser, AppDbContext>(o =>\n",[64,101,103],{"class":66,"line":102},2,[64,104,105],{},"{\n",[64,107,109],{"class":66,"line":108},3,[64,110,111],{},"    o.Passkeys.ServerDomain = \"example.com\"; \u002F\u002F required in Production\n",[64,113,115],{"class":66,"line":114},4,[64,116,117],{},"});\n",[64,119,121],{"class":66,"line":120},5,[64,122,124],{"emptyLinePlaceholder":123},true,"\n",[64,126,128],{"class":66,"line":127},6,[64,129,130],{},"builder.Services.AddTransient\u003CIEmailSender\u003CAppUser>, MyEmailSender>();\n",[64,132,134],{"class":66,"line":133},7,[64,135,124],{"emptyLinePlaceholder":123},[64,137,139],{"class":66,"line":138},8,[64,140,141],{},"var app = builder.Build();\n",[64,143,145],{"class":66,"line":144},9,[64,146,124],{"emptyLinePlaceholder":123},[64,148,150],{"class":66,"line":149},10,[64,151,152],{},"app.UseAuthEndpoints();\n",[64,154,156],{"class":66,"line":155},11,[64,157,158],{},"app.MapAuthEndpoints\u003CAppUser>();\n",[18,160,161,164,165,164,168,171],{},[25,162,163],{},"AddAuthEndpoints"," \u002F ",[25,166,167],{},"UseAuthEndpoints",[25,169,170],{},"MapAuthEndpoints"," map cookie Identity plus passkeys with secure defaults. Rate limiting, antiforgery for cookie flows, and lockout-aware login come with that path.",[18,173,174],{},"Sign-in stacks you choose:",[176,177,178,186,199],"ul",{},[179,180,181,185],"li",{},[182,183,184],"strong",{},"Cookie (default)."," First-party browser apps. Password login sets the Identity application cookie. Clients send credentials and a CSRF token on unsafe cookie requests.",[179,187,188,191,192,195,196,198],{},[182,189,190],{},"Identity bearer."," Pass ",[25,193,194],{},"AuthEndpointsSignIn.IdentityBearer"," into ",[25,197,163],{},". Same Program.cs shape. Login returns access and refresh tokens for native \u002F mobile clients.",[179,200,201,204],{},[182,202,203],{},"Simple JWT."," When you want JWT access tokens from the library's JWT module instead of Identity bearer.",[18,206,207],{},"If the facade is too opinionated, compose modules yourself. The point is you do not rewrite the same register \u002F login \u002F manage surface on every API.",[209,210,212],"h3",{"id":211},"core-flows","Core flows",[18,214,215],{},"Product outcomes the facade maps for a first-party SPA or native client:",[176,217,218,221,224,227,230,233],{},[179,219,220],{},"register and confirm email",[179,222,223],{},"login and logout (cookie or token, depending on the sign-in stack)",[179,225,226],{},"forgot \u002F reset password",[179,228,229],{},"manage account info and 2FA",[179,231,232],{},"passkeys (WebAuthn) for passwordless register and login, plus credential management",[179,234,235],{},"ReAuth for step-up confirmation before sensitive manage actions",[18,237,238],{},"Same Identity user store. Same account lifecycle. AuthEndpoints ships the HTTP surface so you are not inventing route names and payloads per project.",[209,240,242],{"id":241},"why-spa-and-native-clients-share-one-api","Why SPA and native clients share one API",[18,244,245],{},"Browser clients keep cookies and CSRF. Token clients keep bearer or JWT. Account routes stay in one shape, so Nuxt \u002F Vue \u002F React and a mobile app can hit the same backend without two auth stacks.",[18,247,248],{},"Passkeys sit on that host. You do not bolt WebAuthn onto a one-off password API later.",[18,250,251],{},"ReAuth covers step-up. Change email, change password, or other sensitive manage work can require a fresh confirmation without inventing a second auth protocol.",[18,253,254],{},"I still write email senders, pick passkey domains, and choose cookie vs bearer per client. Identity stays the membership system. AuthEndpoints is the endpoints layer I got tired of copying.",[18,256,257,258,263,264,269,270,42],{},"Docs: ",[36,259,262],{"href":260,"rel":261},"https:\u002F\u002Fmadeyoga.github.io\u002FAuthEndpoints",[40],"madeyoga.github.io\u002FAuthEndpoints",". Package: ",[36,265,268],{"href":266,"rel":267},"https:\u002F\u002Fwww.nuget.org\u002Fpackages\u002FAuthEndpoints\u002F",[40],"NuGet AuthEndpoints",". Source: ",[36,271,273],{"href":38,"rel":272},[40],"github.com\u002Fmadeyoga\u002FAuthEndpoints",[44,275,277],{"id":276},"summary","Summary",[18,279,280],{},"I expected Django-shaped auth when I moved to ASP.NET Core. Identity covered membership. The reusable HTTP endpoints for SPA and native clients were the missing piece. AuthEndpoints is that facade so I do not rewrite Identity's HTTP surface on every Web API.",[18,282,283],{},"If you are shipping a SPA or native client on Identity and the MapIdentityApi path feels thin, try it:",[55,285,286],{"className":57,"code":58,"language":59,"meta":60,"style":60},[25,287,288],{"__ignoreMap":60},[64,289,290,292,294,296],{"class":66,"line":67},[64,291,71],{"class":70},[64,293,75],{"class":74},[64,295,78],{"class":74},[64,297,81],{"class":74},[18,299,300,301,306,307,311],{},"Read the ",[36,302,305],{"href":303,"rel":304},"https:\u002F\u002Fmadeyoga.github.io\u002FAuthEndpoints\u002Fgetting-started\u002Fquick-start",[40],"quick start",", star the ",[36,308,310],{"href":38,"rel":309},[40],"repo"," if it helps, and open an issue when a route does not match your client.",[313,314,315],"style",{},"html pre.shiki code .sR7ES, html code.shiki .sR7ES{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}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);}",{"title":60,"searchDepth":102,"depth":108,"links":317},[318,322],{"id":46,"depth":102,"text":47,"children":319},[320,321],{"id":211,"depth":108,"text":212},{"id":241,"depth":108,"text":242},{"id":276,"depth":102,"text":277},"2026-09-18","Identity works. SPA and native auth still meant rewriting endpoints each time. AuthEndpoints is the Identity facade I built for that.","md",{"src":327,"alt":328},"\u002Fimages\u002Fauthendpoints-spa-native.svg","ASP.NET Core Identity auth endpoints for SPA and native clients",{"sitemap":330,"schemaOrg":331},{"lastmod":323},[332],{"type":333,"headline":5,"author":334,"datePublished":323},"BlogPosting",{"type":335,"name":336},"Person","Made Yoga","\u002Fblog\u002Fauthendpoints-spa-native",{"keywords":339,"title":5,"description":324},"ASP.NET Core Identity, AuthEndpoints, SPA authentication, Nuxt auth, React auth, JWT, cookie auth, passkeys, MapIdentityApi, .NET 10","blog\u002Fauthendpoints-spa-native","btPtI-8xoQ2shgFTeDW3tbqdNElXwGGPHLgAC5GMlHs",[343,348],{"title":344,"path":345,"stem":346,"description":347,"children":-1},"AuthEndpoints 3.0 RC: a composable auth endpoints library for ASP.NET Core","\u002Fblog\u002Fauthendpoints-3-0-rc","blog\u002Fauthendpoints-3-0-rc","Announcing AuthEndpoints 3.0 RC, an auth library for ASP.NET Core. Composable auth endpoints on top of ASP.NET Core Identity: registration, login, 2FA, password reset, and passkeys bootstrapped in minutes.",{"title":349,"path":350,"stem":351,"description":352,"children":-1},"Remote AI coding from any device with Grok Bot and Cursor cloud agents","\u002Fblog\u002Fgrok-bot-cursor-cloud-agents","blog\u002Fgrok-bot-cursor-cloud-agents","How I moved from babysitting local Cursor agents to Grok Bot plus Cursor cloud agents, then closed the trust gap with pstack verification: proof screenshots, command output, and video from the cloud agent's computer.",1789742166465]