[{"data":1,"prerenderedAt":612},["ShallowReactive",2],{"navigation":3,"\u002Fguides\u002Fmanage-passkeys":203,"\u002Fguides\u002Fmanage-passkeys-surround":607},[4,33,96,114,177],{"title":5,"path":6,"stem":7,"children":8,"icon":32},"Get started","\u002Fgetting-started","1.getting-started\u002F1.index",[9,12,17,22,27],{"title":10,"path":6,"stem":7,"icon":11},"Introduction","i-lucide-house",{"title":13,"path":14,"stem":15,"icon":16},"Install AuthEndpoints","\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 a sign-in stack","\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},"Use the AuthEndpoints skill with coding agents","\u002Fgetting-started\u002Fai-agents","1.getting-started\u002F5.ai-agents","i-lucide-bot","i-lucide-rocket",{"title":34,"path":35,"stem":36,"children":37,"icon":95},"Guides","\u002Fguides","2.guides\u002F01.index",[38,40,45,50,55,60,65,70,75,80,85,90],{"title":34,"path":35,"stem":36,"icon":39},"i-lucide-list-checks",{"title":41,"path":42,"stem":43,"icon":44},"Register users","\u002Fguides\u002Fregistration","2.guides\u002F02.registration","i-lucide-user-plus",{"title":46,"path":47,"stem":48,"icon":49},"Sign users in","\u002Fguides\u002Fsign-in","2.guides\u002F03.sign-in","i-lucide-log-in",{"title":51,"path":52,"stem":53,"icon":54},"Sign users out","\u002Fguides\u002Fsign-out","2.guides\u002F04.sign-out","i-lucide-log-out",{"title":56,"path":57,"stem":58,"icon":59},"Turn on two-factor authentication","\u002Fguides\u002Ftwo-factor","2.guides\u002F05.two-factor","i-lucide-smartphone",{"title":61,"path":62,"stem":63,"icon":64},"Reset a forgotten password","\u002Fguides\u002Freset-password","2.guides\u002F06.reset-password","i-lucide-key-round",{"title":66,"path":67,"stem":68,"icon":69},"Change a user's email or password","\u002Fguides\u002Fmanage-account","2.guides\u002F07.manage-account","i-lucide-user-cog",{"title":71,"path":72,"stem":73,"icon":74},"Add, rename, and remove passkeys","\u002Fguides\u002Fmanage-passkeys","2.guides\u002F08.manage-passkeys","i-lucide-scan-face",{"title":76,"path":77,"stem":78,"icon":79},"Link and unlink GitHub or Google accounts","\u002Fguides\u002Flink-external-accounts","2.guides\u002F09.link-external-accounts","i-lucide-link",{"title":81,"path":82,"stem":83,"icon":84},"Require step-up before sensitive actions","\u002Fguides\u002Fstep-up","2.guides\u002F10.step-up","i-lucide-shield-check",{"title":86,"path":87,"stem":88,"icon":89},"Call the API from a browser","\u002Fguides\u002Fbrowser-clients","2.guides\u002F11.browser-clients","i-lucide-globe",{"title":91,"path":92,"stem":93,"icon":94},"Prepare for production","\u002Fguides\u002Fproduction","2.guides\u002F12.production","i-lucide-factory","i-lucide-waypoints",{"title":97,"path":98,"stem":99,"children":100,"icon":113},"Composable endpoints","\u002Fcomposables","3.composables\u002F1.index",[101,104,108],{"title":102,"path":98,"stem":99,"icon":103},"How composition works","i-lucide-layout-grid",{"title":105,"path":106,"stem":107,"icon":39},"Composition requirements","\u002Fcomposables\u002Frequirements","3.composables\u002F2.requirements",{"title":109,"path":110,"stem":111,"icon":112},"Compose a custom auth stack","\u002Fcomposables\u002Frecipes","3.composables\u002F3.recipes","i-lucide-book-marked","i-lucide-blocks",{"title":115,"icon":116,"path":117,"stem":118,"children":119,"page":176},"Reference","i-lucide-book-open","\u002Fmodules","4.modules",[120,125,129,134,139,144,148,153,157,162,166,171],{"title":121,"path":122,"stem":123,"icon":124},"Endpoint reference","\u002Fmodules\u002Fendpoints","4.modules\u002F01.endpoints","i-lucide-route",{"title":126,"path":127,"stem":128,"icon":69},"Identity management module","\u002Fmodules\u002Fidentity-management","4.modules\u002F02.identity-management",{"title":130,"path":131,"stem":132,"icon":133},"Cookie sign-in module","\u002Fmodules\u002Fcookie-auth","4.modules\u002F03.cookie-auth","i-lucide-cookie",{"title":135,"path":136,"stem":137,"icon":138},"Identity bearer sign-in module","\u002Fmodules\u002Fbearer-auth","4.modules\u002F04.bearer-auth","i-lucide-key",{"title":140,"path":141,"stem":142,"icon":143},"JWT module","\u002Fmodules\u002Fjwt","4.modules\u002F05.jwt","i-lucide-fingerprint",{"title":145,"path":146,"stem":147,"icon":74},"Passkeys module","\u002Fmodules\u002Fpasskeys","4.modules\u002F06.passkeys",{"title":149,"path":150,"stem":151,"icon":152},"ReAuth module","\u002Fmodules\u002Freauth","4.modules\u002F07.reauth","i-lucide-shield-alert",{"title":154,"path":155,"stem":156,"icon":49},"External OAuth packages","\u002Fmodules\u002Fexternal-oauth","4.modules\u002F08.external-oauth",{"title":158,"path":159,"stem":160,"icon":161},"Configuration options","\u002Fmodules\u002Fconfiguration","4.modules\u002F09.configuration","i-lucide-settings",{"title":163,"path":164,"stem":165,"icon":84},"Antiforgery (CSRF) rules","\u002Fmodules\u002Fcsrf","4.modules\u002F10.csrf",{"title":167,"path":168,"stem":169,"icon":170},"Responses and errors","\u002Fmodules\u002Ferrors","4.modules\u002F11.errors","i-lucide-circle-alert",{"title":172,"path":173,"stem":174,"icon":175},"Rate-limit policies","\u002Fmodules\u002Frate-limits","4.modules\u002F12.rate-limits","i-lucide-gauge",false,{"title":178,"icon":179,"path":180,"stem":181,"children":182,"page":176},"Concepts","i-lucide-lightbulb","\u002Fconcepts","5.concepts",[183,188,193,198],{"title":184,"path":185,"stem":186,"icon":187},"AuthEndpoints compared with other options","\u002Fconcepts\u002Fcompare","5.concepts\u002F1.compare","i-lucide-git-compare",{"title":189,"path":190,"stem":191,"icon":192},"Stock Identity endpoints vs AuthEndpoints","\u002Fconcepts\u002Fstock-identity-vs-authendpoints","5.concepts\u002F2.stock-identity-vs-authendpoints","i-lucide-columns-2",{"title":194,"path":195,"stem":196,"icon":197},"Security model","\u002Fconcepts\u002Fsecurity-model","5.concepts\u002F3.security-model","i-lucide-shield",{"title":199,"path":200,"stem":201,"icon":202},"FAQ","\u002Fconcepts\u002Ffaq","5.concepts\u002F4.faq","i-lucide-circle-help",{"id":204,"title":71,"body":205,"description":600,"extension":601,"links":602,"meta":603,"navigation":604,"path":72,"seo":605,"stem":73,"__hash__":606},"docs\u002F2.guides\u002F08.manage-passkeys.md",{"type":206,"value":207,"toc":593},"minimark",[208,230,240,338,342,513,516,529,532,553,556,565,568,572,589],[209,210,211,212,216,217,220,221,224,225,229],"p",{},"A signed-in user manages passkeys under ",[213,214,215],"code",{},"\u002Faccount\u002Fpasskeys",". Every change requires a CSRF token in the ",[213,218,219],{},"RequestVerificationToken"," header and a fresh ReAuth proof. Send ",[213,222,223],{},"credentials: 'include'"," on every request. To create a new account with a passkey, see ",[226,227,41],"a",{"href":228},"\u002Fguides\u002Fregistration\u002F#register-with-a-passkey"," instead.",[231,232,234],"callout",{"icon":233},"i-lucide-info",[209,235,236,237,239],{},"Signed-in passkey management works on 3.1.0. The 3.1.0 bug affects only anonymous passkey registration and sign-in when JWT is not turned on. Upgrade to 3.1.1 to fix those. See ",[226,238,41],{"href":228},".",[241,242,243,262],"table",{},[244,245,246],"thead",{},[247,248,249,253,256,259],"tr",{},[250,251,252],"th",{},"Task",[250,254,255],{},"Endpoint",[250,257,258],{},"ReAuth",[250,260,261],{},"CSRF",[263,264,265,281,295,310,324],"tbody",{},[247,266,267,271,276,279],{},[268,269,270],"td",{},"Get creation options",[268,272,273],{},[213,274,275],{},"POST \u002Faccount\u002Fpasskeys\u002FcreationOptions",[268,277,278],{},"Yes",[268,280,278],{},[247,282,283,286,291,293],{},[268,284,285],{},"Add a passkey",[268,287,288],{},[213,289,290],{},"POST \u002Faccount\u002Fpasskeys\u002F",[268,292,278],{},[268,294,278],{},[247,296,297,300,305,308],{},[268,298,299],{},"List passkeys",[268,301,302],{},[213,303,304],{},"GET \u002Faccount\u002Fpasskeys\u002F",[268,306,307],{},"No",[268,309,307],{},[247,311,312,315,320,322],{},[268,313,314],{},"Rename a passkey",[268,316,317],{},[213,318,319],{},"PATCH \u002Faccount\u002Fpasskeys\u002F",[268,321,278],{},[268,323,278],{},[247,325,326,329,334,336],{},[268,327,328],{},"Remove a passkey",[268,330,331],{},[213,332,333],{},"DELETE \u002Faccount\u002Fpasskeys\u002F{credentialIdUrl}",[268,335,278],{},[268,337,278],{},[339,340,285],"h2",{"id":341},"add-a-passkey",[343,344,345,352,358,364,479],"ol",{},[346,347,348,349,239],"li",{},"Complete step-up. See ",[226,350,81],{"href":351},"\u002Fguides\u002Fstep-up\u002F",[346,353,354,355,357],{},"Get creation options from ",[213,356,275],{},". The options are for the signed-in user.",[346,359,360,361,239],{},"Call ",[213,362,363],{},"navigator.credentials.create",[346,365,366,367,369,370,373,374,377,378,381,382,239,385],{},"Send ",[213,368,290],{}," with ",[213,371,372],{},"{ \"credentialJson\", \"name\"? }",". AuthEndpoints trims ",[213,375,376],{},"name",". A name longer than 200 characters returns a ",[213,379,380],{},"400"," validation problem with the key ",[213,383,384],{},"Name",[386,387,392],"pre",{"className":388,"code":389,"language":390,"meta":391,"style":391},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const headers = { 'Content-Type': 'application\u002Fjson', 'RequestVerificationToken': csrfToken };\n\nconst options = await fetch('\u002Faccount\u002Fpasskeys\u002FcreationOptions', {\n  method: 'POST', credentials: 'include', headers\n}).then(r => r.json());\n\nconst credential = await navigator.credentials.create({\n  publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(options)\n});\n\nconst added = await fetch('\u002Faccount\u002Fpasskeys\u002F', {\n  method: 'POST', credentials: 'include', headers,\n  body: JSON.stringify({ credentialJson: JSON.stringify(credential), name: 'Work laptop' })\n}).then(r => r.json()); \u002F\u002F { credentialId, displayName, createdAt }\n","js","",[213,393,394,402,409,415,421,427,432,438,444,450,455,461,467,473],{"__ignoreMap":391},[395,396,399],"span",{"class":397,"line":398},"line",1,[395,400,401],{},"const headers = { 'Content-Type': 'application\u002Fjson', 'RequestVerificationToken': csrfToken };\n",[395,403,405],{"class":397,"line":404},2,[395,406,408],{"emptyLinePlaceholder":407},true,"\n",[395,410,412],{"class":397,"line":411},3,[395,413,414],{},"const options = await fetch('\u002Faccount\u002Fpasskeys\u002FcreationOptions', {\n",[395,416,418],{"class":397,"line":417},4,[395,419,420],{},"  method: 'POST', credentials: 'include', headers\n",[395,422,424],{"class":397,"line":423},5,[395,425,426],{},"}).then(r => r.json());\n",[395,428,430],{"class":397,"line":429},6,[395,431,408],{"emptyLinePlaceholder":407},[395,433,435],{"class":397,"line":434},7,[395,436,437],{},"const credential = await navigator.credentials.create({\n",[395,439,441],{"class":397,"line":440},8,[395,442,443],{},"  publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(options)\n",[395,445,447],{"class":397,"line":446},9,[395,448,449],{},"});\n",[395,451,453],{"class":397,"line":452},10,[395,454,408],{"emptyLinePlaceholder":407},[395,456,458],{"class":397,"line":457},11,[395,459,460],{},"const added = await fetch('\u002Faccount\u002Fpasskeys\u002F', {\n",[395,462,464],{"class":397,"line":463},12,[395,465,466],{},"  method: 'POST', credentials: 'include', headers,\n",[395,468,470],{"class":397,"line":469},13,[395,471,472],{},"  body: JSON.stringify({ credentialJson: JSON.stringify(credential), name: 'Work laptop' })\n",[395,474,476],{"class":397,"line":475},14,[395,477,478],{},"}).then(r => r.json()); \u002F\u002F { credentialId, displayName, createdAt }\n",[346,480,481,482],{},"Check the response:",[483,484,485,491,500,508],"ul",{},[346,486,487,490],{},[213,488,489],{},"200 { \"credentialId\", \"displayName\", \"createdAt\" }",": the passkey is stored.",[346,492,493,495,496,499],{},[213,494,380],{}," validation problem ",[213,497,498],{},"UserMismatch",": the passkey belongs to a different user.",[346,501,502,495,504,507],{},[213,503,380],{},[213,505,506],{},"InvalidPasskeyState",": no ceremony was underway. Start again from step 2.",[346,509,510,512],{},[213,511,380],{}," with a detail that starts with \"Could not add the passkey\": the attestation failed.",[339,514,299],{"id":515},"list-passkeys",[209,517,366,518,520,521,524,525,528],{},[213,519,304],{},". The response is ",[213,522,523],{},"{ \"passkeys\": [{ \"credentialId\", \"displayName\", \"createdAt\" }] }",". ",[213,526,527],{},"credentialId"," is Base64Url.",[339,530,314],{"id":531},"rename-a-passkey",[209,533,534,535,369,537,520,540,543,544,547,548,495,550,239],{},"Complete step-up, then send ",[213,536,319],{},[213,538,539],{},"{ \"id\": \"\u003CcredentialId>\", \"newName\": \"...\" }",[213,541,542],{},"200"," with an empty body. An unknown id returns ",[213,545,546],{},"404",". An id that is not valid Base64Url returns a ",[213,549,380],{},[213,551,552],{},"InvalidCredentialId",[339,554,328],{"id":555},"remove-a-passkey",[209,557,534,558,520,561,543,563,239],{},[213,559,560],{},"DELETE \u002Faccount\u002Fpasskeys\u002F\u003CcredentialId>",[213,562,542],{},[213,564,546],{},[209,566,567],{},"This endpoint does not check whether the passkey is the user's last sign-in method. Before you remove a passkey from an account with no password and no external login, warn the user.",[339,569,571],{"id":570},"related","Related",[483,573,574,580,584],{},[346,575,576],{},[226,577,579],{"href":578},"\u002Fguides\u002Fsign-in\u002F#sign-in-with-a-passkey","Sign in with a passkey",[346,581,582],{},[226,583,81],{"href":351},[346,585,586],{},[226,587,145],{"href":588},"\u002Fmodules\u002Fpasskeys\u002F",[590,591,592],"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);}",{"title":391,"searchDepth":398,"depth":404,"links":594},[595,596,597,598,599],{"id":341,"depth":404,"text":285},{"id":515,"depth":404,"text":299},{"id":531,"depth":404,"text":314},{"id":555,"depth":404,"text":328},{"id":570,"depth":404,"text":571},"Let a signed-in user add a passkey to their account, list passkeys, rename them, and remove them.","md",null,{},{"icon":74},{"title":71,"description":600},"WOl6DsT450QY1_5VwAUxeFMNKM4lXaB7u2WlJeFxk-I",[608,610],{"title":66,"path":67,"stem":68,"description":609,"icon":69,"children":-1},"Read account info, change the password, and change the email after the user confirms the new address.",{"title":76,"path":77,"stem":78,"description":611,"icon":79,"children":-1},"Let a signed-in user link a GitHub or Google login to their account, list linked logins, and unlink one.",1791123626899]