[{"data":1,"prerenderedAt":2249},["ShallowReactive",2],{"navLinks":3,"sidebar_docs_navigation_\u002Fdocs\u002Fiam":64,"navigation":257,"navLinks_footer":837,"\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fsecurity_page":850,"\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fsecurity_surround":1714,"\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fsecurity":1717},{"id":4,"extension":5,"links":6,"meta":61,"stem":62,"__hash__":63},"navigationMenu\u002Fnavigation.json","json",[7,52,57],{"nested":8,"label":9,"icon":10,"to":11,"children":12},true,"Docs","i-lucide-book-open","\u002Fdocs\u002Fgetting-started",[13,19,26,32,39,45],{"label":14,"icon":15,"to":11,"description":16,"github":17,"badge":18},"Getting Started","i-lucide-rocket","An introduction to help you understand the core components.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fdocshub","Start Here",{"label":20,"icon":21,"to":22,"description":23,"github":24,"badge":25},"Auth H3 Client","i-lucide-key-round","\u002Fdocs\u002Fauth-h3client","Seamlessly enforce OAuth 2.0 authentication and session management integrated directly as the client of the IAM module.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fauth-h3client","Core",{"label":27,"icon":28,"to":29,"description":30,"github":31,"badge":25},"IAM","i-lucide-shield-check","\u002Fdocs\u002Fiam","Identity and Access Management featuring granular roles, permissions, and security policies.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fauth",{"label":33,"icon":34,"to":35,"description":36,"github":37,"badge":38},"Bot Detection","i-lucide-cpu","\u002Fdocs\u002Fbot-detection","Advanced behavioral analysis and request fingerprinting to stop malicious automated traffic.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fbot-detector","Security",{"label":40,"icon":41,"to":42,"description":43,"github":44,"badge":38},"Shield Base","i-lucide-database-zap","\u002Fdocs\u002Fshield-base","CLI and programmatic toolkit for compiling offline-ready IP intelligence databases from BGP, GeoIP, Tor, FireHOL, and other public threat feeds.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fshield-base-cli",{"label":46,"icon":47,"to":48,"description":49,"github":50,"badge":51},"Utils","i-lucide-wrench","\u002Fdocs\u002Futils","A standard library of highly optimized helpers for formatting, validation, and core logic.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Futils","Library",{"nested":53,"label":54,"icon":55,"to":56},false,"Blog","i-lucide-pen-line","\u002Fblog",{"nested":53,"label":58,"icon":59,"to":60},"Website","lucide:app-window-mac","https:\u002F\u002Friavzon.com",{},"navigation","gkaQ0xRGxSLrLyM3kttLe0oBwkrR1EBjlepF8LSbwF8",[65],{"title":9,"path":66,"stem":67,"children":68,"page":53},"\u002Fdocs","docs",[69],{"title":27,"path":29,"stem":70,"children":71},"docs\u002Fiam\u002Findex",[72,73,76,216,219,236,240],{"title":27,"path":29,"stem":70},{"title":14,"path":74,"stem":75},"\u002Fdocs\u002Fiam\u002Fgetting-started","docs\u002Fiam\u002F00.getting-started",{"title":77,"path":78,"stem":79,"children":80},"Essentials","\u002Fdocs\u002Fiam\u002Fessentials","docs\u002Fiam\u002F01.essentials\u002Findex",[81,82,86,90,94,98,102,106,110,114,118,122,126,130,134,138,142,146,150,154,158,162,166],{"title":77,"path":78,"stem":79},{"title":83,"path":84,"stem":85},"Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Ftokens","docs\u002Fiam\u002F01.essentials\u002F00.tokens",{"title":87,"path":88,"stem":89},"Access Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Faccess-tokens","docs\u002Fiam\u002F01.essentials\u002F01.access-tokens",{"title":91,"path":92,"stem":93},"Refresh Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Frefresh-tokens","docs\u002Fiam\u002F01.essentials\u002F02.refresh-tokens",{"title":95,"path":96,"stem":97},"Anomaly Detection","\u002Fdocs\u002Fiam\u002Fessentials\u002Fanomalies","docs\u002Fiam\u002F01.essentials\u002F03.anomalies",{"title":99,"path":100,"stem":101},"Signup","\u002Fdocs\u002Fiam\u002Fessentials\u002Fsignup","docs\u002Fiam\u002F01.essentials\u002F04.signup",{"title":103,"path":104,"stem":105},"Login","\u002Fdocs\u002Fiam\u002Fessentials\u002Flogin","docs\u002Fiam\u002F01.essentials\u002F05.login",{"title":107,"path":108,"stem":109},"Logout","\u002Fdocs\u002Fiam\u002Fessentials\u002Flogout","docs\u002Fiam\u002F01.essentials\u002F06.logout",{"title":111,"path":112,"stem":113},"OAuth","\u002Fdocs\u002Fiam\u002Fessentials\u002Foauth","docs\u002Fiam\u002F01.essentials\u002F07.oauth",{"title":115,"path":116,"stem":117},"Magic Links","\u002Fdocs\u002Fiam\u002Fessentials\u002Fmagic-links","docs\u002Fiam\u002F01.essentials\u002F08.magic-links",{"title":119,"path":120,"stem":121},"Emails","\u002Fdocs\u002Fiam\u002Fessentials\u002Femails","docs\u002Fiam\u002F01.essentials\u002F09.emails",{"title":123,"path":124,"stem":125},"MFA","\u002Fdocs\u002Fiam\u002Fessentials\u002Fmfa","docs\u002Fiam\u002F01.essentials\u002F10.mfa",{"title":127,"path":128,"stem":129},"Fingerprinting","\u002Fdocs\u002Fiam\u002Fessentials\u002Ffingerprinting","docs\u002Fiam\u002F01.essentials\u002F11.fingerprinting",{"title":131,"path":132,"stem":133},"Backend for Frontend","\u002Fdocs\u002Fiam\u002Fessentials\u002Fbff","docs\u002Fiam\u002F01.essentials\u002F12.bff",{"title":135,"path":136,"stem":137},"HMAC Authentication","\u002Fdocs\u002Fiam\u002Fessentials\u002Fhmac","docs\u002Fiam\u002F01.essentials\u002F13.hmac",{"title":139,"path":140,"stem":141},"XSS Protection","\u002Fdocs\u002Fiam\u002Fessentials\u002Fxss","docs\u002Fiam\u002F01.essentials\u002F14.xss",{"title":143,"path":144,"stem":145},"Logging","\u002Fdocs\u002Fiam\u002Fessentials\u002Flogging","docs\u002Fiam\u002F01.essentials\u002F15.logging",{"title":147,"path":148,"stem":149},"Rate Limiting","\u002Fdocs\u002Fiam\u002Fessentials\u002Frate-limiting","docs\u002Fiam\u002F01.essentials\u002F16.rate-limiting",{"title":151,"path":152,"stem":153},"Database","\u002Fdocs\u002Fiam\u002Fessentials\u002Fdatabase","docs\u002Fiam\u002F01.essentials\u002F17.database",{"title":155,"path":156,"stem":157},"Cookies","\u002Fdocs\u002Fiam\u002Fessentials\u002Fcookies","docs\u002Fiam\u002F01.essentials\u002F18.cookies",{"title":159,"path":160,"stem":161},"Service Startup","\u002Fdocs\u002Fiam\u002Fessentials\u002Fservice","docs\u002Fiam\u002F01.essentials\u002F19.service",{"title":163,"path":164,"stem":165},"Password Reset","\u002Fdocs\u002Fiam\u002Fessentials\u002Fpassword-reset","docs\u002Fiam\u002F01.essentials\u002F20.password-reset",{"title":167,"path":168,"stem":169,"children":170},"API Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi","docs\u002Fiam\u002F01.essentials\u002F21.api\u002Findex",[171,172,176,180,210,213],{"title":167,"path":168,"stem":169},{"title":173,"path":174,"stem":175},"Creating Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fcreation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F00.creation",{"title":177,"path":178,"stem":179},"Verifying Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fverification","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F01.verification",{"title":181,"path":182,"stem":183,"children":184},"Manage Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002Findex",[185,186,190,194,198,202,206],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189},"Privileges","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fprivilege","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F00.privilege",{"title":191,"path":192,"stem":193},"Revocation","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Frevocation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F01.revocation",{"title":195,"path":196,"stem":197},"Rotation","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Frotation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F02.rotation",{"title":199,"path":200,"stem":201},"IP Restriction","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fip-updates","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F03.ip-updates",{"title":203,"path":204,"stem":205},"Metadata","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fmetadata","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F04.metadata",{"title":207,"path":208,"stem":209},"Token Listing","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Flist","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F05.list",{"title":147,"path":211,"stem":212},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Frate-limiting","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F03.rate-limiting",{"title":38,"path":214,"stem":215},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fsecurity","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F04.security",{"title":38,"path":217,"stem":218},"\u002Fdocs\u002Fiam\u002Fsecurity","docs\u002Fiam\u002F02.security",{"title":220,"path":221,"stem":222,"children":223,"page":53},"Guides","\u002Fdocs\u002Fiam\u002Fguides","docs\u002Fiam\u002F03.guides",[224,228,232],{"title":225,"path":226,"stem":227},"Deployment","\u002Fdocs\u002Fiam\u002Fguides\u002Fdeployment","docs\u002Fiam\u002F03.guides\u002Fdeployment",{"title":229,"path":230,"stem":231},"Operation Scripts","\u002Fdocs\u002Fiam\u002Fguides\u002Foperation-scripts","docs\u002Fiam\u002F03.guides\u002Foperation-scripts",{"title":233,"path":234,"stem":235},"Role-Based Access Control","\u002Fdocs\u002Fiam\u002Fguides\u002Frbac","docs\u002Fiam\u002F03.guides\u002Frbac",{"title":237,"path":238,"stem":239},"Configuration","\u002Fdocs\u002Fiam\u002Fconfiguration","docs\u002Fiam\u002F04.configuration",{"title":241,"path":242,"stem":243,"children":244,"page":53},"Api","\u002Fdocs\u002Fiam\u002Fapi","docs\u002Fiam\u002F05.API",[245,249,253],{"title":246,"path":247,"stem":248},"API Reference","\u002Fdocs\u002Fiam\u002Fapi\u002Fapi","docs\u002Fiam\u002F05.API\u002F00.api",{"title":250,"path":251,"stem":252},"Middleware Reference","\u002Fdocs\u002Fiam\u002Fapi\u002Fmiddlewares","docs\u002Fiam\u002F05.API\u002F02.middlewares",{"title":254,"path":255,"stem":256},"Routes Reference","\u002Fdocs\u002Fiam\u002Fapi\u002Froutes","docs\u002Fiam\u002F05.API\u002F03.routes",[258],{"title":9,"path":66,"stem":67,"children":259,"page":53},[260,398,516,521,577,644],{"title":20,"path":22,"stem":261,"children":262},"docs\u002Fauth-h3client\u002Findex",[263,264,273,307,331,353,356,376,379],{"title":20,"path":22,"stem":261},{"title":14,"path":265,"stem":266,"children":267},"\u002Fdocs\u002Fauth-h3client\u002Fgetting-started","docs\u002Fauth-h3client\u002F00.getting-started\u002Findex",[268,269],{"title":14,"path":265,"stem":266},{"title":270,"path":271,"stem":272},"Nuxt Module","\u002Fdocs\u002Fauth-h3client\u002Fgetting-started\u002Fnuxt","docs\u002Fauth-h3client\u002F00.getting-started\u002F00.nuxt",{"title":77,"path":274,"stem":275,"children":276},"\u002Fdocs\u002Fauth-h3client\u002Fessentials","docs\u002Fauth-h3client\u002F01.essentials\u002Findex",[277,278,282,286,290,294,298,301,304],{"title":77,"path":274,"stem":275},{"title":279,"path":280,"stem":281},"Session Management","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fsession","docs\u002Fauth-h3client\u002F01.essentials\u002F00.session",{"title":283,"path":284,"stem":285},"Route Protection","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Froute-protection","docs\u002Fauth-h3client\u002F01.essentials\u002F01.route-protection",{"title":287,"path":288,"stem":289},"CSRF Protection","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fcsrf","docs\u002Fauth-h3client\u002F01.essentials\u002F02.csrf",{"title":291,"path":292,"stem":293},"Auth Flows","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fauth-flows","docs\u002Fauth-h3client\u002F01.essentials\u002F03.auth-flows",{"title":295,"path":296,"stem":297},"OAuth and OIDC","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Foauth","docs\u002Fauth-h3client\u002F01.essentials\u002F04.oauth",{"title":33,"path":299,"stem":300},"\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fbot-detection","docs\u002Fauth-h3client\u002F01.essentials\u002F05.bot-detection",{"title":155,"path":302,"stem":303},"\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fcookies","docs\u002Fauth-h3client\u002F01.essentials\u002F06.cookies",{"title":143,"path":305,"stem":306},"\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Flogging","docs\u002Fauth-h3client\u002F01.essentials\u002F07.logging",{"title":123,"path":308,"stem":309,"children":310},"\u002Fdocs\u002Fauth-h3client\u002Fmfa","docs\u002Fauth-h3client\u002F02.mfa\u002Findex",[311,312,316,319,323,327],{"title":123,"path":308,"stem":309},{"title":313,"path":314,"stem":315},"Built-in MFA","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fbuilt-in-flow","docs\u002Fauth-h3client\u002F02.mfa\u002F01.built-in-flow",{"title":163,"path":317,"stem":318},"\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fpassword-reset","docs\u002Fauth-h3client\u002F02.mfa\u002F02.password-reset",{"title":320,"path":321,"stem":322},"Email Change","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Femail-change","docs\u002Fauth-h3client\u002F02.mfa\u002F03.email-change",{"title":324,"path":325,"stem":326},"Custom MFA Flow","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fcustom-flow","docs\u002Fauth-h3client\u002F02.mfa\u002F04.custom-flow",{"title":328,"path":329,"stem":330},"Client-Side MFA","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fclient-side","docs\u002Fauth-h3client\u002F02.mfa\u002F05.client-side",{"title":332,"path":333,"stem":334,"children":335},"Client-side","\u002Fdocs\u002Fauth-h3client\u002Fclient","docs\u002Fauth-h3client\u002F03.client\u002Findex",[336,337,341,345,349],{"title":332,"path":333,"stem":334},{"title":338,"path":339,"stem":340},"useAuthData","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fuse-auth-data","docs\u002Fauth-h3client\u002F03.client\u002F00.use-auth-data",{"title":342,"path":343,"stem":344},"useMagicLink","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fuse-magic-link","docs\u002Fauth-h3client\u002F03.client\u002F01.use-magic-link",{"title":346,"path":347,"stem":348},"executeRequest","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fexecute-request","docs\u002Fauth-h3client\u002F03.client\u002F02.execute-request",{"title":350,"path":351,"stem":352},"getCsrfToken","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fget-csrf-token","docs\u002Fauth-h3client\u002F03.client\u002F03.get-csrf-token",{"title":38,"path":354,"stem":355},"\u002Fdocs\u002Fauth-h3client\u002Fsecurity","docs\u002Fauth-h3client\u002F04.security",{"title":220,"path":357,"stem":358,"children":359,"page":53},"\u002Fdocs\u002Fauth-h3client\u002Fguides","docs\u002Fauth-h3client\u002F05.guides",[360,364,368,372],{"title":361,"path":362,"stem":363},"H3 and Nitro Setup","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fh3-nitro","docs\u002Fauth-h3client\u002F05.guides\u002F00.h3-nitro",{"title":365,"path":366,"stem":367},"HMAC Inter-service Auth","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fhmac","docs\u002Fauth-h3client\u002F05.guides\u002Fhmac",{"title":369,"path":370,"stem":371},"Image Upload","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fimage-upload","docs\u002Fauth-h3client\u002F05.guides\u002Fimage-upload",{"title":373,"path":374,"stem":375},"mTLS Configuration","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fmtls","docs\u002Fauth-h3client\u002F05.guides\u002Fmtls",{"title":237,"path":377,"stem":378},"\u002Fdocs\u002Fauth-h3client\u002Fconfiguration","docs\u002Fauth-h3client\u002F06.configuration",{"title":246,"path":380,"stem":381,"children":382},"\u002Fdocs\u002Fauth-h3client\u002Fapi","docs\u002Fauth-h3client\u002F07.api\u002Findex",[383,384,387,390,394],{"title":246,"path":380,"stem":381},{"title":254,"path":385,"stem":386},"\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fcontrollers","docs\u002Fauth-h3client\u002F07.api\u002F00.controllers",{"title":250,"path":388,"stem":389},"\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fmiddleware","docs\u002Fauth-h3client\u002F07.api\u002F01.middleware",{"title":391,"path":392,"stem":393},"Client-side Reference","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fcomposables","docs\u002Fauth-h3client\u002F07.api\u002F02.composables",{"title":395,"path":396,"stem":397},"Utilities","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Futilities","docs\u002Fauth-h3client\u002F07.api\u002F03.utilities",{"title":399,"path":35,"stem":400,"children":401},"Bot Detector","docs\u002Fbot-detection\u002Findex",[402,403,406,410,414,433,507,510,513],{"title":399,"path":35,"stem":400},{"title":14,"path":404,"stem":405},"\u002Fdocs\u002Fbot-detection\u002Fgetting-started","docs\u002Fbot-detection\u002F00.getting-started",{"title":407,"path":408,"stem":409},"CLI","\u002Fdocs\u002Fbot-detection\u002Fcli","docs\u002Fbot-detection\u002F01.cli",{"title":411,"path":412,"stem":413},"Data Sources","\u002Fdocs\u002Fbot-detection\u002Fdata-sources","docs\u002Fbot-detection\u002F02.data-sources",{"title":220,"path":415,"stem":416,"children":417,"page":53},"\u002Fdocs\u002Fbot-detection\u002Fguides","docs\u002Fbot-detection\u002F03.guides",[418,422,426,429],{"title":419,"path":420,"stem":421},"Custom Checkers","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fcustom","docs\u002Fbot-detection\u002F03.guides\u002FCUSTOM",{"title":423,"path":424,"stem":425},"Scheduling Database Generation","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fgenerate","docs\u002Fbot-detection\u002F03.guides\u002FGENERATE",{"title":143,"path":427,"stem":428},"\u002Fdocs\u002Fbot-detection\u002Fguides\u002Flogging","docs\u002Fbot-detection\u002F03.guides\u002FLOGGING",{"title":430,"path":431,"stem":432},"Score Modes and Reputation Healing","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fscore","docs\u002Fbot-detection\u002F03.guides\u002FSCORE",{"title":434,"path":435,"stem":436,"children":437},"Checkers","\u002Fdocs\u002Fbot-detection\u002Fcheckers","docs\u002Fbot-detection\u002F04.checkers\u002Findex",[438,439,443,447,451,455,459,463,467,471,475,479,483,487,491,495,499,503],{"title":434,"path":435,"stem":436},{"title":440,"path":441,"stem":442},"IP Validation","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fip-validation","docs\u002Fbot-detection\u002F04.checkers\u002F01.ip-validation",{"title":444,"path":445,"stem":446},"Good \u002F Bad Bot Verification","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fgood-bots","docs\u002Fbot-detection\u002F04.checkers\u002F02.good-bots",{"title":448,"path":449,"stem":450},"Browser & Device Fingerprint","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fbrowser-device","docs\u002Fbot-detection\u002F04.checkers\u002F03.browser-device",{"title":452,"path":453,"stem":454},"Locale Map","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Flocale-map","docs\u002Fbot-detection\u002F04.checkers\u002F04.locale-map",{"title":456,"path":457,"stem":458},"Known Threats","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-threats","docs\u002Fbot-detection\u002F04.checkers\u002F05.known-threats",{"title":460,"path":461,"stem":462},"ASN Classification","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fasn-classification","docs\u002Fbot-detection\u002F04.checkers\u002F06.asn-classification",{"title":464,"path":465,"stem":466},"Tor Analysis","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Ftor-analysis","docs\u002Fbot-detection\u002F04.checkers\u002F07.tor-analysis",{"title":468,"path":469,"stem":470},"Timezone Consistency","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Ftimezone-consistency","docs\u002Fbot-detection\u002F04.checkers\u002F08.timezone-consistency",{"title":472,"path":473,"stem":474},"Honeypot","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fhoneypot","docs\u002Fbot-detection\u002F04.checkers\u002F09.honeypot",{"title":476,"path":477,"stem":478},"Known Bad IPs","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-bad-ips","docs\u002Fbot-detection\u002F04.checkers\u002F10.known-bad-ips",{"title":480,"path":481,"stem":482},"Behavior Rate","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fbehavior-rate","docs\u002Fbot-detection\u002F04.checkers\u002F11.behavior-rate",{"title":484,"path":485,"stem":486},"Proxy \u002F ISP \u002F Cookie","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fproxy-isp-cookies","docs\u002Fbot-detection\u002F04.checkers\u002F12.proxy-isp-cookies",{"title":488,"path":489,"stem":490},"Session Coherence","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fsession-coherence","docs\u002Fbot-detection\u002F04.checkers\u002F13.session-coherence",{"title":492,"path":493,"stem":494},"Velocity Fingerprint","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fvelocity-fingerprint","docs\u002Fbot-detection\u002F04.checkers\u002F14.velocity-fingerprint",{"title":496,"path":497,"stem":498},"UA & Header Analysis","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fua-header","docs\u002Fbot-detection\u002F04.checkers\u002F15.ua-header",{"title":500,"path":501,"stem":502},"Geolocation","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fgeolocation","docs\u002Fbot-detection\u002F04.checkers\u002F16.geolocation",{"title":504,"path":505,"stem":506},"Known Bad User-Agents","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-bad-ua","docs\u002Fbot-detection\u002F04.checkers\u002F17.known-bad-ua",{"title":38,"path":508,"stem":509},"\u002Fdocs\u002Fbot-detection\u002Fsecurity","docs\u002Fbot-detection\u002F04.security",{"title":246,"path":511,"stem":512},"\u002Fdocs\u002Fbot-detection\u002Fapi","docs\u002Fbot-detection\u002F05.api",{"title":237,"path":514,"stem":515},"\u002Fdocs\u002Fbot-detection\u002Fconfiguration","docs\u002Fbot-detection\u002F06.configuration",{"title":517,"path":11,"stem":518,"children":519},"Introduction","docs\u002Fgetting-started\u002Findex",[520],{"title":517,"path":11,"stem":518},{"title":27,"path":29,"stem":70,"children":522},[523,524,525,565,566,571,572],{"title":27,"path":29,"stem":70},{"title":14,"path":74,"stem":75},{"title":77,"path":78,"stem":79,"children":526},[527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549],{"title":77,"path":78,"stem":79},{"title":83,"path":84,"stem":85},{"title":87,"path":88,"stem":89},{"title":91,"path":92,"stem":93},{"title":95,"path":96,"stem":97},{"title":99,"path":100,"stem":101},{"title":103,"path":104,"stem":105},{"title":107,"path":108,"stem":109},{"title":111,"path":112,"stem":113},{"title":115,"path":116,"stem":117},{"title":119,"path":120,"stem":121},{"title":123,"path":124,"stem":125},{"title":127,"path":128,"stem":129},{"title":131,"path":132,"stem":133},{"title":135,"path":136,"stem":137},{"title":139,"path":140,"stem":141},{"title":143,"path":144,"stem":145},{"title":147,"path":148,"stem":149},{"title":151,"path":152,"stem":153},{"title":155,"path":156,"stem":157},{"title":159,"path":160,"stem":161},{"title":163,"path":164,"stem":165},{"title":167,"path":168,"stem":169,"children":550},[551,552,553,554,563,564],{"title":167,"path":168,"stem":169},{"title":173,"path":174,"stem":175},{"title":177,"path":178,"stem":179},{"title":181,"path":182,"stem":183,"children":555},[556,557,558,559,560,561,562],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189},{"title":191,"path":192,"stem":193},{"title":195,"path":196,"stem":197},{"title":199,"path":200,"stem":201},{"title":203,"path":204,"stem":205},{"title":207,"path":208,"stem":209},{"title":147,"path":211,"stem":212},{"title":38,"path":214,"stem":215},{"title":38,"path":217,"stem":218},{"title":220,"path":221,"stem":222,"children":567,"page":53},[568,569,570],{"title":225,"path":226,"stem":227},{"title":229,"path":230,"stem":231},{"title":233,"path":234,"stem":235},{"title":237,"path":238,"stem":239},{"title":241,"path":242,"stem":243,"children":573,"page":53},[574,575,576],{"title":246,"path":247,"stem":248},{"title":250,"path":251,"stem":252},{"title":254,"path":255,"stem":256},{"title":40,"path":42,"stem":578,"children":579},"docs\u002Fshield-base\u002Findex",[580,581,584,588,629,633,637,641],{"title":40,"path":42,"stem":578},{"title":14,"path":582,"stem":583},"\u002Fdocs\u002Fshield-base\u002Fgetting-started","docs\u002Fshield-base\u002F00.getting-started",{"title":585,"path":586,"stem":587},"CLI Reference","\u002Fdocs\u002Fshield-base\u002Fcli","docs\u002Fshield-base\u002F01.cli",{"title":411,"path":589,"stem":590,"children":591},"\u002Fdocs\u002Fshield-base\u002Fdata-sources","docs\u002Fshield-base\u002F02.data-sources\u002Findex",[592,593,597,601,605,609,613,617,621,625],{"title":411,"path":589,"stem":590},{"title":594,"path":595,"stem":596},"BGP \u002F ASN","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fbgp","docs\u002Fshield-base\u002F02.data-sources\u002Fbgp",{"title":598,"path":599,"stem":600},"City Geolocation","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcity","docs\u002Fshield-base\u002F02.data-sources\u002Fcity",{"title":602,"path":603,"stem":604},"Country Geolocation","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcountry","docs\u002Fshield-base\u002F02.data-sources\u002Fcountry",{"title":606,"path":607,"stem":608},"Verified Crawlers","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcrawlers","docs\u002Fshield-base\u002F02.data-sources\u002Fcrawlers",{"title":610,"path":611,"stem":612},"Disposable Emails","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Femail","docs\u002Fshield-base\u002F02.data-sources\u002Femail",{"title":614,"path":615,"stem":616},"FireHOL Threat Intelligence","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Ffirehol","docs\u002Fshield-base\u002F02.data-sources\u002Ffirehol",{"title":618,"path":619,"stem":620},"Proxy Detection","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fproxy","docs\u002Fshield-base\u002F02.data-sources\u002Fproxy",{"title":622,"path":623,"stem":624},"Tor Nodes","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Ftor","docs\u002Fshield-base\u002F02.data-sources\u002Ftor",{"title":626,"path":627,"stem":628},"Suspicious User-Agents","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fuseragent","docs\u002Fshield-base\u002F02.data-sources\u002Fuseragent",{"title":630,"path":631,"stem":632},"Programmatic Usage","\u002Fdocs\u002Fshield-base\u002Fusage","docs\u002Fshield-base\u002F03.usage",{"title":634,"path":635,"stem":636},"Custom Data Sources","\u002Fdocs\u002Fshield-base\u002Fcustom-data-sources","docs\u002Fshield-base\u002F04.custom-data-sources",{"title":638,"path":639,"stem":640},"TypeScript Types","\u002Fdocs\u002Fshield-base\u002Ftypes","docs\u002Fshield-base\u002F05.types",{"title":246,"path":642,"stem":643},"\u002Fdocs\u002Fshield-base\u002Fapi","docs\u002Fshield-base\u002F06.api",{"title":395,"path":48,"stem":645,"children":646},"docs\u002Futils\u002Findex",[647,648,665,698,795],{"title":395,"path":48,"stem":645},{"title":649,"path":650,"stem":651,"children":652,"page":53},"Eslint","\u002Fdocs\u002Futils\u002Feslint","docs\u002Futils\u002Feslint",[653,657,661],{"title":654,"path":655,"stem":656},"React Config","\u002Fdocs\u002Futils\u002Feslint\u002Freact","docs\u002Futils\u002Feslint\u002Freact",{"title":658,"path":659,"stem":660},"TypeScript Config","\u002Fdocs\u002Futils\u002Feslint\u002Ftypescript","docs\u002Futils\u002Feslint\u002Ftypescript",{"title":662,"path":663,"stem":664},"Vue Config","\u002Fdocs\u002Futils\u002Feslint\u002Fvue","docs\u002Futils\u002Feslint\u002Fvue",{"title":666,"path":667,"stem":668,"children":669,"page":53},"Server","\u002Fdocs\u002Futils\u002Fserver","docs\u002Futils\u002Fserver",[670,674,678,682,686,690,694],{"title":671,"path":672,"stem":673},"Encryption","\u002Fdocs\u002Futils\u002Fserver\u002Fencryption","docs\u002Futils\u002Fserver\u002Fencryption",{"title":675,"path":676,"stem":677},"Path Resolver","\u002Fdocs\u002Futils\u002Fserver\u002Fpathresolver","docs\u002Futils\u002Fserver\u002FpathResolver",{"title":679,"path":680,"stem":681},"File Replacements","\u002Fdocs\u002Futils\u002Fserver\u002Freplace","docs\u002Futils\u002Fserver\u002Freplace",{"title":683,"path":684,"stem":685},"run","\u002Fdocs\u002Futils\u002Fserver\u002Frun","docs\u002Futils\u002Fserver\u002Frun",{"title":687,"path":688,"stem":689},"scheduleTask","\u002Fdocs\u002Futils\u002Fserver\u002Fscheduletask","docs\u002Futils\u002Fserver\u002FscheduleTask",{"title":691,"path":692,"stem":693},"spawnRun","\u002Fdocs\u002Futils\u002Fserver\u002Fspawnrun","docs\u002Futils\u002Fserver\u002FspawnRun",{"title":695,"path":696,"stem":697},"uploadCsv","\u002Fdocs\u002Futils\u002Fserver\u002Fuploadcsv","docs\u002Futils\u002Fserver\u002FuploadCsv",{"title":699,"path":700,"stem":701,"children":702,"page":53},"Shared","\u002Fdocs\u002Futils\u002Fshared","docs\u002Futils\u002Fshared",[703,707,711,715,719,723,727,731,735,739,743,747,751,755,759,763,767,771,775,779,783,787,791],{"title":704,"path":705,"stem":706},"BatchQueue","\u002Fdocs\u002Futils\u002Fshared\u002Fbatchqueue","docs\u002Futils\u002Fshared\u002FbatchQueue",{"title":708,"path":709,"stem":710},"capitalize","\u002Fdocs\u002Futils\u002Fshared\u002Fcapitalize","docs\u002Futils\u002Fshared\u002Fcapitalize",{"title":712,"path":713,"stem":714},"chunkProcess","\u002Fdocs\u002Futils\u002Fshared\u002Fchunkprocess","docs\u002Futils\u002Fshared\u002FchunkProcess",{"title":716,"path":717,"stem":718},"cleanObject","\u002Fdocs\u002Futils\u002Fshared\u002Fcleanobject","docs\u002Futils\u002Fshared\u002FcleanObject",{"title":720,"path":721,"stem":722},"createConfigManager","\u002Fdocs\u002Futils\u002Fshared\u002Fconfigurationdefiner","docs\u002Futils\u002Fshared\u002FconfigurationDefiner",{"title":724,"path":725,"stem":726},"debounce","\u002Fdocs\u002Futils\u002Fshared\u002Fdebounce","docs\u002Futils\u002Fshared\u002Fdebounce",{"title":728,"path":729,"stem":730},"ensureArray","\u002Fdocs\u002Futils\u002Fshared\u002Fensurearray","docs\u002Futils\u002Fshared\u002FensureArray",{"title":732,"path":733,"stem":734},"fetchWithRetry","\u002Fdocs\u002Futils\u002Fshared\u002Ffetchwithretry","docs\u002Futils\u002Fshared\u002FfetchWithRetry",{"title":736,"path":737,"stem":738},"filterEmptyValues","\u002Fdocs\u002Futils\u002Fshared\u002Ffilteremptyvalues","docs\u002Futils\u002Fshared\u002FfilterEmptyValues",{"title":740,"path":741,"stem":742},"findStringsInObject","\u002Fdocs\u002Futils\u002Fshared\u002Ffindobjectvalues","docs\u002Futils\u002Fshared\u002FfindObjectValues",{"title":744,"path":745,"stem":746},"fisherYatesShuffle","\u002Fdocs\u002Futils\u002Fshared\u002Ffisheryatesshuffle","docs\u002Futils\u002Fshared\u002FfisherYatesShuffle",{"title":748,"path":749,"stem":750},"getRandomImage","\u002Fdocs\u002Futils\u002Fshared\u002Fgetrandomimage","docs\u002Futils\u002Fshared\u002FgetRandomImage",{"title":752,"path":753,"stem":754},"isObjectHasValues","\u002Fdocs\u002Futils\u002Fshared\u002Fisobjecthasvalues","docs\u002Futils\u002Fshared\u002FisObjectHasValues",{"title":756,"path":757,"stem":758},"isAsyncOrPromise","\u002Fdocs\u002Futils\u002Fshared\u002Fispromise","docs\u002Futils\u002Fshared\u002FisPromise",{"title":760,"path":761,"stem":762},"MiniCache","\u002Fdocs\u002Futils\u002Fshared\u002Fminicache","docs\u002Futils\u002Fshared\u002FminiCache",{"title":764,"path":765,"stem":766},"parseCookies","\u002Fdocs\u002Futils\u002Fshared\u002Fparserawcookies","docs\u002Futils\u002Fshared\u002FparseRawCookies",{"title":768,"path":769,"stem":770},"safeAction","\u002Fdocs\u002Futils\u002Fshared\u002Fpromiselocker","docs\u002Futils\u002Fshared\u002FpromiseLocker",{"title":772,"path":773,"stem":774},"Random","\u002Fdocs\u002Futils\u002Fshared\u002Frandom","docs\u002Futils\u002Fshared\u002Frandom",{"title":776,"path":777,"stem":778},"range","\u002Fdocs\u002Futils\u002Fshared\u002Frange","docs\u002Futils\u002Fshared\u002Frange",{"title":780,"path":781,"stem":782},"rateLimiters","\u002Fdocs\u002Futils\u002Fshared\u002Fratelimiters","docs\u002Futils\u002Fshared\u002FrateLimiters",{"title":784,"path":785,"stem":786},"safeObjectMerge","\u002Fdocs\u002Futils\u002Fshared\u002Fsafemerge","docs\u002Futils\u002Fshared\u002FsafeMerge",{"title":788,"path":789,"stem":790},"textTruncation","\u002Fdocs\u002Futils\u002Fshared\u002Ftexttruncation","docs\u002Futils\u002Fshared\u002FtextTruncation",{"title":792,"path":793,"stem":794},"validateZodSchema","\u002Fdocs\u002Futils\u002Fshared\u002Fvalidatezodschema","docs\u002Futils\u002Fshared\u002FvalidateZodSchema",{"title":796,"path":797,"stem":798,"children":799},"Utility Types","\u002Fdocs\u002Futils\u002Ftypes","docs\u002Futils\u002Ftypes\u002Findex",[800,801,805,809,813,817,821,825,829,833],{"title":796,"path":797,"stem":798},{"title":802,"path":803,"stem":804},"Brand","\u002Fdocs\u002Futils\u002Ftypes\u002Fbrand","docs\u002Futils\u002Ftypes\u002FBrand",{"title":806,"path":807,"stem":808},"DeepPartial","\u002Fdocs\u002Futils\u002Ftypes\u002Fdeeppartial","docs\u002Futils\u002Ftypes\u002FDeepPartial",{"title":810,"path":811,"stem":812},"Merge","\u002Fdocs\u002Futils\u002Ftypes\u002Fmerge","docs\u002Futils\u002Ftypes\u002FMerge",{"title":814,"path":815,"stem":816},"NonNullable","\u002Fdocs\u002Futils\u002Ftypes\u002Fnonnullable","docs\u002Futils\u002Ftypes\u002FNonNullable",{"title":818,"path":819,"stem":820},"Prettify","\u002Fdocs\u002Futils\u002Ftypes\u002Fprettify","docs\u002Futils\u002Ftypes\u002FPrettify",{"title":822,"path":823,"stem":824},"PromiseType","\u002Fdocs\u002Futils\u002Ftypes\u002Fpromisetype","docs\u002Futils\u002Ftypes\u002FPromiseType",{"title":826,"path":827,"stem":828},"RequireKeys","\u002Fdocs\u002Futils\u002Ftypes\u002Frequirekeys","docs\u002Futils\u002Ftypes\u002FRequireKeys",{"title":830,"path":831,"stem":832},"StandardResponse","\u002Fdocs\u002Futils\u002Ftypes\u002Fstandardresponse","docs\u002Futils\u002Ftypes\u002FStandardResponse",{"title":834,"path":835,"stem":836},"ValueOf","\u002Fdocs\u002Futils\u002Ftypes\u002Fvalueof","docs\u002Futils\u002Ftypes\u002FValueOf",{"id":4,"extension":5,"links":838,"meta":849,"stem":62,"__hash__":63},[839,847,848],{"nested":8,"label":9,"icon":10,"to":11,"children":840},[841,842,843,844,845,846],{"label":14,"icon":15,"to":11,"description":16,"github":17,"badge":18},{"label":20,"icon":21,"to":22,"description":23,"github":24,"badge":25},{"label":27,"icon":28,"to":29,"description":30,"github":31,"badge":25},{"label":33,"icon":34,"to":35,"description":36,"github":37,"badge":38},{"label":40,"icon":41,"to":42,"description":43,"github":44,"badge":38},{"label":46,"icon":47,"to":48,"description":49,"github":50,"badge":51},{"nested":53,"label":54,"icon":55,"to":56},{"nested":53,"label":58,"icon":59,"to":60},{},{"id":851,"title":38,"body":852,"description":1707,"extension":1708,"icon":28,"meta":1709,"module":1710,"navigation":8,"path":214,"rawbody":1711,"seo":1712,"stem":215,"__hash__":1713},"docs\u002Fdocs\u002Fiam\u002F01.essentials\u002F21.api\u002F04.security.md",{"type":853,"value":854,"toc":1669},"minimark",[855,859,866,869,874,882,887,906,909,966,970,988,999,1003,1045,1048,1065,1069,1072,1076,1082,1112,1125,1132,1136,1150,1156,1160,1191,1200,1204,1207,1211,1229,1236,1240,1257,1260,1264,1278,1281,1285,1303,1306,1310,1332,1337,1341,1347,1354,1358,1361,1365,1383,1403,1407,1414,1417,1421,1429,1435,1439,1442,1446,1468,1475,1479,1499,1502,1506,1523,1534,1538,1541,1545,1563,1569,1573,1576,1626,1631,1635,1638,1663],[856,857,858],"p",{},"The API token subsystem protects machine-to-machine credentials with layered\ncontrols before, during, and after verification. It rejects malformed keys\nbefore the database lookup, stores only SHA-256 token hashes, binds each token\nto a single privilege, can restrict use to specific IP addresses, and requires\nfull session validation for management actions.",[856,860,861,862,865],{},"This page explains how the subsystem is designed, what each security control\nactually does in code, and how the controls work together to protect tokens.\nFor the broader service-level model outside the API token subsystem, see the\nmain ",[863,864,38],"a",{"href":217}," page.",[867,868],"hr",{},[870,871,873],"h2",{"id":872},"route-boundary","Route boundary",[856,875,876,877,881],{},"The API token subsystem exposes two HTTP surfaces: a public verification route\nand an authenticated management surface. Both inherit service-wide middleware\nfrom ",[878,879,880],"code",{},"bootstrapApp",", but they intentionally sit behind different route chains.",[883,884,886],"h3",{"id":885},"service-wide-guards","Service-wide guards",[856,888,889,890,893,894,897,898,901,902,905],{},"Before either API token router runs, the service disables ",[878,891,892],{},"x-powered-by",",\napplies ",[878,895,896],{},"helmet",", sets no-cache headers, validates ",[878,899,900],{},"req.ip",", and can enforce\nHMAC authentication for every request except local ",[878,903,904],{},"GET \u002Fhealth",". That means\neven the public verification route still benefits from anti-framing headers,\ncache suppression, IP validation, and optional service-to-service\nauthentication.",[856,907,908],{},"The service-wide layer protects the subsystem in the following ways:",[910,911,912,926,944,956],"ul",{},[913,914,915,917,918,921,922,925],"li",{},[878,916,896],{}," denies framing with ",[878,919,920],{},"X-Frame-Options: DENY"," and\n",[878,923,924],{},"frame-ancestors 'none'",".",[913,927,928,931,932,935,936,939,940,943],{},[878,929,930],{},"headers"," sets ",[878,933,934],{},"Cache-Control: no-cache, private, max-age=0",", ",[878,937,938],{},"Pragma:   no-cache",", and ",[878,941,942],{},"Expires: 0"," on responses.",[913,945,946,949,950,952,953,925],{},[878,947,948],{},"validateIp"," rejects requests whose resolved ",[878,951,900],{}," is missing or invalid\nwith ",[878,954,955],{},"403 Forbidden",[913,957,958,961,962,965],{},[878,959,960],{},"hmacAuth"," can turn the API token routes into internal-only routes when\n",[878,963,964],{},"service.Hmac"," is enabled.",[883,967,969],{"id":968},"public-verification-route","Public verification route",[856,971,972,975,976,979,980,983,984,987],{},[878,973,974],{},"GET \u002Fapi\u002Fpublic\u002Fverify"," is mounted through ",[878,977,978],{},"apiVerificationRoute()",". The\nservice mounts it before ",[878,981,982],{},"cookieParser()",", before ",[878,985,986],{},"botDetectorApi",", and before\nthe authentication and management route groups, so it keeps a smaller attack\nsurface and does not depend on session cookies or JSON parsing.",[856,989,990,991,994,995,998],{},"The controller only accepts the raw API key through the ",[878,992,993],{},"x-api-key"," header and\nthe required privilege through the ",[878,996,997],{},"privilege"," query parameter. It relies on\nthe token verifier and API-token limiter group instead of the full session\npipeline.",[883,1000,1002],{"id":1001},"authenticated-management-routes","Authenticated management routes",[856,1004,1005,1008,1009,1012,1013,935,1016,935,1019,1022,1023,935,1026,935,1029,1032,1033,1036,1037,1040,1041,1044],{},[878,1006,1007],{},"\u002Fapi\u002Fmanage\u002F:action"," is the private surface. The ",[878,1010,1011],{},"POST"," route requires\n",[878,1014,1015],{},"requireAccessToken",[878,1017,1018],{},"requireRefreshToken",[878,1020,1021],{},"getFingerPrint",",\n",[878,1024,1025],{},"checkForActiveMfa",[878,1027,1028],{},"protectRoute",[878,1030,1031],{},"contentType('application\u002Fjson')",", and\n",[878,1034,1035],{},"express.json({ limit: '1kb' })"," before ",[878,1038,1039],{},"apiTokensController"," runs. The ",[878,1042,1043],{},"GET","\nroute for listing uses the same chain except for the JSON-only and body-parser\nchecks.",[856,1046,1047],{},"That means a management action must pass bearer-token presence, refresh-session\npresence, canary-bound session checks, anomaly detection, MFA gating, JWT\nverification, and the access-token blacklist before any API token management\nbranch executes.",[1049,1050,1051],"warning",{},[856,1052,1053,1054,935,1057,1060,1061,1064],{},"The direct library helpers do not apply this route chain. If you call helpers\nlike ",[878,1055,1056],{},"updatePrivileges",[878,1058,1059],{},"updateRestriction",", or ",[878,1062,1063],{},"getAllValidTokensList","\ndirectly, you must provide the surrounding authentication and trust boundary\nyourself.",[870,1066,1068],{"id":1067},"token-material-and-storage","Token material and storage",[856,1070,1071],{},"The subsystem separates token secrecy from token management. The raw token is a\nsecret credential. The public identifier is only a management reference.",[883,1073,1075],{"id":1074},"raw-token-structure-and-checksum","Raw token structure and checksum",[856,1077,1078,1079,925],{},"Each created API token uses the format ",[878,1080,1081],{},"prefix_random_checksum",[910,1083,1084,1093,1103],{},[913,1085,1086,1089,1090,925],{},[878,1087,1088],{},"prefix"," comes from the caller or defaults to ",[878,1091,1092],{},"api",[913,1094,1095,1098,1099,1102],{},[878,1096,1097],{},"random"," is 64 bytes from ",[878,1100,1101],{},"crypto.randomBytes(64)"," encoded in hex.",[913,1104,1105,1108,1109,925],{},[878,1106,1107],{},"checksum"," is the first 8 hex characters of ",[878,1110,1111],{},"sha256(randomPart)",[856,1113,1114,1117,1118,1121,1122,1124],{},[878,1115,1116],{},"createApiKey"," rejects any prefix containing ",[878,1119,1120],{},"_",", because ",[878,1123,1120],{}," is the reserved\ndelimiter that separates the three token segments.",[856,1126,1127,1128,1131],{},"The subsystem also generates a separate ",[878,1129,1130],{},"public_identifier",". It uses a second\n64-byte random value and the same short checksum pattern. The browser or BFF\nuses this public identifier for dashboard actions so it never needs the raw API\nkey or the stored token hash.",[883,1133,1135],{"id":1134},"hashed-storage","Hashed storage",[856,1137,1138,1139,1141,1142,1145,1146,1149],{},"Before insertion, ",[878,1140,1116],{}," passes the raw token through ",[878,1143,1144],{},"toDigestHex","\nand stores only the SHA-256 digest in ",[878,1147,1148],{},"api_tokens.api_token",". The database\nnever stores the raw token, and the raw token is only returned once in the\ncreation or rotation success response.",[856,1151,1152,1153,1155],{},"The ",[878,1154,1130],{}," is intentionally not hashed. It is not treated as a\nsecret. Its job is to identify a token safely during authenticated management\nflows without revealing the actual credential.",[883,1157,1159],{"id":1158},"privilege-scoping-and-token-inventory","Privilege scoping and token inventory",[856,1161,1162,1163,935,1166,1022,1169,935,1172,1060,1175,1178,1179,1182,1183,1186,1187,1190],{},"Every token row is bound to one privilege type: ",[878,1164,1165],{},"demo",[878,1167,1168],{},"restricted",[878,1170,1171],{},"protected",[878,1173,1174],{},"full",[878,1176,1177],{},"custom",". ",[878,1180,1181],{},"verifyApiKey"," queries by both\n",[878,1184,1185],{},"api_token = ?"," and ",[878,1188,1189],{},"privilege_type = ?",", so the same key cannot be reused at\nanother privilege level.",[856,1192,1193,1195,1196,1199],{},[878,1194,1116],{}," also enforces ",[878,1197,1198],{},"apiTokens.limitTokensPerUser"," before generating a\nnew credential. If the user already has too many valid tokens, creation fails\nbefore any new key is minted.",[870,1201,1203],{"id":1202},"verification-and-lifecycle-protections","Verification and lifecycle protections",[856,1205,1206],{},"Verification uses a layered sequence. The earlier layers reject malformed input\ncheaply. The later layers enforce ownership, host restrictions, and lifecycle\nstate against the database.",[883,1208,1210],{"id":1209},"fast-reject-before-the-database-lookup","Fast reject before the database lookup",[856,1212,1213,1214,1216,1217,1022,1219,939,1222,1224,1225,1228],{},"When the caller provides a raw key, ",[878,1215,1181],{}," splits it into ",[878,1218,1088],{},[878,1220,1221],{},"randomPart",[878,1223,1107],{},". If any piece is missing, verification returns\n",[878,1226,1227],{},"Invalid key"," immediately.",[856,1230,1231,1232,1235],{},"Then it recomputes the checksum from the random part and compares it with\n",[878,1233,1234],{},"crypto.timingSafeEqual",". Counterfeit, truncated, or corrupted raw keys are\nrejected before the database query runs.",[883,1237,1239],{"id":1238},"hash-normalization-and-strict-lookup","Hash normalization and strict lookup",[856,1241,1242,1243,1245,1246,1249,1250,1253,1254,925],{},"After structure validation, ",[878,1244,1181],{}," hashes the input if needed and\nvalidates that the result is a well-formed SHA-256 hex digest with\n",[878,1247,1248],{},"ensureSha256Hex",". The database lookup only accepts rows where ",[878,1251,1252],{},"valid = 1"," and\nthe stored privilege exactly matches ",[878,1255,1256],{},"providedPrivilege",[856,1258,1259],{},"This gives the subsystem two security layers. The first rejects malformed\ncredentials without spending a query. The second ensures a leaked digest cannot\nbe reused with a different privilege or after revocation.",[883,1261,1263],{"id":1262},"ip-restriction-enforcement","IP restriction enforcement",[856,1265,1266,1267,1270,1271,1274,1275,925],{},"If the token row contains ",[878,1268,1269],{},"restricted_to_ip_address"," and the caller does not\nset ",[878,1272,1273],{},"byPassIpCheck",", verification parses the stored JSON list and requires the\nprovided IP address to exist in that whitelist. If the request does not supply\nan IP or the IP is not in the list, verification rolls back and returns\n",[878,1276,1277],{},"Invalid Host",[856,1279,1280],{},"That means IP restrictions are enforced at verification time, not only stored\nas metadata. A token can exist and still be unusable from the wrong network.",[883,1282,1284],{"id":1283},"expiration-invalidation","Expiration invalidation",[856,1286,1287,1288,1291,1292,1294,1295,1298,1299,1302],{},"If ",[878,1289,1290],{},"expires_at"," exists and its time is in the past, ",[878,1293,1181],{},"\nimmediately updates the row to ",[878,1296,1297],{},"valid = 0",", commits that invalidation, and\nreturns ",[878,1300,1301],{},"Token expired",". The token does not stay temporarily valid after the\nfirst expired use.",[856,1304,1305],{},"This turns expiration into both a time check and a state change. After the\nfirst expired attempt, later verification no longer sees the token as active.",[883,1307,1309],{"id":1308},"usage-tracking","Usage tracking",[856,1311,1312,1313,1315,1316,1319,1320,1323,1324,1327,1328,1331],{},"On a successful external verification, ",[878,1314,1181],{}," increments ",[878,1317,1318],{},"usage_count","\nand updates ",[878,1321,1322],{},"last_used = UTC_TIMESTAMP()"," inside the same transaction. The\nquery uses ",[878,1325,1326],{},"FOR UPDATE"," unless ",[878,1329,1330],{},"skipCountUpdates"," is enabled, which prevents\nconcurrent requests from racing the usage counter.",[856,1333,1334,1335,925],{},"Internal flows can disable this behavior when they need to inspect or revoke a\ntoken without treating that action as token consumption. That is why metadata\nreads and internal revoke checks do not inflate ",[878,1336,1318],{},[883,1338,1340],{"id":1339},"revocation-and-rotation","Revocation and rotation",[856,1342,1343,1344,1346],{},"Revocation sets ",[878,1345,1297],{}," instead of deleting the row. That preserves\nmetadata and makes revoked keys fail the main verification lookup\nautomatically.",[856,1348,1349,1350,1353],{},"Rotation first revokes the current token and only then creates the replacement\nkey. When management routes call rotation through ",[878,1351,1352],{},"privateActionManager",", the\nreplacement inherits the current privilege, name, prefix, remaining TTL, and IP\nrestrictions, which prevents a rotation flow from silently widening access.",[870,1355,1357],{"id":1356},"management","Management",[856,1359,1360],{},"Management actions are designed so the client can act on a token without ever\nhandling the raw secret. The manager resolves the real stored token hash on the\nserver after it validates a public identity record.",[883,1362,1364],{"id":1363},"public-identifier-proxy","Public identifier proxy",[856,1366,1367,1369,1370,935,1373,935,1376,1032,1379,1382],{},[878,1368,1352],{}," takes ",[878,1371,1372],{},"userId",[878,1374,1375],{},"tokenId",[878,1377,1378],{},"name",[878,1380,1381],{},"publicIdentifier",", validates the public identifier checksum, and then queries\nthe database for an exact row match.",[856,1384,1385,1386,935,1389,935,1392,935,1394,1032,1396,1398,1399,1402],{},"That query requires ",[878,1387,1388],{},"id",[878,1390,1391],{},"user_id",[878,1393,1378],{},[878,1395,1130],{},[878,1397,1252],{}," to line up. If any of those values do not match, the manager\nreturns ",[878,1400,1401],{},"Bad Request"," and does not disclose whether a different token exists.",[883,1404,1406],{"id":1405},"scoped-action-dispatch","Scoped action dispatch",[856,1408,1409,1410,1413],{},"Once the manager resolves the row, it uses the stored hashed ",[878,1411,1412],{},"api_token"," and\nthe stored privilege to dispatch the requested action. That means revoke,\nrotate, metadata, IP updates, and privilege updates all execute against the\ndatabase record the user already owns, not against a caller-supplied raw key.",[856,1415,1416],{},"This is the core safety model for dashboard actions. The browser handles a\nnon-secret public identifier, while the server resolves the real token hash\ninternally.",[883,1418,1420],{"id":1419},"listing-without-secret-exposure","Listing without secret exposure",[856,1422,1423,1425,1426,1428],{},[878,1424,1063],{}," deliberately returns metadata plus\n",[878,1427,1130],{},", but never the raw API key or the stored hash.\nAuthenticated clients can render a management UI from that list and still avoid\nhandling the secret itself.",[856,1430,1431,1432,1434],{},"Token listing does not use ",[878,1433,1352],{},", but the route still requires\nthe full authenticated management middleware chain before it can return the\nlist.",[870,1436,1438],{"id":1437},"input-validation-and-sanitization","Input validation and sanitization",[856,1440,1441],{},"The subsystem validates both route parameters and request bodies before helper\nlogic runs. It also treats hostile HTML as an attack signal instead of a normal\nbad-request case.",[883,1443,1445],{"id":1444},"zod-schemas-and-safe-strings","Zod schemas and safe strings",[856,1447,1448,1449,1022,1452,935,1455,935,1458,935,1461,1032,1464,1467],{},"The public API surface is constrained by ",[878,1450,1451],{},"newApiTokenSchema",[878,1453,1454],{},"standardSchema",[878,1456,1457],{},"ipRestrictionUpdate",[878,1459,1460],{},"privilegeUpdate",[878,1462,1463],{},"privilegeQ",[878,1465,1466],{},"reqParams",". These schemas validate action names, token names, prefixes,\nprivilege values, token ids, and public identifiers before the controller calls\nthe token helpers.",[856,1469,1470,1471,1474],{},"Every string field built with ",[878,1472,1473],{},"makeSafeString"," passes through the HTML\nsanitizer before it is accepted. That includes token names, prefixes, and\npublic identifiers.",[883,1476,1478],{"id":1477},"xss-detection-and-bans","XSS detection and bans",[856,1480,1481,1482,1484,1485,1488,1489,1492,1493,1496,1497,925],{},"If sanitized input still contains HTML or event-handler style payloads,\n",[878,1483,1473],{}," raises a custom Zod issue and ",[878,1486,1487],{},"validateSchema"," converts it\ninto an XSS handling path. That path calls ",[878,1490,1491],{},"handleXSS",", logs the incident, and\nreturns ",[878,1494,1495],{},"{ banned: true }"," with ",[878,1498,955],{},[856,1500,1501],{},"This is why the API token routes do not treat hostile HTML like a normal\nvalidation miss. They treat it as an attack signal.",[883,1503,1505],{"id":1504},"json-and-body-size-enforcement","JSON and body-size enforcement",[856,1507,1508,1509,1512,1513,1516,1517,1036,1520,1522],{},"Management POST routes require ",[878,1510,1511],{},"Content-Type: application\u002Fjson",". If the content\ntype is wrong, ",[878,1514,1515],{},"contentType()"," returns ",[878,1518,1519],{},"403",[878,1521,1039],{},"\nruns.",[856,1524,1525,1526,1529,1530,1533],{},"The POST parser also caps bodies at ",[878,1527,1528],{},"1kb"," and rejects empty JSON bodies during\nthe parser ",[878,1531,1532],{},"verify"," hook. The GET listing route bypasses the body parser\nentirely because it does not need a body.",[870,1535,1537],{"id":1536},"rate-limiting-and-failure-shaping","Rate limiting and failure shaping",[856,1539,1540],{},"The subsystem uses different rate-limit strategies for the public verification\npath and the authenticated management routes. It also intentionally flattens\nsome security failures into shared public reasons to reduce token-state\ndisclosure.",[883,1542,1544],{"id":1543},"abuse-controls","Abuse controls",[856,1546,1547,1548,1551,1552,1555,1556,1559,1560,1562],{},"The public verify route uses ",[878,1549,1550],{},"consumptionRateLimiter"," for failed attempts and\ncan optionally use ",[878,1553,1554],{},"generalUnionLimiter"," when\n",[878,1557,1558],{},"apiTokens.rateLimitOnSuccessfulRequest"," is enabled. The management routes put\n",[878,1561,1554],{}," in front of every action and add a dedicated limiter for\ncreation, revocation, metadata, rotation, IP updates, or privilege updates when\nneeded.",[856,1564,1565,1566,1568],{},"See ",[863,1567,147],{"href":211}," for the full\nlimiter map, default behavior, reset rules, and permanent-block escalation.",[883,1570,1572],{"id":1571},"security-oriented-error-design","Security-oriented error design",[856,1574,1575],{},"The subsystem intentionally collapses several failure states into the same\npublic reason so callers cannot easily enumerate token state.",[910,1577,1578,1585,1590,1595,1606,1611,1617],{},[913,1579,1580,1582,1583,925],{},[878,1581,1227],{}," covers malformed raw keys, checksum failures, missing rows,\nrevoked rows, and privilege mismatches in ",[878,1584,1181],{},[913,1586,1587,1589],{},[878,1588,1277],{}," only appears when a real token exists but the caller IP does\nnot satisfy the stored whitelist.",[913,1591,1592,1594],{},[878,1593,1301],{}," appears once the subsystem has marked the expired row\ninvalid.",[913,1596,1597,1600,1601,1603,1604,925],{},[878,1598,1599],{},"Invalid identity"," is reserved for bad ",[878,1602,1381],{}," checksum\nvalidation in ",[878,1605,1352],{},[913,1607,1608,1610],{},[878,1609,1401],{}," in manager-backed actions means the requested token metadata\ndid not match a currently valid row or the action itself was unsupported.",[913,1612,1613,1616],{},[878,1614,1615],{},"429 Too Many Requests"," always comes from the limiter layer, not from token\nbusiness logic.",[913,1618,1619,1496,1622,1625],{},[878,1620,1621],{},"202",[878,1623,1624],{},"mfa: true"," can appear before management actions when the session\nalready has an active MFA challenge or anomaly detection requires a new one.",[856,1627,1628,1629,925],{},"The direct revoke helper goes one step further and returns success even when\nthe token is already invalid or missing. That behavior reduces token-state\ndisclosure for direct server-side callers, while the route stays stricter\nbecause it only resolves currently valid rows through ",[878,1630,1352],{},[870,1632,1634],{"id":1633},"known-limitations","Known limitations",[856,1636,1637],{},"The subsystem is deliberately opinionated, but some protections are only as\nstrong as the way you call the API.",[910,1639,1640,1643,1649,1652,1657],{},[913,1641,1642],{},"Direct library helpers are trusted server-side primitives. They do not apply\nthe route-level authentication, anomaly checks, MFA gating, or JSON guards.",[913,1644,1645,1648],{},[878,1646,1647],{},"getUserApiKeysMetaData"," intentionally bypasses IP enforcement and usage\ncounter updates so metadata reads do not consume the token.",[913,1650,1651],{},"Internal revoke checks also bypass IP enforcement and usage counter updates.\nThat is useful for administration, but it means the helper is not a full\nsubstitute for the route chain.",[913,1653,1654,1656],{},[878,1655,1130],{}," is not a secret. It is safe to expose to an authenticated\ndashboard, but it is still a management capability and must stay behind your\ntrusted management surface.",[913,1658,1659,1660,1662],{},"The verification route is public by design unless you enable ",[878,1661,964],{},".\nIf you want token verification to stay internal to your BFF, turn HMAC on.",[1664,1665,1666],"note",{},[856,1667,1668],{},"Use the authenticated routes when you want the full subsystem security model.\nUse the direct helpers only in trusted server-side code where you intentionally\ncontrol the surrounding authentication and transport boundary.",{"title":1670,"searchDepth":1671,"depth":1671,"links":1672},"",2,[1673,1679,1684,1692,1697,1702,1706],{"id":872,"depth":1671,"text":873,"children":1674},[1675,1677,1678],{"id":885,"depth":1676,"text":886},3,{"id":968,"depth":1676,"text":969},{"id":1001,"depth":1676,"text":1002},{"id":1067,"depth":1671,"text":1068,"children":1680},[1681,1682,1683],{"id":1074,"depth":1676,"text":1075},{"id":1134,"depth":1676,"text":1135},{"id":1158,"depth":1676,"text":1159},{"id":1202,"depth":1671,"text":1203,"children":1685},[1686,1687,1688,1689,1690,1691],{"id":1209,"depth":1676,"text":1210},{"id":1238,"depth":1676,"text":1239},{"id":1262,"depth":1676,"text":1263},{"id":1283,"depth":1676,"text":1284},{"id":1308,"depth":1676,"text":1309},{"id":1339,"depth":1676,"text":1340},{"id":1356,"depth":1671,"text":1357,"children":1693},[1694,1695,1696],{"id":1363,"depth":1676,"text":1364},{"id":1405,"depth":1676,"text":1406},{"id":1419,"depth":1676,"text":1420},{"id":1437,"depth":1671,"text":1438,"children":1698},[1699,1700,1701],{"id":1444,"depth":1676,"text":1445},{"id":1477,"depth":1676,"text":1478},{"id":1504,"depth":1676,"text":1505},{"id":1536,"depth":1671,"text":1537,"children":1703},[1704,1705],{"id":1543,"depth":1676,"text":1544},{"id":1571,"depth":1676,"text":1572},{"id":1633,"depth":1671,"text":1634},"Defense-in-depth security design of the API token subsystem, covering route boundaries, hashed storage, privilege binding, IP checks, management identity validation, sanitization, and abuse controls.","md",{},null,"---\ntitle: Security\ndescription: Defense-in-depth security design of the API token subsystem, covering route boundaries, hashed storage, privilege binding, IP checks, management identity validation, sanitization, and abuse controls.\nicon: i-lucide-shield-check\n---\n\nThe API token subsystem protects machine-to-machine credentials with layered\ncontrols before, during, and after verification. It rejects malformed keys\nbefore the database lookup, stores only SHA-256 token hashes, binds each token\nto a single privilege, can restrict use to specific IP addresses, and requires\nfull session validation for management actions.\n\nThis page explains how the subsystem is designed, what each security control\nactually does in code, and how the controls work together to protect tokens.\nFor the broader service-level model outside the API token subsystem, see the\nmain [Security](\u002Fdocs\u002Fiam\u002Fsecurity) page.\n\n---\n\n## Route boundary\n\nThe API token subsystem exposes two HTTP surfaces: a public verification route\nand an authenticated management surface. Both inherit service-wide middleware\nfrom `bootstrapApp`, but they intentionally sit behind different route chains.\n\n### Service-wide guards\n\nBefore either API token router runs, the service disables `x-powered-by`,\napplies `helmet`, sets no-cache headers, validates `req.ip`, and can enforce\nHMAC authentication for every request except local `GET \u002Fhealth`. That means\neven the public verification route still benefits from anti-framing headers,\ncache suppression, IP validation, and optional service-to-service\nauthentication.\n\nThe service-wide layer protects the subsystem in the following ways:\n\n- `helmet` denies framing with `X-Frame-Options: DENY` and\n\t`frame-ancestors 'none'`.\n- `headers` sets `Cache-Control: no-cache, private, max-age=0`, `Pragma:\n\tno-cache`, and `Expires: 0` on responses.\n- `validateIp` rejects requests whose resolved `req.ip` is missing or invalid\n\twith `403 Forbidden`.\n- `hmacAuth` can turn the API token routes into internal-only routes when\n\t`service.Hmac` is enabled.\n\n### Public verification route\n\n`GET \u002Fapi\u002Fpublic\u002Fverify` is mounted through `apiVerificationRoute()`. The\nservice mounts it before `cookieParser()`, before `botDetectorApi`, and before\nthe authentication and management route groups, so it keeps a smaller attack\nsurface and does not depend on session cookies or JSON parsing.\n\nThe controller only accepts the raw API key through the `x-api-key` header and\nthe required privilege through the `privilege` query parameter. It relies on\nthe token verifier and API-token limiter group instead of the full session\npipeline.\n\n### Authenticated management routes\n\n`\u002Fapi\u002Fmanage\u002F:action` is the private surface. The `POST` route requires\n`requireAccessToken`, `requireRefreshToken`, `getFingerPrint`,\n`checkForActiveMfa`, `protectRoute`, `contentType('application\u002Fjson')`, and\n`express.json({ limit: '1kb' })` before `apiTokensController` runs. The `GET`\nroute for listing uses the same chain except for the JSON-only and body-parser\nchecks.\n\nThat means a management action must pass bearer-token presence, refresh-session\npresence, canary-bound session checks, anomaly detection, MFA gating, JWT\nverification, and the access-token blacklist before any API token management\nbranch executes.\n\n::warning\nThe direct library helpers do not apply this route chain. If you call helpers\nlike `updatePrivileges`, `updateRestriction`, or `getAllValidTokensList`\ndirectly, you must provide the surrounding authentication and trust boundary\nyourself.\n::\n\n## Token material and storage\n\nThe subsystem separates token secrecy from token management. The raw token is a\nsecret credential. The public identifier is only a management reference.\n\n### Raw token structure and checksum\n\nEach created API token uses the format `prefix_random_checksum`.\n\n- `prefix` comes from the caller or defaults to `api`.\n- `random` is 64 bytes from `crypto.randomBytes(64)` encoded in hex.\n- `checksum` is the first 8 hex characters of `sha256(randomPart)`.\n\n`createApiKey` rejects any prefix containing `_`, because `_` is the reserved\ndelimiter that separates the three token segments.\n\nThe subsystem also generates a separate `public_identifier`. It uses a second\n64-byte random value and the same short checksum pattern. The browser or BFF\nuses this public identifier for dashboard actions so it never needs the raw API\nkey or the stored token hash.\n\n### Hashed storage\n\nBefore insertion, `createApiKey` passes the raw token through `toDigestHex`\nand stores only the SHA-256 digest in `api_tokens.api_token`. The database\nnever stores the raw token, and the raw token is only returned once in the\ncreation or rotation success response.\n\nThe `public_identifier` is intentionally not hashed. It is not treated as a\nsecret. Its job is to identify a token safely during authenticated management\nflows without revealing the actual credential.\n\n### Privilege scoping and token inventory\n\nEvery token row is bound to one privilege type: `demo`, `restricted`,\n`protected`, `full`, or `custom`. `verifyApiKey` queries by both\n`api_token = ?` and `privilege_type = ?`, so the same key cannot be reused at\nanother privilege level.\n\n`createApiKey` also enforces `apiTokens.limitTokensPerUser` before generating a\nnew credential. If the user already has too many valid tokens, creation fails\nbefore any new key is minted.\n\n## Verification and lifecycle protections\n\nVerification uses a layered sequence. The earlier layers reject malformed input\ncheaply. The later layers enforce ownership, host restrictions, and lifecycle\nstate against the database.\n\n### Fast reject before the database lookup\n\nWhen the caller provides a raw key, `verifyApiKey` splits it into `prefix`,\n`randomPart`, and `checksum`. If any piece is missing, verification returns\n`Invalid key` immediately.\n\nThen it recomputes the checksum from the random part and compares it with\n`crypto.timingSafeEqual`. Counterfeit, truncated, or corrupted raw keys are\nrejected before the database query runs.\n\n### Hash normalization and strict lookup\n\nAfter structure validation, `verifyApiKey` hashes the input if needed and\nvalidates that the result is a well-formed SHA-256 hex digest with\n`ensureSha256Hex`. The database lookup only accepts rows where `valid = 1` and\nthe stored privilege exactly matches `providedPrivilege`.\n\nThis gives the subsystem two security layers. The first rejects malformed\ncredentials without spending a query. The second ensures a leaked digest cannot\nbe reused with a different privilege or after revocation.\n\n### IP restriction enforcement\n\nIf the token row contains `restricted_to_ip_address` and the caller does not\nset `byPassIpCheck`, verification parses the stored JSON list and requires the\nprovided IP address to exist in that whitelist. If the request does not supply\nan IP or the IP is not in the list, verification rolls back and returns\n`Invalid Host`.\n\nThat means IP restrictions are enforced at verification time, not only stored\nas metadata. A token can exist and still be unusable from the wrong network.\n\n### Expiration invalidation\n\nIf `expires_at` exists and its time is in the past, `verifyApiKey`\nimmediately updates the row to `valid = 0`, commits that invalidation, and\nreturns `Token expired`. The token does not stay temporarily valid after the\nfirst expired use.\n\nThis turns expiration into both a time check and a state change. After the\nfirst expired attempt, later verification no longer sees the token as active.\n\n### Usage tracking\n\nOn a successful external verification, `verifyApiKey` increments `usage_count`\nand updates `last_used = UTC_TIMESTAMP()` inside the same transaction. The\nquery uses `FOR UPDATE` unless `skipCountUpdates` is enabled, which prevents\nconcurrent requests from racing the usage counter.\n\nInternal flows can disable this behavior when they need to inspect or revoke a\ntoken without treating that action as token consumption. That is why metadata\nreads and internal revoke checks do not inflate `usage_count`.\n\n### Revocation and rotation\n\nRevocation sets `valid = 0` instead of deleting the row. That preserves\nmetadata and makes revoked keys fail the main verification lookup\nautomatically.\n\nRotation first revokes the current token and only then creates the replacement\nkey. When management routes call rotation through `privateActionManager`, the\nreplacement inherits the current privilege, name, prefix, remaining TTL, and IP\nrestrictions, which prevents a rotation flow from silently widening access.\n\n## Management\n\nManagement actions are designed so the client can act on a token without ever\nhandling the raw secret. The manager resolves the real stored token hash on the\nserver after it validates a public identity record.\n\n### Public identifier proxy\n\n`privateActionManager` takes `userId`, `tokenId`, `name`, and\n`publicIdentifier`, validates the public identifier checksum, and then queries\nthe database for an exact row match.\n\nThat query requires `id`, `user_id`, `name`, `public_identifier`, and\n`valid = 1` to line up. If any of those values do not match, the manager\nreturns `Bad Request` and does not disclose whether a different token exists.\n\n### Scoped action dispatch\n\nOnce the manager resolves the row, it uses the stored hashed `api_token` and\nthe stored privilege to dispatch the requested action. That means revoke,\nrotate, metadata, IP updates, and privilege updates all execute against the\ndatabase record the user already owns, not against a caller-supplied raw key.\n\nThis is the core safety model for dashboard actions. The browser handles a\nnon-secret public identifier, while the server resolves the real token hash\ninternally.\n\n### Listing without secret exposure\n\n`getAllValidTokensList` deliberately returns metadata plus\n`public_identifier`, but never the raw API key or the stored hash.\nAuthenticated clients can render a management UI from that list and still avoid\nhandling the secret itself.\n\nToken listing does not use `privateActionManager`, but the route still requires\nthe full authenticated management middleware chain before it can return the\nlist.\n\n## Input validation and sanitization\n\nThe subsystem validates both route parameters and request bodies before helper\nlogic runs. It also treats hostile HTML as an attack signal instead of a normal\nbad-request case.\n\n### Zod schemas and safe strings\n\nThe public API surface is constrained by `newApiTokenSchema`,\n`standardSchema`, `ipRestrictionUpdate`, `privilegeUpdate`, `privilegeQ`, and\n`reqParams`. These schemas validate action names, token names, prefixes,\nprivilege values, token ids, and public identifiers before the controller calls\nthe token helpers.\n\nEvery string field built with `makeSafeString` passes through the HTML\nsanitizer before it is accepted. That includes token names, prefixes, and\npublic identifiers.\n\n### XSS detection and bans\n\nIf sanitized input still contains HTML or event-handler style payloads,\n`makeSafeString` raises a custom Zod issue and `validateSchema` converts it\ninto an XSS handling path. That path calls `handleXSS`, logs the incident, and\nreturns `{ banned: true }` with `403 Forbidden`.\n\nThis is why the API token routes do not treat hostile HTML like a normal\nvalidation miss. They treat it as an attack signal.\n\n### JSON and body-size enforcement\n\nManagement POST routes require `Content-Type: application\u002Fjson`. If the content\ntype is wrong, `contentType()` returns `403` before `apiTokensController`\nruns.\n\nThe POST parser also caps bodies at `1kb` and rejects empty JSON bodies during\nthe parser `verify` hook. The GET listing route bypasses the body parser\nentirely because it does not need a body.\n\n## Rate limiting and failure shaping\n\nThe subsystem uses different rate-limit strategies for the public verification\npath and the authenticated management routes. It also intentionally flattens\nsome security failures into shared public reasons to reduce token-state\ndisclosure.\n\n### Abuse controls\n\nThe public verify route uses `consumptionRateLimiter` for failed attempts and\ncan optionally use `generalUnionLimiter` when\n`apiTokens.rateLimitOnSuccessfulRequest` is enabled. The management routes put\n`generalUnionLimiter` in front of every action and add a dedicated limiter for\ncreation, revocation, metadata, rotation, IP updates, or privilege updates when\nneeded.\n\nSee [Rate Limiting](\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Frate-limiting) for the full\nlimiter map, default behavior, reset rules, and permanent-block escalation.\n\n### Security-oriented error design\n\nThe subsystem intentionally collapses several failure states into the same\npublic reason so callers cannot easily enumerate token state.\n\n- `Invalid key` covers malformed raw keys, checksum failures, missing rows,\n\trevoked rows, and privilege mismatches in `verifyApiKey`.\n- `Invalid Host` only appears when a real token exists but the caller IP does\n\tnot satisfy the stored whitelist.\n- `Token expired` appears once the subsystem has marked the expired row\n\tinvalid.\n- `Invalid identity` is reserved for bad `publicIdentifier` checksum\n\tvalidation in `privateActionManager`.\n- `Bad Request` in manager-backed actions means the requested token metadata\n\tdid not match a currently valid row or the action itself was unsupported.\n- `429 Too Many Requests` always comes from the limiter layer, not from token\n\tbusiness logic.\n- `202` with `mfa: true` can appear before management actions when the session\n\talready has an active MFA challenge or anomaly detection requires a new one.\n\nThe direct revoke helper goes one step further and returns success even when\nthe token is already invalid or missing. That behavior reduces token-state\ndisclosure for direct server-side callers, while the route stays stricter\nbecause it only resolves currently valid rows through `privateActionManager`.\n\n## Known limitations\n\nThe subsystem is deliberately opinionated, but some protections are only as\nstrong as the way you call the API.\n\n- Direct library helpers are trusted server-side primitives. They do not apply\n\tthe route-level authentication, anomaly checks, MFA gating, or JSON guards.\n- `getUserApiKeysMetaData` intentionally bypasses IP enforcement and usage\n\tcounter updates so metadata reads do not consume the token.\n- Internal revoke checks also bypass IP enforcement and usage counter updates.\n\tThat is useful for administration, but it means the helper is not a full\n\tsubstitute for the route chain.\n- `public_identifier` is not a secret. It is safe to expose to an authenticated\n\tdashboard, but it is still a management capability and must stay behind your\n\ttrusted management surface.\n- The verification route is public by design unless you enable `service.Hmac`.\n\tIf you want token verification to stay internal to your BFF, turn HMAC on.\n\n::note\nUse the authenticated routes when you want the full subsystem security model.\nUse the direct helpers only in trusted server-side code where you intentionally\ncontrol the surrounding authentication and transport boundary.\n::\n",{"title":38,"description":1707},"LNVvUbBMHAX5sgM0VUI1HEpI8guZsB0cL4anESuBcLQ",[1715,1716],{"title":147,"path":211,"stem":212,"children":-1},{"title":38,"path":217,"stem":218,"children":-1},{"id":851,"title":38,"body":1718,"description":1707,"extension":1708,"icon":28,"meta":2247,"module":1710,"navigation":8,"path":214,"rawbody":1711,"seo":2248,"stem":215,"__hash__":1713},{"type":853,"value":1719,"toc":2212},[1720,1722,1726,1728,1730,1734,1736,1746,1748,1782,1784,1794,1800,1802,1826,1828,1838,1840,1842,1844,1848,1868,1876,1880,1882,1890,1894,1896,1914,1920,1922,1924,1926,1938,1942,1944,1954,1956,1958,1966,1968,1970,1980,1982,1984,1996,2000,2002,2006,2010,2012,2014,2016,2028,2042,2044,2048,2050,2052,2058,2062,2064,2066,2068,2082,2086,2088,2100,2102,2104,2114,2120,2122,2124,2126,2136,2140,2142,2144,2182,2186,2188,2190,2208],[856,1721,858],{},[856,1723,861,1724,865],{},[863,1725,38],{"href":217},[867,1727],{},[870,1729,873],{"id":872},[856,1731,876,1732,881],{},[878,1733,880],{},[883,1735,886],{"id":885},[856,1737,889,1738,893,1740,897,1742,901,1744,905],{},[878,1739,892],{},[878,1741,896],{},[878,1743,900],{},[878,1745,904],{},[856,1747,908],{},[910,1749,1750,1758,1768,1776],{},[913,1751,1752,917,1754,921,1756,925],{},[878,1753,896],{},[878,1755,920],{},[878,1757,924],{},[913,1759,1760,931,1762,935,1764,939,1766,943],{},[878,1761,930],{},[878,1763,934],{},[878,1765,938],{},[878,1767,942],{},[913,1769,1770,949,1772,952,1774,925],{},[878,1771,948],{},[878,1773,900],{},[878,1775,955],{},[913,1777,1778,961,1780,965],{},[878,1779,960],{},[878,1781,964],{},[883,1783,969],{"id":968},[856,1785,1786,975,1788,979,1790,983,1792,987],{},[878,1787,974],{},[878,1789,978],{},[878,1791,982],{},[878,1793,986],{},[856,1795,990,1796,994,1798,998],{},[878,1797,993],{},[878,1799,997],{},[883,1801,1002],{"id":1001},[856,1803,1804,1008,1806,1012,1808,935,1810,935,1812,1022,1814,935,1816,935,1818,1032,1820,1036,1822,1040,1824,1044],{},[878,1805,1007],{},[878,1807,1011],{},[878,1809,1015],{},[878,1811,1018],{},[878,1813,1021],{},[878,1815,1025],{},[878,1817,1028],{},[878,1819,1031],{},[878,1821,1035],{},[878,1823,1039],{},[878,1825,1043],{},[856,1827,1047],{},[1049,1829,1830],{},[856,1831,1053,1832,935,1834,1060,1836,1064],{},[878,1833,1056],{},[878,1835,1059],{},[878,1837,1063],{},[870,1839,1068],{"id":1067},[856,1841,1071],{},[883,1843,1075],{"id":1074},[856,1845,1078,1846,925],{},[878,1847,1081],{},[910,1849,1850,1856,1862],{},[913,1851,1852,1089,1854,925],{},[878,1853,1088],{},[878,1855,1092],{},[913,1857,1858,1098,1860,1102],{},[878,1859,1097],{},[878,1861,1101],{},[913,1863,1864,1108,1866,925],{},[878,1865,1107],{},[878,1867,1111],{},[856,1869,1870,1117,1872,1121,1874,1124],{},[878,1871,1116],{},[878,1873,1120],{},[878,1875,1120],{},[856,1877,1127,1878,1131],{},[878,1879,1130],{},[883,1881,1135],{"id":1134},[856,1883,1138,1884,1141,1886,1145,1888,1149],{},[878,1885,1116],{},[878,1887,1144],{},[878,1889,1148],{},[856,1891,1152,1892,1155],{},[878,1893,1130],{},[883,1895,1159],{"id":1158},[856,1897,1162,1898,935,1900,1022,1902,935,1904,1060,1906,1178,1908,1182,1910,1186,1912,1190],{},[878,1899,1165],{},[878,1901,1168],{},[878,1903,1171],{},[878,1905,1174],{},[878,1907,1177],{},[878,1909,1181],{},[878,1911,1185],{},[878,1913,1189],{},[856,1915,1916,1195,1918,1199],{},[878,1917,1116],{},[878,1919,1198],{},[870,1921,1203],{"id":1202},[856,1923,1206],{},[883,1925,1210],{"id":1209},[856,1927,1213,1928,1216,1930,1022,1932,939,1934,1224,1936,1228],{},[878,1929,1181],{},[878,1931,1088],{},[878,1933,1221],{},[878,1935,1107],{},[878,1937,1227],{},[856,1939,1231,1940,1235],{},[878,1941,1234],{},[883,1943,1239],{"id":1238},[856,1945,1242,1946,1245,1948,1249,1950,1253,1952,925],{},[878,1947,1181],{},[878,1949,1248],{},[878,1951,1252],{},[878,1953,1256],{},[856,1955,1259],{},[883,1957,1263],{"id":1262},[856,1959,1266,1960,1270,1962,1274,1964,925],{},[878,1961,1269],{},[878,1963,1273],{},[878,1965,1277],{},[856,1967,1280],{},[883,1969,1284],{"id":1283},[856,1971,1287,1972,1291,1974,1294,1976,1298,1978,1302],{},[878,1973,1290],{},[878,1975,1181],{},[878,1977,1297],{},[878,1979,1301],{},[856,1981,1305],{},[883,1983,1309],{"id":1308},[856,1985,1312,1986,1315,1988,1319,1990,1323,1992,1327,1994,1331],{},[878,1987,1181],{},[878,1989,1318],{},[878,1991,1322],{},[878,1993,1326],{},[878,1995,1330],{},[856,1997,1334,1998,925],{},[878,1999,1318],{},[883,2001,1340],{"id":1339},[856,2003,1343,2004,1346],{},[878,2005,1297],{},[856,2007,1349,2008,1353],{},[878,2009,1352],{},[870,2011,1357],{"id":1356},[856,2013,1360],{},[883,2015,1364],{"id":1363},[856,2017,2018,1369,2020,935,2022,935,2024,1032,2026,1382],{},[878,2019,1352],{},[878,2021,1372],{},[878,2023,1375],{},[878,2025,1378],{},[878,2027,1381],{},[856,2029,1385,2030,935,2032,935,2034,935,2036,1032,2038,1398,2040,1402],{},[878,2031,1388],{},[878,2033,1391],{},[878,2035,1378],{},[878,2037,1130],{},[878,2039,1252],{},[878,2041,1401],{},[883,2043,1406],{"id":1405},[856,2045,1409,2046,1413],{},[878,2047,1412],{},[856,2049,1416],{},[883,2051,1420],{"id":1419},[856,2053,2054,1425,2056,1428],{},[878,2055,1063],{},[878,2057,1130],{},[856,2059,1431,2060,1434],{},[878,2061,1352],{},[870,2063,1438],{"id":1437},[856,2065,1441],{},[883,2067,1445],{"id":1444},[856,2069,1448,2070,1022,2072,935,2074,935,2076,935,2078,1032,2080,1467],{},[878,2071,1451],{},[878,2073,1454],{},[878,2075,1457],{},[878,2077,1460],{},[878,2079,1463],{},[878,2081,1466],{},[856,2083,1470,2084,1474],{},[878,2085,1473],{},[883,2087,1478],{"id":1477},[856,2089,1481,2090,1484,2092,1488,2094,1492,2096,1496,2098,925],{},[878,2091,1473],{},[878,2093,1487],{},[878,2095,1491],{},[878,2097,1495],{},[878,2099,955],{},[856,2101,1501],{},[883,2103,1505],{"id":1504},[856,2105,1508,2106,1512,2108,1516,2110,1036,2112,1522],{},[878,2107,1511],{},[878,2109,1515],{},[878,2111,1519],{},[878,2113,1039],{},[856,2115,1525,2116,1529,2118,1533],{},[878,2117,1528],{},[878,2119,1532],{},[870,2121,1537],{"id":1536},[856,2123,1540],{},[883,2125,1544],{"id":1543},[856,2127,1547,2128,1551,2130,1555,2132,1559,2134,1562],{},[878,2129,1550],{},[878,2131,1554],{},[878,2133,1558],{},[878,2135,1554],{},[856,2137,1565,2138,1568],{},[863,2139,147],{"href":211},[883,2141,1572],{"id":1571},[856,2143,1575],{},[910,2145,2146,2152,2156,2160,2168,2172,2176],{},[913,2147,2148,1582,2150,925],{},[878,2149,1227],{},[878,2151,1181],{},[913,2153,2154,1589],{},[878,2155,1277],{},[913,2157,2158,1594],{},[878,2159,1301],{},[913,2161,2162,1600,2164,1603,2166,925],{},[878,2163,1599],{},[878,2165,1381],{},[878,2167,1352],{},[913,2169,2170,1610],{},[878,2171,1401],{},[913,2173,2174,1616],{},[878,2175,1615],{},[913,2177,2178,1496,2180,1625],{},[878,2179,1621],{},[878,2181,1624],{},[856,2183,1628,2184,925],{},[878,2185,1352],{},[870,2187,1634],{"id":1633},[856,2189,1637],{},[910,2191,2192,2194,2198,2200,2204],{},[913,2193,1642],{},[913,2195,2196,1648],{},[878,2197,1647],{},[913,2199,1651],{},[913,2201,2202,1656],{},[878,2203,1130],{},[913,2205,1659,2206,1662],{},[878,2207,964],{},[1664,2209,2210],{},[856,2211,1668],{},{"title":1670,"searchDepth":1671,"depth":1671,"links":2213},[2214,2219,2224,2232,2237,2242,2246],{"id":872,"depth":1671,"text":873,"children":2215},[2216,2217,2218],{"id":885,"depth":1676,"text":886},{"id":968,"depth":1676,"text":969},{"id":1001,"depth":1676,"text":1002},{"id":1067,"depth":1671,"text":1068,"children":2220},[2221,2222,2223],{"id":1074,"depth":1676,"text":1075},{"id":1134,"depth":1676,"text":1135},{"id":1158,"depth":1676,"text":1159},{"id":1202,"depth":1671,"text":1203,"children":2225},[2226,2227,2228,2229,2230,2231],{"id":1209,"depth":1676,"text":1210},{"id":1238,"depth":1676,"text":1239},{"id":1262,"depth":1676,"text":1263},{"id":1283,"depth":1676,"text":1284},{"id":1308,"depth":1676,"text":1309},{"id":1339,"depth":1676,"text":1340},{"id":1356,"depth":1671,"text":1357,"children":2233},[2234,2235,2236],{"id":1363,"depth":1676,"text":1364},{"id":1405,"depth":1676,"text":1406},{"id":1419,"depth":1676,"text":1420},{"id":1437,"depth":1671,"text":1438,"children":2238},[2239,2240,2241],{"id":1444,"depth":1676,"text":1445},{"id":1477,"depth":1676,"text":1478},{"id":1504,"depth":1676,"text":1505},{"id":1536,"depth":1671,"text":1537,"children":2243},[2244,2245],{"id":1543,"depth":1676,"text":1544},{"id":1571,"depth":1676,"text":1572},{"id":1633,"depth":1671,"text":1634},{},{"title":38,"description":1707},1780436285484]