[{"data":1,"prerenderedAt":2871},["ShallowReactive",2],{"navLinks":3,"sidebar_docs_navigation_\u002Fblog\u002Fhow-token-rotation-works":64,"navigation":77,"navLinks_footer":791,"\u002Fblog\u002Fhow-token-rotation-works_page":804,"\u002Fblog\u002Fhow-token-rotation-works":2025},{"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":54,"path":56,"stem":66,"children":67,"page":53},"blog",[68,73],{"title":69,"path":70,"stem":71,"icon":72},"IAM API Tokens with Auth H3 Client: Secure M2M Access in Nuxt and Nitro","\u002Fblog\u002Fiam-api-tokens-auth-h3client","blog\u002Fiam-api-tokens-auth-h3client",null,{"title":74,"path":75,"stem":76,"icon":72},"Layered Bot Defense: How Shield Base, Bot Detector, and the IAM Canary Cookie Work Together","\u002Fblog\u002Flayered-bot-defense","blog\u002Flayered-bot-defense",[78],{"title":9,"path":79,"stem":80,"children":81,"page":53},"\u002Fdocs","docs",[82,230,348,353,531,598],{"title":20,"path":22,"stem":83,"children":84},"docs\u002Fauth-h3client\u002Findex",[85,86,95,132,158,180,183,204,208],{"title":20,"path":22,"stem":83},{"title":14,"path":87,"stem":88,"children":89},"\u002Fdocs\u002Fauth-h3client\u002Fgetting-started","docs\u002Fauth-h3client\u002F00.getting-started\u002Findex",[90,91],{"title":14,"path":87,"stem":88},{"title":92,"path":93,"stem":94},"Nuxt Module","\u002Fdocs\u002Fauth-h3client\u002Fgetting-started\u002Fnuxt","docs\u002Fauth-h3client\u002F00.getting-started\u002F00.nuxt",{"title":96,"path":97,"stem":98,"children":99},"Essentials","\u002Fdocs\u002Fauth-h3client\u002Fessentials","docs\u002Fauth-h3client\u002F01.essentials\u002Findex",[100,101,105,109,113,117,121,124,128],{"title":96,"path":97,"stem":98},{"title":102,"path":103,"stem":104},"Session Management","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fsession","docs\u002Fauth-h3client\u002F01.essentials\u002F00.session",{"title":106,"path":107,"stem":108},"Route Protection","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Froute-protection","docs\u002Fauth-h3client\u002F01.essentials\u002F01.route-protection",{"title":110,"path":111,"stem":112},"CSRF Protection","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fcsrf","docs\u002Fauth-h3client\u002F01.essentials\u002F02.csrf",{"title":114,"path":115,"stem":116},"Auth Flows","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fauth-flows","docs\u002Fauth-h3client\u002F01.essentials\u002F03.auth-flows",{"title":118,"path":119,"stem":120},"OAuth and OIDC","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Foauth","docs\u002Fauth-h3client\u002F01.essentials\u002F04.oauth",{"title":33,"path":122,"stem":123},"\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fbot-detection","docs\u002Fauth-h3client\u002F01.essentials\u002F05.bot-detection",{"title":125,"path":126,"stem":127},"Cookies","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fcookies","docs\u002Fauth-h3client\u002F01.essentials\u002F06.cookies",{"title":129,"path":130,"stem":131},"Logging","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Flogging","docs\u002Fauth-h3client\u002F01.essentials\u002F07.logging",{"title":133,"path":134,"stem":135,"children":136},"MFA","\u002Fdocs\u002Fauth-h3client\u002Fmfa","docs\u002Fauth-h3client\u002F02.mfa\u002Findex",[137,138,142,146,150,154],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141},"Built-in MFA","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fbuilt-in-flow","docs\u002Fauth-h3client\u002F02.mfa\u002F01.built-in-flow",{"title":143,"path":144,"stem":145},"Password Reset","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fpassword-reset","docs\u002Fauth-h3client\u002F02.mfa\u002F02.password-reset",{"title":147,"path":148,"stem":149},"Email Change","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Femail-change","docs\u002Fauth-h3client\u002F02.mfa\u002F03.email-change",{"title":151,"path":152,"stem":153},"Custom MFA Flow","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fcustom-flow","docs\u002Fauth-h3client\u002F02.mfa\u002F04.custom-flow",{"title":155,"path":156,"stem":157},"Client-Side MFA","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fclient-side","docs\u002Fauth-h3client\u002F02.mfa\u002F05.client-side",{"title":159,"path":160,"stem":161,"children":162},"Client-side","\u002Fdocs\u002Fauth-h3client\u002Fclient","docs\u002Fauth-h3client\u002F03.client\u002Findex",[163,164,168,172,176],{"title":159,"path":160,"stem":161},{"title":165,"path":166,"stem":167},"useAuthData","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fuse-auth-data","docs\u002Fauth-h3client\u002F03.client\u002F00.use-auth-data",{"title":169,"path":170,"stem":171},"useMagicLink","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fuse-magic-link","docs\u002Fauth-h3client\u002F03.client\u002F01.use-magic-link",{"title":173,"path":174,"stem":175},"executeRequest","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fexecute-request","docs\u002Fauth-h3client\u002F03.client\u002F02.execute-request",{"title":177,"path":178,"stem":179},"getCsrfToken","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fget-csrf-token","docs\u002Fauth-h3client\u002F03.client\u002F03.get-csrf-token",{"title":38,"path":181,"stem":182},"\u002Fdocs\u002Fauth-h3client\u002Fsecurity","docs\u002Fauth-h3client\u002F04.security",{"title":184,"path":185,"stem":186,"children":187,"page":53},"Guides","\u002Fdocs\u002Fauth-h3client\u002Fguides","docs\u002Fauth-h3client\u002F05.guides",[188,192,196,200],{"title":189,"path":190,"stem":191},"H3 and Nitro Setup","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fh3-nitro","docs\u002Fauth-h3client\u002F05.guides\u002F00.h3-nitro",{"title":193,"path":194,"stem":195},"HMAC Inter-service Auth","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fhmac","docs\u002Fauth-h3client\u002F05.guides\u002Fhmac",{"title":197,"path":198,"stem":199},"Image Upload","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fimage-upload","docs\u002Fauth-h3client\u002F05.guides\u002Fimage-upload",{"title":201,"path":202,"stem":203},"mTLS Configuration","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fmtls","docs\u002Fauth-h3client\u002F05.guides\u002Fmtls",{"title":205,"path":206,"stem":207},"Configuration","\u002Fdocs\u002Fauth-h3client\u002Fconfiguration","docs\u002Fauth-h3client\u002F06.configuration",{"title":209,"path":210,"stem":211,"children":212},"API Reference","\u002Fdocs\u002Fauth-h3client\u002Fapi","docs\u002Fauth-h3client\u002F07.api\u002Findex",[213,214,218,222,226],{"title":209,"path":210,"stem":211},{"title":215,"path":216,"stem":217},"Routes Reference","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fcontrollers","docs\u002Fauth-h3client\u002F07.api\u002F00.controllers",{"title":219,"path":220,"stem":221},"Middleware Reference","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fmiddleware","docs\u002Fauth-h3client\u002F07.api\u002F01.middleware",{"title":223,"path":224,"stem":225},"Client-side Reference","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fcomposables","docs\u002Fauth-h3client\u002F07.api\u002F02.composables",{"title":227,"path":228,"stem":229},"Utilities","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Futilities","docs\u002Fauth-h3client\u002F07.api\u002F03.utilities",{"title":231,"path":35,"stem":232,"children":233},"Bot Detector","docs\u002Fbot-detection\u002Findex",[234,235,238,242,246,265,339,342,345],{"title":231,"path":35,"stem":232},{"title":14,"path":236,"stem":237},"\u002Fdocs\u002Fbot-detection\u002Fgetting-started","docs\u002Fbot-detection\u002F00.getting-started",{"title":239,"path":240,"stem":241},"CLI","\u002Fdocs\u002Fbot-detection\u002Fcli","docs\u002Fbot-detection\u002F01.cli",{"title":243,"path":244,"stem":245},"Data Sources","\u002Fdocs\u002Fbot-detection\u002Fdata-sources","docs\u002Fbot-detection\u002F02.data-sources",{"title":184,"path":247,"stem":248,"children":249,"page":53},"\u002Fdocs\u002Fbot-detection\u002Fguides","docs\u002Fbot-detection\u002F03.guides",[250,254,258,261],{"title":251,"path":252,"stem":253},"Custom Checkers","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fcustom","docs\u002Fbot-detection\u002F03.guides\u002FCUSTOM",{"title":255,"path":256,"stem":257},"Scheduling Database Generation","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fgenerate","docs\u002Fbot-detection\u002F03.guides\u002FGENERATE",{"title":129,"path":259,"stem":260},"\u002Fdocs\u002Fbot-detection\u002Fguides\u002Flogging","docs\u002Fbot-detection\u002F03.guides\u002FLOGGING",{"title":262,"path":263,"stem":264},"Score Modes and Reputation Healing","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fscore","docs\u002Fbot-detection\u002F03.guides\u002FSCORE",{"title":266,"path":267,"stem":268,"children":269},"Checkers","\u002Fdocs\u002Fbot-detection\u002Fcheckers","docs\u002Fbot-detection\u002F04.checkers\u002Findex",[270,271,275,279,283,287,291,295,299,303,307,311,315,319,323,327,331,335],{"title":266,"path":267,"stem":268},{"title":272,"path":273,"stem":274},"IP Validation","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fip-validation","docs\u002Fbot-detection\u002F04.checkers\u002F01.ip-validation",{"title":276,"path":277,"stem":278},"Good \u002F Bad Bot Verification","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fgood-bots","docs\u002Fbot-detection\u002F04.checkers\u002F02.good-bots",{"title":280,"path":281,"stem":282},"Browser & Device Fingerprint","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fbrowser-device","docs\u002Fbot-detection\u002F04.checkers\u002F03.browser-device",{"title":284,"path":285,"stem":286},"Locale Map","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Flocale-map","docs\u002Fbot-detection\u002F04.checkers\u002F04.locale-map",{"title":288,"path":289,"stem":290},"Known Threats","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-threats","docs\u002Fbot-detection\u002F04.checkers\u002F05.known-threats",{"title":292,"path":293,"stem":294},"ASN Classification","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fasn-classification","docs\u002Fbot-detection\u002F04.checkers\u002F06.asn-classification",{"title":296,"path":297,"stem":298},"Tor Analysis","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Ftor-analysis","docs\u002Fbot-detection\u002F04.checkers\u002F07.tor-analysis",{"title":300,"path":301,"stem":302},"Timezone Consistency","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Ftimezone-consistency","docs\u002Fbot-detection\u002F04.checkers\u002F08.timezone-consistency",{"title":304,"path":305,"stem":306},"Honeypot","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fhoneypot","docs\u002Fbot-detection\u002F04.checkers\u002F09.honeypot",{"title":308,"path":309,"stem":310},"Known Bad IPs","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-bad-ips","docs\u002Fbot-detection\u002F04.checkers\u002F10.known-bad-ips",{"title":312,"path":313,"stem":314},"Behavior Rate","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fbehavior-rate","docs\u002Fbot-detection\u002F04.checkers\u002F11.behavior-rate",{"title":316,"path":317,"stem":318},"Proxy \u002F ISP \u002F Cookie","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fproxy-isp-cookies","docs\u002Fbot-detection\u002F04.checkers\u002F12.proxy-isp-cookies",{"title":320,"path":321,"stem":322},"Session Coherence","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fsession-coherence","docs\u002Fbot-detection\u002F04.checkers\u002F13.session-coherence",{"title":324,"path":325,"stem":326},"Velocity Fingerprint","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fvelocity-fingerprint","docs\u002Fbot-detection\u002F04.checkers\u002F14.velocity-fingerprint",{"title":328,"path":329,"stem":330},"UA & Header Analysis","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fua-header","docs\u002Fbot-detection\u002F04.checkers\u002F15.ua-header",{"title":332,"path":333,"stem":334},"Geolocation","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fgeolocation","docs\u002Fbot-detection\u002F04.checkers\u002F16.geolocation",{"title":336,"path":337,"stem":338},"Known Bad User-Agents","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-bad-ua","docs\u002Fbot-detection\u002F04.checkers\u002F17.known-bad-ua",{"title":38,"path":340,"stem":341},"\u002Fdocs\u002Fbot-detection\u002Fsecurity","docs\u002Fbot-detection\u002F04.security",{"title":209,"path":343,"stem":344},"\u002Fdocs\u002Fbot-detection\u002Fapi","docs\u002Fbot-detection\u002F05.api",{"title":205,"path":346,"stem":347},"\u002Fdocs\u002Fbot-detection\u002Fconfiguration","docs\u002Fbot-detection\u002F06.configuration",{"title":349,"path":11,"stem":350,"children":351},"Introduction","docs\u002Fgetting-started\u002Findex",[352],{"title":349,"path":11,"stem":350},{"title":27,"path":29,"stem":354,"children":355},"docs\u002Fiam\u002Findex",[356,357,360,495,498,514,517],{"title":27,"path":29,"stem":354},{"title":14,"path":358,"stem":359},"\u002Fdocs\u002Fiam\u002Fgetting-started","docs\u002Fiam\u002F00.getting-started",{"title":96,"path":361,"stem":362,"children":363},"\u002Fdocs\u002Fiam\u002Fessentials","docs\u002Fiam\u002F01.essentials\u002Findex",[364,365,369,373,377,381,385,389,393,397,401,405,408,412,416,420,424,427,431,435,438,442,445],{"title":96,"path":361,"stem":362},{"title":366,"path":367,"stem":368},"Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Ftokens","docs\u002Fiam\u002F01.essentials\u002F00.tokens",{"title":370,"path":371,"stem":372},"Access Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Faccess-tokens","docs\u002Fiam\u002F01.essentials\u002F01.access-tokens",{"title":374,"path":375,"stem":376},"Refresh Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Frefresh-tokens","docs\u002Fiam\u002F01.essentials\u002F02.refresh-tokens",{"title":378,"path":379,"stem":380},"Anomaly Detection","\u002Fdocs\u002Fiam\u002Fessentials\u002Fanomalies","docs\u002Fiam\u002F01.essentials\u002F03.anomalies",{"title":382,"path":383,"stem":384},"Signup","\u002Fdocs\u002Fiam\u002Fessentials\u002Fsignup","docs\u002Fiam\u002F01.essentials\u002F04.signup",{"title":386,"path":387,"stem":388},"Login","\u002Fdocs\u002Fiam\u002Fessentials\u002Flogin","docs\u002Fiam\u002F01.essentials\u002F05.login",{"title":390,"path":391,"stem":392},"Logout","\u002Fdocs\u002Fiam\u002Fessentials\u002Flogout","docs\u002Fiam\u002F01.essentials\u002F06.logout",{"title":394,"path":395,"stem":396},"OAuth","\u002Fdocs\u002Fiam\u002Fessentials\u002Foauth","docs\u002Fiam\u002F01.essentials\u002F07.oauth",{"title":398,"path":399,"stem":400},"Magic Links","\u002Fdocs\u002Fiam\u002Fessentials\u002Fmagic-links","docs\u002Fiam\u002F01.essentials\u002F08.magic-links",{"title":402,"path":403,"stem":404},"Emails","\u002Fdocs\u002Fiam\u002Fessentials\u002Femails","docs\u002Fiam\u002F01.essentials\u002F09.emails",{"title":133,"path":406,"stem":407},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fmfa","docs\u002Fiam\u002F01.essentials\u002F10.mfa",{"title":409,"path":410,"stem":411},"Fingerprinting","\u002Fdocs\u002Fiam\u002Fessentials\u002Ffingerprinting","docs\u002Fiam\u002F01.essentials\u002F11.fingerprinting",{"title":413,"path":414,"stem":415},"Backend for Frontend","\u002Fdocs\u002Fiam\u002Fessentials\u002Fbff","docs\u002Fiam\u002F01.essentials\u002F12.bff",{"title":417,"path":418,"stem":419},"HMAC Authentication","\u002Fdocs\u002Fiam\u002Fessentials\u002Fhmac","docs\u002Fiam\u002F01.essentials\u002F13.hmac",{"title":421,"path":422,"stem":423},"XSS Protection","\u002Fdocs\u002Fiam\u002Fessentials\u002Fxss","docs\u002Fiam\u002F01.essentials\u002F14.xss",{"title":129,"path":425,"stem":426},"\u002Fdocs\u002Fiam\u002Fessentials\u002Flogging","docs\u002Fiam\u002F01.essentials\u002F15.logging",{"title":428,"path":429,"stem":430},"Rate Limiting","\u002Fdocs\u002Fiam\u002Fessentials\u002Frate-limiting","docs\u002Fiam\u002F01.essentials\u002F16.rate-limiting",{"title":432,"path":433,"stem":434},"Database","\u002Fdocs\u002Fiam\u002Fessentials\u002Fdatabase","docs\u002Fiam\u002F01.essentials\u002F17.database",{"title":125,"path":436,"stem":437},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fcookies","docs\u002Fiam\u002F01.essentials\u002F18.cookies",{"title":439,"path":440,"stem":441},"Service Startup","\u002Fdocs\u002Fiam\u002Fessentials\u002Fservice","docs\u002Fiam\u002F01.essentials\u002F19.service",{"title":143,"path":443,"stem":444},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fpassword-reset","docs\u002Fiam\u002F01.essentials\u002F20.password-reset",{"title":446,"path":447,"stem":448,"children":449},"API Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi","docs\u002Fiam\u002F01.essentials\u002F21.api\u002Findex",[450,451,455,459,489,492],{"title":446,"path":447,"stem":448},{"title":452,"path":453,"stem":454},"Creating Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fcreation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F00.creation",{"title":456,"path":457,"stem":458},"Verifying Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fverification","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F01.verification",{"title":460,"path":461,"stem":462,"children":463},"Management","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002Findex",[464,465,469,473,477,481,485],{"title":460,"path":461,"stem":462},{"title":466,"path":467,"stem":468},"Privilege and Scopes","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fprivilege","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F00.privilege",{"title":470,"path":471,"stem":472},"Revocation","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Frevocation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F01.revocation",{"title":474,"path":475,"stem":476},"Rotation","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Frotation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F02.rotation",{"title":478,"path":479,"stem":480},"IP Restriction","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fip-updates","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F03.ip-updates",{"title":482,"path":483,"stem":484},"Metadata","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fmetadata","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F04.metadata",{"title":486,"path":487,"stem":488},"Token Listing","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Flist","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F05.list",{"title":428,"path":490,"stem":491},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Frate-limiting","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F03.rate-limiting",{"title":38,"path":493,"stem":494},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fsecurity","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F04.security",{"title":38,"path":496,"stem":497},"\u002Fdocs\u002Fiam\u002Fsecurity","docs\u002Fiam\u002F02.security",{"title":184,"path":499,"stem":500,"children":501,"page":53},"\u002Fdocs\u002Fiam\u002Fguides","docs\u002Fiam\u002F03.guides",[502,506,510],{"title":503,"path":504,"stem":505},"Deployment","\u002Fdocs\u002Fiam\u002Fguides\u002Fdeployment","docs\u002Fiam\u002F03.guides\u002Fdeployment",{"title":507,"path":508,"stem":509},"Operation Scripts","\u002Fdocs\u002Fiam\u002Fguides\u002Foperation-scripts","docs\u002Fiam\u002F03.guides\u002Foperation-scripts",{"title":511,"path":512,"stem":513},"Role-Based Access Control","\u002Fdocs\u002Fiam\u002Fguides\u002Frbac","docs\u002Fiam\u002F03.guides\u002Frbac",{"title":205,"path":515,"stem":516},"\u002Fdocs\u002Fiam\u002Fconfiguration","docs\u002Fiam\u002F04.configuration",{"title":518,"path":519,"stem":520,"children":521,"page":53},"Api","\u002Fdocs\u002Fiam\u002Fapi","docs\u002Fiam\u002F05.API",[522,525,528],{"title":209,"path":523,"stem":524},"\u002Fdocs\u002Fiam\u002Fapi\u002Fapi","docs\u002Fiam\u002F05.API\u002F00.api",{"title":219,"path":526,"stem":527},"\u002Fdocs\u002Fiam\u002Fapi\u002Fmiddlewares","docs\u002Fiam\u002F05.API\u002F02.middlewares",{"title":215,"path":529,"stem":530},"\u002Fdocs\u002Fiam\u002Fapi\u002Froutes","docs\u002Fiam\u002F05.API\u002F03.routes",{"title":40,"path":42,"stem":532,"children":533},"docs\u002Fshield-base\u002Findex",[534,535,538,542,583,587,591,595],{"title":40,"path":42,"stem":532},{"title":14,"path":536,"stem":537},"\u002Fdocs\u002Fshield-base\u002Fgetting-started","docs\u002Fshield-base\u002F00.getting-started",{"title":539,"path":540,"stem":541},"CLI Reference","\u002Fdocs\u002Fshield-base\u002Fcli","docs\u002Fshield-base\u002F01.cli",{"title":243,"path":543,"stem":544,"children":545},"\u002Fdocs\u002Fshield-base\u002Fdata-sources","docs\u002Fshield-base\u002F02.data-sources\u002Findex",[546,547,551,555,559,563,567,571,575,579],{"title":243,"path":543,"stem":544},{"title":548,"path":549,"stem":550},"BGP \u002F ASN","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fbgp","docs\u002Fshield-base\u002F02.data-sources\u002Fbgp",{"title":552,"path":553,"stem":554},"City Geolocation","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcity","docs\u002Fshield-base\u002F02.data-sources\u002Fcity",{"title":556,"path":557,"stem":558},"Country Geolocation","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcountry","docs\u002Fshield-base\u002F02.data-sources\u002Fcountry",{"title":560,"path":561,"stem":562},"Verified Crawlers","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcrawlers","docs\u002Fshield-base\u002F02.data-sources\u002Fcrawlers",{"title":564,"path":565,"stem":566},"Disposable Emails","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Femail","docs\u002Fshield-base\u002F02.data-sources\u002Femail",{"title":568,"path":569,"stem":570},"FireHOL Threat Intelligence","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Ffirehol","docs\u002Fshield-base\u002F02.data-sources\u002Ffirehol",{"title":572,"path":573,"stem":574},"Proxy Detection","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fproxy","docs\u002Fshield-base\u002F02.data-sources\u002Fproxy",{"title":576,"path":577,"stem":578},"Tor Nodes","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Ftor","docs\u002Fshield-base\u002F02.data-sources\u002Ftor",{"title":580,"path":581,"stem":582},"Suspicious User-Agents","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fuseragent","docs\u002Fshield-base\u002F02.data-sources\u002Fuseragent",{"title":584,"path":585,"stem":586},"Programmatic Usage","\u002Fdocs\u002Fshield-base\u002Fusage","docs\u002Fshield-base\u002F03.usage",{"title":588,"path":589,"stem":590},"Custom Data Sources","\u002Fdocs\u002Fshield-base\u002Fcustom-data-sources","docs\u002Fshield-base\u002F04.custom-data-sources",{"title":592,"path":593,"stem":594},"TypeScript Types","\u002Fdocs\u002Fshield-base\u002Ftypes","docs\u002Fshield-base\u002F05.types",{"title":209,"path":596,"stem":597},"\u002Fdocs\u002Fshield-base\u002Fapi","docs\u002Fshield-base\u002F06.api",{"title":227,"path":48,"stem":599,"children":600},"docs\u002Futils\u002Findex",[601,602,619,652,749],{"title":227,"path":48,"stem":599},{"title":603,"path":604,"stem":605,"children":606,"page":53},"Eslint","\u002Fdocs\u002Futils\u002Feslint","docs\u002Futils\u002Feslint",[607,611,615],{"title":608,"path":609,"stem":610},"React Config","\u002Fdocs\u002Futils\u002Feslint\u002Freact","docs\u002Futils\u002Feslint\u002Freact",{"title":612,"path":613,"stem":614},"TypeScript Config","\u002Fdocs\u002Futils\u002Feslint\u002Ftypescript","docs\u002Futils\u002Feslint\u002Ftypescript",{"title":616,"path":617,"stem":618},"Vue Config","\u002Fdocs\u002Futils\u002Feslint\u002Fvue","docs\u002Futils\u002Feslint\u002Fvue",{"title":620,"path":621,"stem":622,"children":623,"page":53},"Server","\u002Fdocs\u002Futils\u002Fserver","docs\u002Futils\u002Fserver",[624,628,632,636,640,644,648],{"title":625,"path":626,"stem":627},"Encryption","\u002Fdocs\u002Futils\u002Fserver\u002Fencryption","docs\u002Futils\u002Fserver\u002Fencryption",{"title":629,"path":630,"stem":631},"Path Resolver","\u002Fdocs\u002Futils\u002Fserver\u002Fpathresolver","docs\u002Futils\u002Fserver\u002FpathResolver",{"title":633,"path":634,"stem":635},"File Replacements","\u002Fdocs\u002Futils\u002Fserver\u002Freplace","docs\u002Futils\u002Fserver\u002Freplace",{"title":637,"path":638,"stem":639},"run","\u002Fdocs\u002Futils\u002Fserver\u002Frun","docs\u002Futils\u002Fserver\u002Frun",{"title":641,"path":642,"stem":643},"scheduleTask","\u002Fdocs\u002Futils\u002Fserver\u002Fscheduletask","docs\u002Futils\u002Fserver\u002FscheduleTask",{"title":645,"path":646,"stem":647},"spawnRun","\u002Fdocs\u002Futils\u002Fserver\u002Fspawnrun","docs\u002Futils\u002Fserver\u002FspawnRun",{"title":649,"path":650,"stem":651},"uploadCsv","\u002Fdocs\u002Futils\u002Fserver\u002Fuploadcsv","docs\u002Futils\u002Fserver\u002FuploadCsv",{"title":653,"path":654,"stem":655,"children":656,"page":53},"Shared","\u002Fdocs\u002Futils\u002Fshared","docs\u002Futils\u002Fshared",[657,661,665,669,673,677,681,685,689,693,697,701,705,709,713,717,721,725,729,733,737,741,745],{"title":658,"path":659,"stem":660},"BatchQueue","\u002Fdocs\u002Futils\u002Fshared\u002Fbatchqueue","docs\u002Futils\u002Fshared\u002FbatchQueue",{"title":662,"path":663,"stem":664},"capitalize","\u002Fdocs\u002Futils\u002Fshared\u002Fcapitalize","docs\u002Futils\u002Fshared\u002Fcapitalize",{"title":666,"path":667,"stem":668},"chunkProcess","\u002Fdocs\u002Futils\u002Fshared\u002Fchunkprocess","docs\u002Futils\u002Fshared\u002FchunkProcess",{"title":670,"path":671,"stem":672},"cleanObject","\u002Fdocs\u002Futils\u002Fshared\u002Fcleanobject","docs\u002Futils\u002Fshared\u002FcleanObject",{"title":674,"path":675,"stem":676},"createConfigManager","\u002Fdocs\u002Futils\u002Fshared\u002Fconfigurationdefiner","docs\u002Futils\u002Fshared\u002FconfigurationDefiner",{"title":678,"path":679,"stem":680},"debounce","\u002Fdocs\u002Futils\u002Fshared\u002Fdebounce","docs\u002Futils\u002Fshared\u002Fdebounce",{"title":682,"path":683,"stem":684},"ensureArray","\u002Fdocs\u002Futils\u002Fshared\u002Fensurearray","docs\u002Futils\u002Fshared\u002FensureArray",{"title":686,"path":687,"stem":688},"fetchWithRetry","\u002Fdocs\u002Futils\u002Fshared\u002Ffetchwithretry","docs\u002Futils\u002Fshared\u002FfetchWithRetry",{"title":690,"path":691,"stem":692},"filterEmptyValues","\u002Fdocs\u002Futils\u002Fshared\u002Ffilteremptyvalues","docs\u002Futils\u002Fshared\u002FfilterEmptyValues",{"title":694,"path":695,"stem":696},"findStringsInObject","\u002Fdocs\u002Futils\u002Fshared\u002Ffindobjectvalues","docs\u002Futils\u002Fshared\u002FfindObjectValues",{"title":698,"path":699,"stem":700},"fisherYatesShuffle","\u002Fdocs\u002Futils\u002Fshared\u002Ffisheryatesshuffle","docs\u002Futils\u002Fshared\u002FfisherYatesShuffle",{"title":702,"path":703,"stem":704},"getRandomImage","\u002Fdocs\u002Futils\u002Fshared\u002Fgetrandomimage","docs\u002Futils\u002Fshared\u002FgetRandomImage",{"title":706,"path":707,"stem":708},"isObjectHasValues","\u002Fdocs\u002Futils\u002Fshared\u002Fisobjecthasvalues","docs\u002Futils\u002Fshared\u002FisObjectHasValues",{"title":710,"path":711,"stem":712},"isAsyncOrPromise","\u002Fdocs\u002Futils\u002Fshared\u002Fispromise","docs\u002Futils\u002Fshared\u002FisPromise",{"title":714,"path":715,"stem":716},"MiniCache","\u002Fdocs\u002Futils\u002Fshared\u002Fminicache","docs\u002Futils\u002Fshared\u002FminiCache",{"title":718,"path":719,"stem":720},"parseCookies","\u002Fdocs\u002Futils\u002Fshared\u002Fparserawcookies","docs\u002Futils\u002Fshared\u002FparseRawCookies",{"title":722,"path":723,"stem":724},"safeAction","\u002Fdocs\u002Futils\u002Fshared\u002Fpromiselocker","docs\u002Futils\u002Fshared\u002FpromiseLocker",{"title":726,"path":727,"stem":728},"Random","\u002Fdocs\u002Futils\u002Fshared\u002Frandom","docs\u002Futils\u002Fshared\u002Frandom",{"title":730,"path":731,"stem":732},"range","\u002Fdocs\u002Futils\u002Fshared\u002Frange","docs\u002Futils\u002Fshared\u002Frange",{"title":734,"path":735,"stem":736},"rateLimiters","\u002Fdocs\u002Futils\u002Fshared\u002Fratelimiters","docs\u002Futils\u002Fshared\u002FrateLimiters",{"title":738,"path":739,"stem":740},"safeObjectMerge","\u002Fdocs\u002Futils\u002Fshared\u002Fsafemerge","docs\u002Futils\u002Fshared\u002FsafeMerge",{"title":742,"path":743,"stem":744},"textTruncation","\u002Fdocs\u002Futils\u002Fshared\u002Ftexttruncation","docs\u002Futils\u002Fshared\u002FtextTruncation",{"title":746,"path":747,"stem":748},"validateZodSchema","\u002Fdocs\u002Futils\u002Fshared\u002Fvalidatezodschema","docs\u002Futils\u002Fshared\u002FvalidateZodSchema",{"title":750,"path":751,"stem":752,"children":753},"Utility Types","\u002Fdocs\u002Futils\u002Ftypes","docs\u002Futils\u002Ftypes\u002Findex",[754,755,759,763,767,771,775,779,783,787],{"title":750,"path":751,"stem":752},{"title":756,"path":757,"stem":758},"Brand","\u002Fdocs\u002Futils\u002Ftypes\u002Fbrand","docs\u002Futils\u002Ftypes\u002FBrand",{"title":760,"path":761,"stem":762},"DeepPartial","\u002Fdocs\u002Futils\u002Ftypes\u002Fdeeppartial","docs\u002Futils\u002Ftypes\u002FDeepPartial",{"title":764,"path":765,"stem":766},"Merge","\u002Fdocs\u002Futils\u002Ftypes\u002Fmerge","docs\u002Futils\u002Ftypes\u002FMerge",{"title":768,"path":769,"stem":770},"NonNullable","\u002Fdocs\u002Futils\u002Ftypes\u002Fnonnullable","docs\u002Futils\u002Ftypes\u002FNonNullable",{"title":772,"path":773,"stem":774},"Prettify","\u002Fdocs\u002Futils\u002Ftypes\u002Fprettify","docs\u002Futils\u002Ftypes\u002FPrettify",{"title":776,"path":777,"stem":778},"PromiseType","\u002Fdocs\u002Futils\u002Ftypes\u002Fpromisetype","docs\u002Futils\u002Ftypes\u002FPromiseType",{"title":780,"path":781,"stem":782},"RequireKeys","\u002Fdocs\u002Futils\u002Ftypes\u002Frequirekeys","docs\u002Futils\u002Ftypes\u002FRequireKeys",{"title":784,"path":785,"stem":786},"StandardResponse","\u002Fdocs\u002Futils\u002Ftypes\u002Fstandardresponse","docs\u002Futils\u002Ftypes\u002FStandardResponse",{"title":788,"path":789,"stem":790},"ValueOf","\u002Fdocs\u002Futils\u002Ftypes\u002Fvalueof","docs\u002Futils\u002Ftypes\u002FValueOf",{"id":4,"extension":5,"links":792,"meta":803,"stem":62,"__hash__":63},[793,801,802],{"nested":8,"label":9,"icon":10,"to":11,"children":794},[795,796,797,798,799,800],{"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":805,"title":806,"author":807,"authorGithub":808,"authorGithubUserName":809,"authorImg":810,"body":811,"date":2013,"description":2014,"extension":2015,"featured":53,"icon":72,"image":2016,"meta":2017,"navigation":53,"path":2018,"rawbody":2019,"readingTime":2020,"seo":2021,"stem":2022,"tags":2023,"__hash__":2024},"blog\u002Fblog\u002Fhow-token-rotation-works.md","How Token Rotation Works: Access Tokens, Refresh Tokens, and the Deduplication Problem","Sergio","https:\u002F\u002Fgithub.com\u002FSergo706","Sergo706","https:\u002F\u002Fgithub.com\u002FSergo706.png",{"type":812,"value":813,"toc":1996},"minimark",[814,818,821,824,829,832,845,863,1017,1026,1033,1035,1039,1042,1045,1051,1135,1138,1140,1144,1150,1153,1242,1254,1263,1265,1269,1272,1285,1402,1409,1481,1483,1487,1490,1493,1506,1509,1523,1605,1608,1611,1613,1617,1620,1625,1635,1650,1655,1671,1674,1676,1680,1683,1706,1724,1733,1741,1744,1746,1750,1760,1769,1838,1848,1954,1957,1959,1963,1966,1969,1975,1978,1986,1992],[815,816,817],"p",{},"Most authentication systems issue a single credential — a session ID, a JWT, a cookie — and use it until it expires or the user logs out. The problem with that model is straightforward: if an attacker obtains that credential, they have as long as it lives to use it. The longer it lives, the bigger the exposure window.",[815,819,820],{},"Riavzon solves this with a dual-token architecture. Access tokens are short-lived and verified cryptographically. Refresh tokens are long-lived but stored as hashes in a database, consumed atomically, and wrapped in a reuse detection system that revokes every session the moment replay is detected. This post explains every layer of that architecture: why it is designed this way, how each piece works, and what happens when two requests from the same user arrive at the same time.",[822,823],"hr",{},[825,826,828],"h2",{"id":827},"the-two-token-model","The Two-Token Model",[815,830,831],{},"Every authenticated user in the system holds two credentials at once.",[815,833,834,835,839,840,844],{},"The ",[836,837,838],"strong",{},"access token"," is a signed JWT. It lives in a ",[841,842,843],"code",{},"__Secure-a"," cookie on the browser. Its lifetime is short — typically 15 minutes — and it is verified on every request without touching the database. The IAM service uses an LRU cache to hold every valid token, so verification is a cache lookup plus a cryptographic check, not a database query. When the token expires, the cache entry is evicted and the next verification call fails immediately.",[815,846,834,847,850,851,854,855,858,859,862],{},[836,848,849],{},"refresh token"," is a 64-byte cryptographically random string, hex encoded. The browser holds the raw token in an ",[841,852,853],{},"httpOnly"," cookie named ",[841,856,857],{},"session",". The server never stores the raw token. Instead, it hashes it with SHA-256 and stores the hash in a MySQL ",[841,860,861],{},"refresh_tokens"," table. The raw token leaves the server exactly once, when it is issued, and the server never sees it again in plaintext.",[864,865,869],"pre",{"className":866,"code":867,"language":5,"meta":868,"style":868},"language-json shiki shiki-themes light-plus light-plus dracula","{\n  \"visitor\": \"vis_abc123\",\n  \"roles\": [\"user\"],\n  \"sub\": \"42\",\n  \"jti\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"iat\": 1710000000,\n  \"exp\": 1710000900\n}\n","",[841,870,871,880,911,936,957,978,996,1011],{"__ignoreMap":868},[872,873,876],"span",{"class":874,"line":875},"line",1,[872,877,879],{"class":878},"sDd4n","{\n",[872,881,883,887,891,894,898,902,906,908],{"class":874,"line":882},2,[872,884,886],{"class":885},"saJyd","  \"",[872,888,890],{"class":889},"s_W10","visitor",[872,892,893],{"class":885},"\"",[872,895,897],{"class":896},"saOXh",":",[872,899,901],{"class":900},"sFkSl"," \"",[872,903,905],{"class":904},"sFB1V","vis_abc123",[872,907,893],{"class":900},[872,909,910],{"class":878},",\n",[872,912,914,916,919,921,923,926,928,931,933],{"class":874,"line":913},3,[872,915,886],{"class":885},[872,917,918],{"class":889},"roles",[872,920,893],{"class":885},[872,922,897],{"class":896},[872,924,925],{"class":878}," [",[872,927,893],{"class":900},[872,929,930],{"class":904},"user",[872,932,893],{"class":900},[872,934,935],{"class":878},"],\n",[872,937,939,941,944,946,948,950,953,955],{"class":874,"line":938},4,[872,940,886],{"class":885},[872,942,943],{"class":889},"sub",[872,945,893],{"class":885},[872,947,897],{"class":896},[872,949,901],{"class":900},[872,951,952],{"class":904},"42",[872,954,893],{"class":900},[872,956,910],{"class":878},[872,958,960,962,965,967,969,971,974,976],{"class":874,"line":959},5,[872,961,886],{"class":885},[872,963,964],{"class":889},"jti",[872,966,893],{"class":885},[872,968,897],{"class":896},[872,970,901],{"class":900},[872,972,973],{"class":904},"550e8400-e29b-41d4-a716-446655440000",[872,975,893],{"class":900},[872,977,910],{"class":878},[872,979,981,983,986,988,990,994],{"class":874,"line":980},6,[872,982,886],{"class":885},[872,984,985],{"class":889},"iat",[872,987,893],{"class":885},[872,989,897],{"class":896},[872,991,993],{"class":992},"spgvN"," 1710000000",[872,995,910],{"class":878},[872,997,999,1001,1004,1006,1008],{"class":874,"line":998},7,[872,1000,886],{"class":885},[872,1002,1003],{"class":889},"exp",[872,1005,893],{"class":885},[872,1007,897],{"class":896},[872,1009,1010],{"class":992}," 1710000900\n",[872,1012,1014],{"class":874,"line":1013},8,[872,1015,1016],{"class":878},"}\n",[815,1018,1019,1020,1022,1023,1025],{},"That is a typical access token payload. The ",[841,1021,964],{}," is a UUID generated fresh on every issuance. It is also the key by which the token lives in the LRU cache. Deleting the cache entry for a ",[841,1024,964],{}," revokes that token immediately, without a database write, without waiting for expiry.",[815,1027,1028,1029,1032],{},"The canary cookie — ",[841,1030,1031],{},"canary_id"," — ties the session to a specific device fingerprint. It is issued by the Bot Detector middleware and is required alongside both tokens for any sensitive operation. It is neither a credential nor an authentication factor on its own, but it binds the token family to the visitor context that created it, and any mismatch triggers anomaly detection.",[822,1034],{},[825,1036,1038],{"id":1037},"why-short-lived-access-tokens","Why Short-Lived Access Tokens",[815,1040,1041],{},"The conventional objection to short-lived tokens is the extra network round trips. If the token expires every 15 minutes, the user's browser needs to refresh it every 15 minutes. That cost is real, but the security benefit justifies it.",[815,1043,1044],{},"An access token that lives for 15 minutes and is stolen gives an attacker a 15-minute window. An access token that lives for 24 hours gives an attacker 24 hours. In practice, the difference between these windows matters enormously when you consider how often stolen credentials go undetected. The 15-minute window usually closes before the attacker can do meaningful damage. The 24-hour window rarely does.",[815,1046,1047,1048,1050],{},"More importantly, the LRU cache is the real enforcement boundary. An access token is not just valid because it carries the right signature. It is valid because it exists in the cache. This means revocation is instant and free. Deleting the cache entry with the token's ",[841,1049,964],{}," terminates that token immediately, regardless of how long it has until expiry. Sessions can be force-terminated without a database write, without blocking, and without any propagation delay.",[864,1052,1056],{"className":1053,"code":1054,"language":1055,"meta":868,"style":868},"language-ts shiki shiki-themes light-plus light-plus dracula","import { tokenCache } from '@riavzon\u002Fauth'\n\nconst cache = tokenCache()\ncache.delete(rawToken) \u002F\u002F This token is now invalid. No database write needed.\n","ts",[841,1057,1058,1086,1091,1111],{"__ignoreMap":868},[872,1059,1060,1064,1067,1071,1074,1077,1080,1083],{"class":874,"line":875},[872,1061,1063],{"class":1062},"sZ328","import",[872,1065,1066],{"class":878}," { ",[872,1068,1070],{"class":1069},"sjsA6","tokenCache",[872,1072,1073],{"class":878}," } ",[872,1075,1076],{"class":1062},"from",[872,1078,1079],{"class":900}," '",[872,1081,1082],{"class":904},"@riavzon\u002Fauth",[872,1084,1085],{"class":900},"'\n",[872,1087,1088],{"class":874,"line":882},[872,1089,1090],{"emptyLinePlaceholder":8},"\n",[872,1092,1093,1097,1101,1104,1108],{"class":874,"line":913},[872,1094,1096],{"class":1095},"sl46w","const",[872,1098,1100],{"class":1099},"s3JHE"," cache",[872,1102,1103],{"class":896}," =",[872,1105,1107],{"class":1106},"sHOzp"," tokenCache",[872,1109,1110],{"class":878},"()\n",[872,1112,1113,1116,1119,1122,1125,1128,1131],{"class":874,"line":938},[872,1114,1115],{"class":1069},"cache",[872,1117,1118],{"class":878},".",[872,1120,1121],{"class":1106},"delete",[872,1123,1124],{"class":878},"(",[872,1126,1127],{"class":1069},"rawToken",[872,1129,1130],{"class":878},") ",[872,1132,1134],{"class":1133},"sghk6","\u002F\u002F This token is now invalid. No database write needed.\n",[815,1136,1137],{},"The two-gate verification model — cache check first, cryptographic check second — also means the cryptographic work only happens when the cache says the token could be valid. Revoked tokens fail at the first gate, before any cryptographic computation runs.",[822,1139],{},[825,1141,1143],{"id":1142},"why-hashed-refresh-tokens-in-the-database","Why Hashed Refresh Tokens in the Database",[815,1145,1146,1147,1149],{},"Long-lived tokens stored in plaintext are a liability. If the database is compromised, every session is compromised. Hashing the token before storing it breaks that link. An attacker with a dump of the ",[841,1148,861],{}," table gets SHA-256 hashes — not the raw tokens they need to authenticate.",[815,1151,1152],{},"The storage schema for a refresh token row looks like this:",[1154,1155,1156,1169],"table",{},[1157,1158,1159],"thead",{},[1160,1161,1162,1166],"tr",{},[1163,1164,1165],"th",{},"Column",[1163,1167,1168],{},"Value",[1170,1171,1172,1186,1203,1218,1228],"tbody",{},[1160,1173,1174,1180],{},[1175,1176,1177],"td",{},[841,1178,1179],{},"token",[1175,1181,1182,1185],{},[841,1183,1184],{},"sha256(rawToken)"," — never the raw value",[1160,1187,1188,1193],{},[1175,1189,1190],{},[841,1191,1192],{},"valid",[1175,1194,1195,1198,1199,1202],{},[841,1196,1197],{},"1"," when active, ",[841,1200,1201],{},"0"," when revoked",[1160,1204,1205,1210],{},[1175,1206,1207],{},[841,1208,1209],{},"usage_count",[1175,1211,1212,1214,1215,1217],{},[841,1213,1201],{}," when fresh, ",[841,1216,1197],{}," after first consumption",[1160,1219,1220,1225],{},[1175,1221,1222],{},[841,1223,1224],{},"session_started_at",[1175,1226,1227],{},"Timestamp from the original login, carried across all rotations",[1160,1229,1230,1235],{},[1175,1231,1232],{},[841,1233,1234],{},"expiresAt",[1175,1236,1237,1238,1241],{},"Computed from ",[841,1239,1240],{},"refresh_ttl"," at insert time",[815,1243,834,1244,1246,1247,1249,1250,1253],{},[841,1245,1209],{}," column is the core of the reuse detection system. It starts at zero. The moment the token is consumed — used to issue a new token pair — the database atomically sets it to ",[841,1248,1197],{},". Any second attempt to consume a token with ",[841,1251,1252],{},"usage_count > 0"," is treated as a replay attack, and all sessions for that user are immediately revoked.",[815,1255,834,1256,1258,1259,1262],{},[841,1257,1224],{}," column persists the original login timestamp across every rotation. No matter how many times the token is rotated, the session chain traces back to the original authentication event. This is how ",[841,1260,1261],{},"MAX_SESSION_LIFE"," works: the system knows when the session began and can enforce an absolute ceiling on how long any session can live, regardless of how often it is refreshed.",[822,1264],{},[825,1266,1268],{"id":1267},"the-rotation-lifecycle","The Rotation Lifecycle",[815,1270,1271],{},"Rotation is the process that converts old credentials into new ones. It is the most security-sensitive operation in the system, and it runs in a strict sequence.",[815,1273,1274,1275,1278,1279,1281,1282,1284],{},"When the access token is about to expire, Auth H3 Client calls ",[841,1276,1277],{},"POST \u002Fauth\u002Fuser\u002Frefresh-session"," on the IAM service with the ",[841,1280,857],{}," and ",[841,1283,1031],{}," cookies. The IAM rotation controller runs this sequence:",[1286,1287,1288,1293,1300,1304,1331,1335,1356,1362,1366,1379,1383],"steps",{},[1289,1290,1292],"h3",{"id":1291},"rate-limiting","Rate limiting",[815,1294,1295,1296,1299],{},"Three layered rate limiters run first: an IP limiter, a token-hash limiter, and a composite ",[841,1297,1298],{},"ip_tokenhash"," limiter. Each uses consecutive caches that escalate block duration on repeated violations. Brute force attempts are stopped before anything else runs.",[1289,1301,1303],{"id":1302},"anomaly-detection","Anomaly detection",[815,1305,1306,1309,1310,1312,1313,1316,1317,1320,1321,1323,1324,1327,1328,1118],{},[841,1307,1308],{},"strangeThings()"," runs nine sequential checks against the session. It verifies the ",[841,1311,1031],{}," binding, checks IP range consistency against historical records, compares the ",[841,1314,1315],{},"User-Agent"," fingerprint, validates that the session has not exceeded ",[841,1318,1319],{},"maxAllowedSessionsPerUser",", and checks that the token has not already been consumed (",[841,1322,1252],{},"). The first check that fails short-circuits the rest. If anomalies are recoverable, the service sends an MFA email and returns ",[841,1325,1326],{},"202",". If they are not recoverable, the token is revoked and the service returns ",[841,1329,1330],{},"401",[1289,1332,1334],{"id":1333},"atomic-consumption","Atomic consumption",[815,1336,1337,1340,1341,1344,1345,1347,1348,1351,1352,1355],{},[841,1338,1339],{},"consumeAndVerifyRefreshToken"," runs a single atomic ",[841,1342,1343],{},"UPDATE"," inside a transaction. It increments ",[841,1346,1209],{}," by one, but only if the row exists, is ",[841,1349,1350],{},"valid = 1",", has ",[841,1353,1354],{},"usage_count = 0",", and has not expired. All four conditions must pass in the same transaction. If even one fails, no rows are affected.",[815,1357,1358,1359,1361],{},"If no rows are affected, the function investigates: the token might not exist, it might have been revoked, or it might have ",[841,1360,1252],{}," from a previous consumption. That last case is a reuse detection trigger — all sessions for the user are revoked immediately.",[1289,1363,1365],{"id":1364},"session-lifetime-check","Session lifetime check",[815,1367,1368,1369,1371,1372,1374,1375,1378],{},"If the token consumed successfully but ",[841,1370,1224],{}," is older than ",[841,1373,1261],{},", the controller revokes the token and returns ",[841,1376,1377],{},"401 Session is expired",". The session chain has lived as long as policy allows.",[1289,1380,1382],{"id":1381},"new-credential-issuance","New credential issuance",[815,1384,1385,1386,1389,1390,1392,1393,1395,1396,1398,1399,1401],{},"The old token is set to ",[841,1387,1388],{},"valid = 0",". A new 64-byte random refresh token is generated, hashed, and inserted with ",[841,1391,1350],{},", ",[841,1394,1354],{},", and the same ",[841,1397,1224],{}," from the consumed token. A new access token is signed with a fresh ",[841,1400,964],{}," and cached. Both are sent to the browser.",[815,1403,1404,1405,1408],{},"The success response carries the new access token in the body. The new refresh token arrives in the ",[841,1406,1407],{},"Set-Cookie"," header. The browser replaces its cookies transparently.",[864,1410,1412],{"className":866,"code":1411,"language":5,"meta":868,"style":868},"{\n  \"message\": \"Refresh & access tokens rotated\",\n  \"accessToken\": \"\u003Csigned jwt>\",\n  \"accessIat\": \"1710000000000\"\n}\n",[841,1413,1414,1418,1438,1458,1477],{"__ignoreMap":868},[872,1415,1416],{"class":874,"line":875},[872,1417,879],{"class":878},[872,1419,1420,1422,1425,1427,1429,1431,1434,1436],{"class":874,"line":882},[872,1421,886],{"class":885},[872,1423,1424],{"class":889},"message",[872,1426,893],{"class":885},[872,1428,897],{"class":896},[872,1430,901],{"class":900},[872,1432,1433],{"class":904},"Refresh & access tokens rotated",[872,1435,893],{"class":900},[872,1437,910],{"class":878},[872,1439,1440,1442,1445,1447,1449,1451,1454,1456],{"class":874,"line":913},[872,1441,886],{"class":885},[872,1443,1444],{"class":889},"accessToken",[872,1446,893],{"class":885},[872,1448,897],{"class":896},[872,1450,901],{"class":900},[872,1452,1453],{"class":904},"\u003Csigned jwt>",[872,1455,893],{"class":900},[872,1457,910],{"class":878},[872,1459,1460,1462,1465,1467,1469,1471,1474],{"class":874,"line":938},[872,1461,886],{"class":885},[872,1463,1464],{"class":889},"accessIat",[872,1466,893],{"class":885},[872,1468,897],{"class":896},[872,1470,901],{"class":900},[872,1472,1473],{"class":904},"1710000000000",[872,1475,1476],{"class":900},"\"\n",[872,1478,1479],{"class":874,"line":959},[872,1480,1016],{"class":878},[822,1482],{},[825,1484,1486],{"id":1485},"the-deduplication-problem","The Deduplication Problem",[815,1488,1489],{},"Single-page applications create a problem that most token rotation systems ignore: concurrent requests.",[815,1491,1492],{},"Consider a user whose access token has just expired. Their browser has three in-flight requests — a profile fetch, a feed load, and a notification count. All three arrive at the server at the same moment. All three see an expired access token. All three decide to rotate.",[815,1494,1495,1496,1498,1499,1502,1503,1505],{},"Without deduplication, all three would call ",[841,1497,1277],{}," simultaneously. The first call consumes the refresh token (sets ",[841,1500,1501],{},"usage_count = 1","). The second call tries to consume the same token and finds ",[841,1504,1252],{},". The reuse detection system interprets this as a replay attack and revokes all sessions. The user is logged out, and they did nothing wrong.",[815,1507,1508],{},"This is not a theoretical edge case. It happens on any page with multiple parallel API calls, and it happens reliably whenever token expiry falls at a high-traffic moment.",[815,1510,1511,1512,1515,1516,1519,1520,1522],{},"Auth H3 Client solves this with ",[841,1513,1514],{},"lockAsyncAction",", a keyed async mutex. When ",[841,1517,1518],{},"ensureValidCredentials"," needs to rotate, it acquires a lock keyed on the refresh token value — the ",[841,1521,857],{}," cookie — before making any call to the IAM service. A second request for the same session finds the lock held, waits for the first call to complete, and reuses its result.",[864,1524,1526],{"className":1053,"code":1525,"language":1055,"meta":868,"style":868},"\u002F\u002F Inside ensureValidCredentials — simplified view\nconst result = await lockAsyncAction(refreshToken, async () => {\n  \u002F\u002F Only one call per session cookie value runs at a time.\n  \u002F\u002F All others wait here and get the same result.\n  return await callIAMRotation(sessionCookie, canaryCookie)\n})\n",[841,1527,1528,1533,1567,1572,1577,1600],{"__ignoreMap":868},[872,1529,1530],{"class":874,"line":875},[872,1531,1532],{"class":1133},"\u002F\u002F Inside ensureValidCredentials — simplified view\n",[872,1534,1535,1537,1540,1542,1545,1548,1550,1553,1555,1558,1561,1564],{"class":874,"line":882},[872,1536,1096],{"class":1095},[872,1538,1539],{"class":1099}," result",[872,1541,1103],{"class":896},[872,1543,1544],{"class":1062}," await",[872,1546,1547],{"class":1106}," lockAsyncAction",[872,1549,1124],{"class":878},[872,1551,1552],{"class":1069},"refreshToken",[872,1554,1392],{"class":878},[872,1556,1557],{"class":1095},"async",[872,1559,1560],{"class":878}," () ",[872,1562,1563],{"class":1095},"=>",[872,1565,1566],{"class":878}," {\n",[872,1568,1569],{"class":874,"line":913},[872,1570,1571],{"class":1133},"  \u002F\u002F Only one call per session cookie value runs at a time.\n",[872,1573,1574],{"class":874,"line":938},[872,1575,1576],{"class":1133},"  \u002F\u002F All others wait here and get the same result.\n",[872,1578,1579,1582,1584,1587,1589,1592,1594,1597],{"class":874,"line":959},[872,1580,1581],{"class":1062},"  return",[872,1583,1544],{"class":1062},[872,1585,1586],{"class":1106}," callIAMRotation",[872,1588,1124],{"class":878},[872,1590,1591],{"class":1069},"sessionCookie",[872,1593,1392],{"class":878},[872,1595,1596],{"class":1069},"canaryCookie",[872,1598,1599],{"class":878},")\n",[872,1601,1602],{"class":874,"line":980},[872,1603,1604],{"class":878},"})\n",[815,1606,1607],{},"The lock is keyed on the refresh token value itself, not on a user ID or session ID. This matters because a user might have multiple active sessions across devices. Each session has its own refresh token, so each gets its own independent lock. Concurrent rotation on one device does not block rotation on another.",[815,1609,1610],{},"The result is cached briefly after the lock releases. Requests that arrive after the first call completes but before the lock is fully released still get the cached result without making another call. This covers the common case where a burst of requests resolves in quick succession rather than simultaneously.",[822,1612],{},[825,1614,1616],{"id":1615},"reuse-detection-in-depth","Reuse Detection in Depth",[815,1618,1619],{},"The reuse detection system operates on a core assumption: a refresh token should only ever be consumed once. Any second consumption means either the token was stolen and replayed, or something in the rotation flow went wrong. Either way, the safest response is to terminate all sessions for that user immediately.",[815,1621,1622],{},[836,1623,1624],{},"Scenario 1: An attacker steals a valid, unconsumed refresh token.",[815,1626,1627,1628,1630,1631,1634],{},"To use the stolen token, the attacker must also replicate the user's ",[841,1629,1031],{}," cookie and pass the fingerprint checks in ",[841,1632,1633],{},"strangeThings",". If the fingerprint does not match, the anomaly engine sends an MFA challenge to the real user's email before any rotation happens. The attacker cannot proceed without access to the user's email.",[815,1636,1637,1638,1640,1641,1643,1644,1646,1647,1649],{},"If the attacker somehow passes the fingerprint checks and consumes the token, ",[841,1639,1209],{}," becomes ",[841,1642,1197],{},". The next time the legitimate user's browser tries to rotate — which happens automatically as the access token approaches expiry — ",[841,1645,1339],{}," finds ",[841,1648,1252],{},". All sessions are revoked. Both the attacker and the legitimate user are forced to re-authenticate. The attacker cannot complete MFA without the user's email.",[815,1651,1652],{},[836,1653,1654],{},"Scenario 2: An attacker steals a token that has already been rotated.",[815,1656,1657,1658,1660,1661,1663,1664,1666,1667,1670],{},"The stolen token has ",[841,1659,1501],{},". The attacker attempts to consume it. ",[841,1662,1339],{}," detects ",[841,1665,1252],{}," immediately, revokes all sessions for the user, and returns ",[841,1668,1669],{},"valid: false",". The attacker's attempt terminates the legitimate user's current session, but the attacker gains nothing — they still cannot authenticate.",[815,1672,1673],{},"In both scenarios, the worst outcome for the legitimate user is being forced to log in again and prove their identity through MFA. The attacker is locked out at every step.",[822,1675],{},[825,1677,1679],{"id":1678},"two-lifetime-controls","Two Lifetime Controls",[815,1681,1682],{},"Refresh tokens have two independent lifetime mechanisms, and understanding the difference between them matters.",[815,1684,1685,1691,1692,1694,1695,1698,1699,1701,1702,1705],{},[836,1686,1687,1688,1690],{},"Token TTL (",[841,1689,1240],{},")"," controls how long a single refresh token row stays valid in the database. When a token expires, verification sets ",[841,1693,1388],{}," and clears ",[841,1696,1697],{},"last_mfa_at"," for the user. Clearing ",[841,1700,1697],{}," resets the ",[841,1703,1704],{},"byPassAnomaliesFor"," cooldown — the next session anomaly (if one occurs) will not be bypassed, though a clean login from the same device still passes without MFA.",[815,1707,1708,1713,1714,1716,1717,1720,1721,1723],{},[836,1709,1710,1711,1690],{},"Session lifetime (",[841,1712,1261],{}," controls how long the entire session chain can survive. Every time a token is rotated, the new token inherits the same ",[841,1715,1224],{}," timestamp from the consumed token. That timestamp is the anchor. When ",[841,1718,1719],{},"Date.now() - session_started_at"," exceeds ",[841,1722,1261],{},", the rotation controller refuses to issue new credentials, even if the token itself has not expired yet.",[815,1725,1726,1727,1729,1730,1732],{},"The practical configuration is to set ",[841,1728,1240],{}," to something like 3 days and ",[841,1731,1261],{}," to 30 days. Individual tokens force periodic rotation and limit the exposure window of any single credential. The session ceiling prevents sessions from living indefinitely through continuous renewal.",[864,1734,1739],{"className":1735,"code":1737,"language":1738},[1736],"language-text","refresh_ttl:      3 days    — Each token lives this long\nMAX_SESSION_LIFE: 30 days   — The session chain lives this long\n","text",[841,1740,1737],{"__ignoreMap":868},[815,1742,1743],{},"A \"remember me\" flow with a 3-day token TTL still expires the session completely after 30 days. The user must log in again, not just refresh.",[822,1745],{},[825,1747,1749],{"id":1748},"how-auth-h3-client-drives-this","How Auth H3 Client Drives This",[815,1751,1752,1753,1756,1757,1759],{},"Auth H3 Client, the gateway layer for Nuxt and Nitro applications, handles the entire rotation lifecycle transparently. Your application code never calls the rotation endpoint directly. Instead, every protected route wraps its handler in ",[841,1754,1755],{},"defineAuthenticatedEventHandler",", which calls ",[841,1758,1518],{}," before your code runs.",[815,1761,1762,1764,1765,1768],{},[841,1763,1518],{}," decides whether to rotate based on the metadata it receives from the IAM ",[841,1766,1767],{},"\u002Fsecret\u002Faccesstoken\u002Fmetadata"," endpoint. The decision logic covers every case:",[1154,1770,1771,1781],{},[1157,1772,1773],{},[1160,1774,1775,1778],{},[1163,1776,1777],{},"Metadata result",[1163,1779,1780],{},"Action",[1170,1782,1783,1791,1802,1812,1819,1830],{},[1160,1784,1785,1788],{},[1175,1786,1787],{},"No access token present",[1175,1789,1790],{},"Rotate immediately",[1160,1792,1793,1799],{},[1175,1794,1795,1798],{},[841,1796,1797],{},"shouldRotate: true"," (within 25% of TTL)",[1175,1800,1801],{},"Rotate proactively",[1160,1803,1804,1809],{},[1175,1805,1806],{},[841,1807,1808],{},"authorized: false",[1175,1810,1811],{},"Rotate",[1160,1813,1814,1817],{},[1175,1815,1816],{},"Server error or no response",[1175,1818,1811],{},[1160,1820,1821,1827],{},[1175,1822,1823,1826],{},[841,1824,1825],{},"mfa: true"," (IAM returned 202)",[1175,1828,1829],{},"Return 202, do not rotate",[1160,1831,1832,1835],{},[1175,1833,1834],{},"Valid and within threshold",[1175,1836,1837],{},"Set token on context, continue",[815,1839,1840,1841,1843,1844,1847],{},"The metadata response is cached in a ",[841,1842,714],{}," instance keyed by the access token value. The cache TTL is ",[841,1845,1846],{},"msUntilExp - refreshThreshold - 5 seconds",", so the cache expires just before the token would trigger a rotation check anyway. Requests within that window read the cached metadata without a network call.",[864,1849,1851],{"className":1053,"code":1850,"language":1055,"meta":868,"style":868},"export default defineAuthenticatedEventHandler(async (event) => {\n  \u002F\u002F By the time this line runs:\n  \u002F\u002F - The access token has been verified or rotated\n  \u002F\u002F - New cookies have been applied to the response if rotation happened\n  \u002F\u002F - Concurrent requests from the same session were deduplicated\n  \u002F\u002F - event.context.authorizedData contains the verified session data\n  const { userId, roles } = event.context.authorizedData\n  return { userId }\n})\n",[841,1852,1853,1881,1886,1891,1896,1901,1906,1938,1949],{"__ignoreMap":868},[872,1854,1855,1858,1861,1864,1866,1868,1871,1875,1877,1879],{"class":874,"line":875},[872,1856,1857],{"class":1062},"export",[872,1859,1860],{"class":1062}," default",[872,1862,1863],{"class":1106}," defineAuthenticatedEventHandler",[872,1865,1124],{"class":878},[872,1867,1557],{"class":1095},[872,1869,1870],{"class":878}," (",[872,1872,1874],{"class":1873},"sygFZ","event",[872,1876,1130],{"class":878},[872,1878,1563],{"class":1095},[872,1880,1566],{"class":878},[872,1882,1883],{"class":874,"line":882},[872,1884,1885],{"class":1133},"  \u002F\u002F By the time this line runs:\n",[872,1887,1888],{"class":874,"line":913},[872,1889,1890],{"class":1133},"  \u002F\u002F - The access token has been verified or rotated\n",[872,1892,1893],{"class":874,"line":938},[872,1894,1895],{"class":1133},"  \u002F\u002F - New cookies have been applied to the response if rotation happened\n",[872,1897,1898],{"class":874,"line":959},[872,1899,1900],{"class":1133},"  \u002F\u002F - Concurrent requests from the same session were deduplicated\n",[872,1902,1903],{"class":874,"line":980},[872,1904,1905],{"class":1133},"  \u002F\u002F - event.context.authorizedData contains the verified session data\n",[872,1907,1908,1911,1913,1916,1918,1920,1922,1925,1928,1930,1933,1935],{"class":874,"line":998},[872,1909,1910],{"class":1095},"  const",[872,1912,1066],{"class":878},[872,1914,1915],{"class":1099},"userId",[872,1917,1392],{"class":878},[872,1919,918],{"class":1099},[872,1921,1073],{"class":878},[872,1923,1924],{"class":896},"=",[872,1926,1927],{"class":1069}," event",[872,1929,1118],{"class":878},[872,1931,1932],{"class":1069},"context",[872,1934,1118],{"class":878},[872,1936,1937],{"class":1069},"authorizedData\n",[872,1939,1940,1942,1944,1946],{"class":874,"line":1013},[872,1941,1581],{"class":1062},[872,1943,1066],{"class":878},[872,1945,1915],{"class":1069},[872,1947,1948],{"class":878}," }\n",[872,1950,1952],{"class":874,"line":1951},9,[872,1953,1604],{"class":878},[815,1955,1956],{},"The deduplication lock, the metadata cache, and the rotation decision all happen before the first line of your handler. From your handler's perspective, the session is always valid when it arrives.",[822,1958],{},[825,1960,1962],{"id":1961},"summary","Summary",[815,1964,1965],{},"The dual-token architecture exists because no single credential can satisfy both the performance requirement (fast verification, no database query on every request) and the security requirement (cheap revocation, short exposure windows).",[815,1967,1968],{},"Access tokens satisfy the performance requirement. They are verified in memory with a cache lookup and a signature check. Revoking one is a cache delete. The database is never involved.",[815,1970,1971,1972,1974],{},"Refresh tokens satisfy the security requirement. They are stored as hashes, consumed atomically, and protected by a reuse detection system that terminates all sessions at the first sign of replay. Their long TTL makes them practical for real users while ",[841,1973,1261],{}," ensures sessions cannot live indefinitely.",[815,1976,1977],{},"The deduplication layer sits between the two, preventing the concurrent-rotation problem that makes dual-token systems brittle in practice.",[815,1979,1980,1981,1985],{},"Read the full ",[1982,1983,1984],"a",{"href":367},"token reference"," for the IAM service",[815,1987,1988,1989,1991],{},"Read how ",[1982,1990,20],{"href":103}," manages session state and drives token rotation",[1993,1994,1995],"style",{},"html pre.shiki code .sDd4n, html code.shiki .sDd4n{--shiki-light:#000000;--shiki-default:#000000;--shiki-dark:#F8F8F2}html pre.shiki code .saJyd, html code.shiki .saJyd{--shiki-light:#0451A5;--shiki-default:#0451A5;--shiki-dark:#8BE9FE}html pre.shiki code .s_W10, html code.shiki .s_W10{--shiki-light:#0451A5;--shiki-default:#0451A5;--shiki-dark:#8BE9FD}html pre.shiki code .saOXh, html code.shiki .saOXh{--shiki-light:#000000;--shiki-default:#000000;--shiki-dark:#FF79C6}html pre.shiki code .sFkSl, html code.shiki .sFkSl{--shiki-light:#A31515;--shiki-default:#A31515;--shiki-dark:#E9F284}html pre.shiki code .sFB1V, html code.shiki .sFB1V{--shiki-light:#A31515;--shiki-default:#A31515;--shiki-dark:#F1FA8C}html pre.shiki code .spgvN, html code.shiki .spgvN{--shiki-light:#098658;--shiki-default:#098658;--shiki-dark:#BD93F9}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZ328, html code.shiki .sZ328{--shiki-light:#AF00DB;--shiki-default:#AF00DB;--shiki-dark:#FF79C6}html pre.shiki code .sjsA6, html code.shiki .sjsA6{--shiki-light:#001080;--shiki-default:#001080;--shiki-dark:#F8F8F2}html pre.shiki code .sl46w, html code.shiki .sl46w{--shiki-light:#0000FF;--shiki-default:#0000FF;--shiki-dark:#FF79C6}html pre.shiki code .s3JHE, html code.shiki .s3JHE{--shiki-light:#0070C1;--shiki-default:#0070C1;--shiki-dark:#F8F8F2}html pre.shiki code .sHOzp, html code.shiki .sHOzp{--shiki-light:#795E26;--shiki-default:#795E26;--shiki-dark:#50FA7B}html pre.shiki code .sghk6, html code.shiki .sghk6{--shiki-light:#008000;--shiki-default:#008000;--shiki-dark:#6272A4}html pre.shiki code .sygFZ, html code.shiki .sygFZ{--shiki-light:#001080;--shiki-light-font-style:inherit;--shiki-default:#001080;--shiki-default-font-style:inherit;--shiki-dark:#FFB86C;--shiki-dark-font-style:italic}",{"title":868,"searchDepth":882,"depth":882,"links":1997},[1998,1999,2000,2001,2008,2009,2010,2011,2012],{"id":827,"depth":882,"text":828},{"id":1037,"depth":882,"text":1038},{"id":1142,"depth":882,"text":1143},{"id":1267,"depth":882,"text":1268,"children":2002},[2003,2004,2005,2006,2007],{"id":1291,"depth":913,"text":1292},{"id":1302,"depth":913,"text":1303},{"id":1333,"depth":913,"text":1334},{"id":1364,"depth":913,"text":1365},{"id":1381,"depth":913,"text":1382},{"id":1485,"depth":882,"text":1486},{"id":1615,"depth":882,"text":1616},{"id":1678,"depth":882,"text":1679},{"id":1748,"depth":882,"text":1749},{"id":1961,"depth":882,"text":1962},"2026-04-13","A deep dive into the dual-token lifecycle, why short-lived access tokens paired with hashed refresh tokens are safer than sessions, and how concurrent rotation requests are coalesced.","md","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1614064641938-3bbee52942c7?w=1200&q=80",{},"\u002Fblog\u002Fhow-token-rotation-works","---\ntitle: \"How Token Rotation Works: Access Tokens, Refresh Tokens, and the Deduplication Problem\"\ndescription: \"A deep dive into the dual-token lifecycle, why short-lived access tokens paired with hashed refresh tokens are safer than sessions, and how concurrent rotation requests are coalesced.\"\nnavigation: false\ntags:\n    - Tokens\n    - Security\nimage: \"https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1614064641938-3bbee52942c7?w=1200&q=80\"\nauthor: \"Sergio\"\nauthorImg: \"https:\u002F\u002Fgithub.com\u002FSergo706.png\"\nauthorGithub: \"https:\u002F\u002Fgithub.com\u002FSergo706\"\nauthorGithubUserName: \"Sergo706\"\nfeatured: false\ndate: 2026-04-13T10:00:00.000Z\nreadingTime: \"10 min read\"\n---\n\nMost authentication systems issue a single credential — a session ID, a JWT, a cookie — and use it until it expires or the user logs out. The problem with that model is straightforward: if an attacker obtains that credential, they have as long as it lives to use it. The longer it lives, the bigger the exposure window.\n\nRiavzon solves this with a dual-token architecture. Access tokens are short-lived and verified cryptographically. Refresh tokens are long-lived but stored as hashes in a database, consumed atomically, and wrapped in a reuse detection system that revokes every session the moment replay is detected. This post explains every layer of that architecture: why it is designed this way, how each piece works, and what happens when two requests from the same user arrive at the same time.\n\n---\n\n## The Two-Token Model\n\nEvery authenticated user in the system holds two credentials at once.\n\nThe **access token** is a signed JWT. It lives in a `__Secure-a` cookie on the browser. Its lifetime is short — typically 15 minutes — and it is verified on every request without touching the database. The IAM service uses an LRU cache to hold every valid token, so verification is a cache lookup plus a cryptographic check, not a database query. When the token expires, the cache entry is evicted and the next verification call fails immediately.\n\nThe **refresh token** is a 64-byte cryptographically random string, hex encoded. The browser holds the raw token in an `httpOnly` cookie named `session`. The server never stores the raw token. Instead, it hashes it with SHA-256 and stores the hash in a MySQL `refresh_tokens` table. The raw token leaves the server exactly once, when it is issued, and the server never sees it again in plaintext.\n\n```json\n{\n  \"visitor\": \"vis_abc123\",\n  \"roles\": [\"user\"],\n  \"sub\": \"42\",\n  \"jti\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"iat\": 1710000000,\n  \"exp\": 1710000900\n}\n```\n\nThat is a typical access token payload. The `jti` is a UUID generated fresh on every issuance. It is also the key by which the token lives in the LRU cache. Deleting the cache entry for a `jti` revokes that token immediately, without a database write, without waiting for expiry.\n\nThe canary cookie — `canary_id` — ties the session to a specific device fingerprint. It is issued by the Bot Detector middleware and is required alongside both tokens for any sensitive operation. It is neither a credential nor an authentication factor on its own, but it binds the token family to the visitor context that created it, and any mismatch triggers anomaly detection.\n\n---\n\n## Why Short-Lived Access Tokens\n\nThe conventional objection to short-lived tokens is the extra network round trips. If the token expires every 15 minutes, the user's browser needs to refresh it every 15 minutes. That cost is real, but the security benefit justifies it.\n\nAn access token that lives for 15 minutes and is stolen gives an attacker a 15-minute window. An access token that lives for 24 hours gives an attacker 24 hours. In practice, the difference between these windows matters enormously when you consider how often stolen credentials go undetected. The 15-minute window usually closes before the attacker can do meaningful damage. The 24-hour window rarely does.\n\nMore importantly, the LRU cache is the real enforcement boundary. An access token is not just valid because it carries the right signature. It is valid because it exists in the cache. This means revocation is instant and free. Deleting the cache entry with the token's `jti` terminates that token immediately, regardless of how long it has until expiry. Sessions can be force-terminated without a database write, without blocking, and without any propagation delay.\n\n```ts\nimport { tokenCache } from '@riavzon\u002Fauth'\n\nconst cache = tokenCache()\ncache.delete(rawToken) \u002F\u002F This token is now invalid. No database write needed.\n```\n\nThe two-gate verification model — cache check first, cryptographic check second — also means the cryptographic work only happens when the cache says the token could be valid. Revoked tokens fail at the first gate, before any cryptographic computation runs.\n\n---\n\n## Why Hashed Refresh Tokens in the Database\n\nLong-lived tokens stored in plaintext are a liability. If the database is compromised, every session is compromised. Hashing the token before storing it breaks that link. An attacker with a dump of the `refresh_tokens` table gets SHA-256 hashes — not the raw tokens they need to authenticate.\n\nThe storage schema for a refresh token row looks like this:\n\n| Column | Value |\n|---|---|\n| `token` | `sha256(rawToken)` — never the raw value |\n| `valid` | `1` when active, `0` when revoked |\n| `usage_count` | `0` when fresh, `1` after first consumption |\n| `session_started_at` | Timestamp from the original login, carried across all rotations |\n| `expiresAt` | Computed from `refresh_ttl` at insert time |\n\nThe `usage_count` column is the core of the reuse detection system. It starts at zero. The moment the token is consumed — used to issue a new token pair — the database atomically sets it to `1`. Any second attempt to consume a token with `usage_count > 0` is treated as a replay attack, and all sessions for that user are immediately revoked.\n\nThe `session_started_at` column persists the original login timestamp across every rotation. No matter how many times the token is rotated, the session chain traces back to the original authentication event. This is how `MAX_SESSION_LIFE` works: the system knows when the session began and can enforce an absolute ceiling on how long any session can live, regardless of how often it is refreshed.\n\n---\n\n## The Rotation Lifecycle\n\nRotation is the process that converts old credentials into new ones. It is the most security-sensitive operation in the system, and it runs in a strict sequence.\n\nWhen the access token is about to expire, Auth H3 Client calls `POST \u002Fauth\u002Fuser\u002Frefresh-session` on the IAM service with the `session` and `canary_id` cookies. The IAM rotation controller runs this sequence:\n\n::steps\n\n### Rate limiting\n\nThree layered rate limiters run first: an IP limiter, a token-hash limiter, and a composite `ip_tokenhash` limiter. Each uses consecutive caches that escalate block duration on repeated violations. Brute force attempts are stopped before anything else runs.\n\n### Anomaly detection\n\n`strangeThings()` runs nine sequential checks against the session. It verifies the `canary_id` binding, checks IP range consistency against historical records, compares the `User-Agent` fingerprint, validates that the session has not exceeded `maxAllowedSessionsPerUser`, and checks that the token has not already been consumed (`usage_count > 0`). The first check that fails short-circuits the rest. If anomalies are recoverable, the service sends an MFA email and returns `202`. If they are not recoverable, the token is revoked and the service returns `401`.\n\n### Atomic consumption\n\n`consumeAndVerifyRefreshToken` runs a single atomic `UPDATE` inside a transaction. It increments `usage_count` by one, but only if the row exists, is `valid = 1`, has `usage_count = 0`, and has not expired. All four conditions must pass in the same transaction. If even one fails, no rows are affected.\n\nIf no rows are affected, the function investigates: the token might not exist, it might have been revoked, or it might have `usage_count > 0` from a previous consumption. That last case is a reuse detection trigger — all sessions for the user are revoked immediately.\n\n### Session lifetime check\n\nIf the token consumed successfully but `session_started_at` is older than `MAX_SESSION_LIFE`, the controller revokes the token and returns `401 Session is expired`. The session chain has lived as long as policy allows.\n\n### New credential issuance\n\nThe old token is set to `valid = 0`. A new 64-byte random refresh token is generated, hashed, and inserted with `valid = 1`, `usage_count = 0`, and the same `session_started_at` from the consumed token. A new access token is signed with a fresh `jti` and cached. Both are sent to the browser.\n\n::\n\nThe success response carries the new access token in the body. The new refresh token arrives in the `Set-Cookie` header. The browser replaces its cookies transparently.\n\n```json\n{\n  \"message\": \"Refresh & access tokens rotated\",\n  \"accessToken\": \"\u003Csigned jwt>\",\n  \"accessIat\": \"1710000000000\"\n}\n```\n\n---\n\n## The Deduplication Problem\n\nSingle-page applications create a problem that most token rotation systems ignore: concurrent requests.\n\nConsider a user whose access token has just expired. Their browser has three in-flight requests — a profile fetch, a feed load, and a notification count. All three arrive at the server at the same moment. All three see an expired access token. All three decide to rotate.\n\nWithout deduplication, all three would call `POST \u002Fauth\u002Fuser\u002Frefresh-session` simultaneously. The first call consumes the refresh token (sets `usage_count = 1`). The second call tries to consume the same token and finds `usage_count > 0`. The reuse detection system interprets this as a replay attack and revokes all sessions. The user is logged out, and they did nothing wrong.\n\nThis is not a theoretical edge case. It happens on any page with multiple parallel API calls, and it happens reliably whenever token expiry falls at a high-traffic moment.\n\nAuth H3 Client solves this with `lockAsyncAction`, a keyed async mutex. When `ensureValidCredentials` needs to rotate, it acquires a lock keyed on the refresh token value — the `session` cookie — before making any call to the IAM service. A second request for the same session finds the lock held, waits for the first call to complete, and reuses its result.\n\n```ts\n\u002F\u002F Inside ensureValidCredentials — simplified view\nconst result = await lockAsyncAction(refreshToken, async () => {\n  \u002F\u002F Only one call per session cookie value runs at a time.\n  \u002F\u002F All others wait here and get the same result.\n  return await callIAMRotation(sessionCookie, canaryCookie)\n})\n```\n\nThe lock is keyed on the refresh token value itself, not on a user ID or session ID. This matters because a user might have multiple active sessions across devices. Each session has its own refresh token, so each gets its own independent lock. Concurrent rotation on one device does not block rotation on another.\n\nThe result is cached briefly after the lock releases. Requests that arrive after the first call completes but before the lock is fully released still get the cached result without making another call. This covers the common case where a burst of requests resolves in quick succession rather than simultaneously.\n\n---\n\n## Reuse Detection in Depth\n\nThe reuse detection system operates on a core assumption: a refresh token should only ever be consumed once. Any second consumption means either the token was stolen and replayed, or something in the rotation flow went wrong. Either way, the safest response is to terminate all sessions for that user immediately.\n\n**Scenario 1: An attacker steals a valid, unconsumed refresh token.**\n\nTo use the stolen token, the attacker must also replicate the user's `canary_id` cookie and pass the fingerprint checks in `strangeThings`. If the fingerprint does not match, the anomaly engine sends an MFA challenge to the real user's email before any rotation happens. The attacker cannot proceed without access to the user's email.\n\nIf the attacker somehow passes the fingerprint checks and consumes the token, `usage_count` becomes `1`. The next time the legitimate user's browser tries to rotate — which happens automatically as the access token approaches expiry — `consumeAndVerifyRefreshToken` finds `usage_count > 0`. All sessions are revoked. Both the attacker and the legitimate user are forced to re-authenticate. The attacker cannot complete MFA without the user's email.\n\n**Scenario 2: An attacker steals a token that has already been rotated.**\n\nThe stolen token has `usage_count = 1`. The attacker attempts to consume it. `consumeAndVerifyRefreshToken` detects `usage_count > 0` immediately, revokes all sessions for the user, and returns `valid: false`. The attacker's attempt terminates the legitimate user's current session, but the attacker gains nothing — they still cannot authenticate.\n\nIn both scenarios, the worst outcome for the legitimate user is being forced to log in again and prove their identity through MFA. The attacker is locked out at every step.\n\n---\n\n## Two Lifetime Controls\n\nRefresh tokens have two independent lifetime mechanisms, and understanding the difference between them matters.\n\n**Token TTL (`refresh_ttl`)** controls how long a single refresh token row stays valid in the database. When a token expires, verification sets `valid = 0` and clears `last_mfa_at` for the user. Clearing `last_mfa_at` resets the `byPassAnomaliesFor` cooldown — the next session anomaly (if one occurs) will not be bypassed, though a clean login from the same device still passes without MFA.\n\n**Session lifetime (`MAX_SESSION_LIFE`)** controls how long the entire session chain can survive. Every time a token is rotated, the new token inherits the same `session_started_at` timestamp from the consumed token. That timestamp is the anchor. When `Date.now() - session_started_at` exceeds `MAX_SESSION_LIFE`, the rotation controller refuses to issue new credentials, even if the token itself has not expired yet.\n\nThe practical configuration is to set `refresh_ttl` to something like 3 days and `MAX_SESSION_LIFE` to 30 days. Individual tokens force periodic rotation and limit the exposure window of any single credential. The session ceiling prevents sessions from living indefinitely through continuous renewal.\n\n```\nrefresh_ttl:      3 days    — Each token lives this long\nMAX_SESSION_LIFE: 30 days   — The session chain lives this long\n```\n\nA \"remember me\" flow with a 3-day token TTL still expires the session completely after 30 days. The user must log in again, not just refresh.\n\n---\n\n## How Auth H3 Client Drives This\n\nAuth H3 Client, the gateway layer for Nuxt and Nitro applications, handles the entire rotation lifecycle transparently. Your application code never calls the rotation endpoint directly. Instead, every protected route wraps its handler in `defineAuthenticatedEventHandler`, which calls `ensureValidCredentials` before your code runs.\n\n`ensureValidCredentials` decides whether to rotate based on the metadata it receives from the IAM `\u002Fsecret\u002Faccesstoken\u002Fmetadata` endpoint. The decision logic covers every case:\n\n| Metadata result | Action |\n|---|---|\n| No access token present | Rotate immediately |\n| `shouldRotate: true` (within 25% of TTL) | Rotate proactively |\n| `authorized: false` | Rotate |\n| Server error or no response | Rotate |\n| `mfa: true` (IAM returned 202) | Return 202, do not rotate |\n| Valid and within threshold | Set token on context, continue |\n\nThe metadata response is cached in a `MiniCache` instance keyed by the access token value. The cache TTL is `msUntilExp - refreshThreshold - 5 seconds`, so the cache expires just before the token would trigger a rotation check anyway. Requests within that window read the cached metadata without a network call.\n\n```ts\nexport default defineAuthenticatedEventHandler(async (event) => {\n  \u002F\u002F By the time this line runs:\n  \u002F\u002F - The access token has been verified or rotated\n  \u002F\u002F - New cookies have been applied to the response if rotation happened\n  \u002F\u002F - Concurrent requests from the same session were deduplicated\n  \u002F\u002F - event.context.authorizedData contains the verified session data\n  const { userId, roles } = event.context.authorizedData\n  return { userId }\n})\n```\n\nThe deduplication lock, the metadata cache, and the rotation decision all happen before the first line of your handler. From your handler's perspective, the session is always valid when it arrives.\n\n---\n\n## Summary\n\nThe dual-token architecture exists because no single credential can satisfy both the performance requirement (fast verification, no database query on every request) and the security requirement (cheap revocation, short exposure windows).\n\nAccess tokens satisfy the performance requirement. They are verified in memory with a cache lookup and a signature check. Revoking one is a cache delete. The database is never involved.\n\nRefresh tokens satisfy the security requirement. They are stored as hashes, consumed atomically, and protected by a reuse detection system that terminates all sessions at the first sign of replay. Their long TTL makes them practical for real users while `MAX_SESSION_LIFE` ensures sessions cannot live indefinitely.\n\nThe deduplication layer sits between the two, preventing the concurrent-rotation problem that makes dual-token systems brittle in practice.\n\n\nRead the full [token reference](\u002Fdocs\u002Fiam\u002Fessentials\u002Ftokens) for the IAM service\n\n\nRead how [Auth H3 Client](\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fsession) manages session state and drives token rotation\n\n","10 min read",{"title":806,"description":2014},"blog\u002Fhow-token-rotation-works",[366,38],"GbsvB31LBRJ0U3cfO2xcPbmIspXuqJsH8lPWrRQ5pzc",{"id":805,"title":806,"author":807,"authorGithub":808,"authorGithubUserName":809,"authorImg":810,"body":2026,"date":2013,"description":2014,"extension":2015,"featured":53,"icon":72,"image":2016,"meta":2868,"navigation":53,"path":2018,"rawbody":2019,"readingTime":2020,"seo":2869,"stem":2022,"tags":2870,"__hash__":2024},{"type":812,"value":2027,"toc":2851},[2028,2030,2032,2034,2036,2038,2044,2054,2166,2172,2176,2178,2180,2182,2184,2188,2242,2244,2246,2248,2252,2254,2318,2326,2332,2334,2336,2338,2346,2414,2418,2482,2484,2486,2488,2490,2498,2500,2508,2572,2574,2576,2578,2580,2582,2586,2592,2602,2606,2616,2618,2620,2622,2624,2638,2650,2656,2661,2663,2665,2667,2673,2679,2733,2739,2825,2827,2829,2831,2833,2835,2839,2841,2845,2849],[815,2029,817],{},[815,2031,820],{},[822,2033],{},[825,2035,828],{"id":827},[815,2037,831],{},[815,2039,834,2040,839,2042,844],{},[836,2041,838],{},[841,2043,843],{},[815,2045,834,2046,850,2048,854,2050,858,2052,862],{},[836,2047,849],{},[841,2049,853],{},[841,2051,857],{},[841,2053,861],{},[864,2055,2056],{"className":866,"code":867,"language":5,"meta":868,"style":868},[841,2057,2058,2062,2080,2100,2118,2136,2150,2162],{"__ignoreMap":868},[872,2059,2060],{"class":874,"line":875},[872,2061,879],{"class":878},[872,2063,2064,2066,2068,2070,2072,2074,2076,2078],{"class":874,"line":882},[872,2065,886],{"class":885},[872,2067,890],{"class":889},[872,2069,893],{"class":885},[872,2071,897],{"class":896},[872,2073,901],{"class":900},[872,2075,905],{"class":904},[872,2077,893],{"class":900},[872,2079,910],{"class":878},[872,2081,2082,2084,2086,2088,2090,2092,2094,2096,2098],{"class":874,"line":913},[872,2083,886],{"class":885},[872,2085,918],{"class":889},[872,2087,893],{"class":885},[872,2089,897],{"class":896},[872,2091,925],{"class":878},[872,2093,893],{"class":900},[872,2095,930],{"class":904},[872,2097,893],{"class":900},[872,2099,935],{"class":878},[872,2101,2102,2104,2106,2108,2110,2112,2114,2116],{"class":874,"line":938},[872,2103,886],{"class":885},[872,2105,943],{"class":889},[872,2107,893],{"class":885},[872,2109,897],{"class":896},[872,2111,901],{"class":900},[872,2113,952],{"class":904},[872,2115,893],{"class":900},[872,2117,910],{"class":878},[872,2119,2120,2122,2124,2126,2128,2130,2132,2134],{"class":874,"line":959},[872,2121,886],{"class":885},[872,2123,964],{"class":889},[872,2125,893],{"class":885},[872,2127,897],{"class":896},[872,2129,901],{"class":900},[872,2131,973],{"class":904},[872,2133,893],{"class":900},[872,2135,910],{"class":878},[872,2137,2138,2140,2142,2144,2146,2148],{"class":874,"line":980},[872,2139,886],{"class":885},[872,2141,985],{"class":889},[872,2143,893],{"class":885},[872,2145,897],{"class":896},[872,2147,993],{"class":992},[872,2149,910],{"class":878},[872,2151,2152,2154,2156,2158,2160],{"class":874,"line":998},[872,2153,886],{"class":885},[872,2155,1003],{"class":889},[872,2157,893],{"class":885},[872,2159,897],{"class":896},[872,2161,1010],{"class":992},[872,2163,2164],{"class":874,"line":1013},[872,2165,1016],{"class":878},[815,2167,1019,2168,1022,2170,1025],{},[841,2169,964],{},[841,2171,964],{},[815,2173,1028,2174,1032],{},[841,2175,1031],{},[822,2177],{},[825,2179,1038],{"id":1037},[815,2181,1041],{},[815,2183,1044],{},[815,2185,1047,2186,1050],{},[841,2187,964],{},[864,2189,2190],{"className":1053,"code":1054,"language":1055,"meta":868,"style":868},[841,2191,2192,2210,2214,2226],{"__ignoreMap":868},[872,2193,2194,2196,2198,2200,2202,2204,2206,2208],{"class":874,"line":875},[872,2195,1063],{"class":1062},[872,2197,1066],{"class":878},[872,2199,1070],{"class":1069},[872,2201,1073],{"class":878},[872,2203,1076],{"class":1062},[872,2205,1079],{"class":900},[872,2207,1082],{"class":904},[872,2209,1085],{"class":900},[872,2211,2212],{"class":874,"line":882},[872,2213,1090],{"emptyLinePlaceholder":8},[872,2215,2216,2218,2220,2222,2224],{"class":874,"line":913},[872,2217,1096],{"class":1095},[872,2219,1100],{"class":1099},[872,2221,1103],{"class":896},[872,2223,1107],{"class":1106},[872,2225,1110],{"class":878},[872,2227,2228,2230,2232,2234,2236,2238,2240],{"class":874,"line":938},[872,2229,1115],{"class":1069},[872,2231,1118],{"class":878},[872,2233,1121],{"class":1106},[872,2235,1124],{"class":878},[872,2237,1127],{"class":1069},[872,2239,1130],{"class":878},[872,2241,1134],{"class":1133},[815,2243,1137],{},[822,2245],{},[825,2247,1143],{"id":1142},[815,2249,1146,2250,1149],{},[841,2251,861],{},[815,2253,1152],{},[1154,2255,2256,2264],{},[1157,2257,2258],{},[1160,2259,2260,2262],{},[1163,2261,1165],{},[1163,2263,1168],{},[1170,2265,2266,2276,2288,2300,2308],{},[1160,2267,2268,2272],{},[1175,2269,2270],{},[841,2271,1179],{},[1175,2273,2274,1185],{},[841,2275,1184],{},[1160,2277,2278,2282],{},[1175,2279,2280],{},[841,2281,1192],{},[1175,2283,2284,1198,2286,1202],{},[841,2285,1197],{},[841,2287,1201],{},[1160,2289,2290,2294],{},[1175,2291,2292],{},[841,2293,1209],{},[1175,2295,2296,1214,2298,1217],{},[841,2297,1201],{},[841,2299,1197],{},[1160,2301,2302,2306],{},[1175,2303,2304],{},[841,2305,1224],{},[1175,2307,1227],{},[1160,2309,2310,2314],{},[1175,2311,2312],{},[841,2313,1234],{},[1175,2315,1237,2316,1241],{},[841,2317,1240],{},[815,2319,834,2320,1246,2322,1249,2324,1253],{},[841,2321,1209],{},[841,2323,1197],{},[841,2325,1252],{},[815,2327,834,2328,1258,2330,1262],{},[841,2329,1224],{},[841,2331,1261],{},[822,2333],{},[825,2335,1268],{"id":1267},[815,2337,1271],{},[815,2339,1274,2340,1278,2342,1281,2344,1284],{},[841,2341,1277],{},[841,2343,857],{},[841,2345,1031],{},[1286,2347,2348,2350,2354,2356,2372,2374,2386,2390,2392,2400,2402],{},[1289,2349,1292],{"id":1291},[815,2351,1295,2352,1299],{},[841,2353,1298],{},[1289,2355,1303],{"id":1302},[815,2357,2358,1309,2360,1312,2362,1316,2364,1320,2366,1323,2368,1327,2370,1118],{},[841,2359,1308],{},[841,2361,1031],{},[841,2363,1315],{},[841,2365,1319],{},[841,2367,1252],{},[841,2369,1326],{},[841,2371,1330],{},[1289,2373,1334],{"id":1333},[815,2375,2376,1340,2378,1344,2380,1347,2382,1351,2384,1355],{},[841,2377,1339],{},[841,2379,1343],{},[841,2381,1209],{},[841,2383,1350],{},[841,2385,1354],{},[815,2387,1358,2388,1361],{},[841,2389,1252],{},[1289,2391,1365],{"id":1364},[815,2393,1368,2394,1371,2396,1374,2398,1378],{},[841,2395,1224],{},[841,2397,1261],{},[841,2399,1377],{},[1289,2401,1382],{"id":1381},[815,2403,1385,2404,1389,2406,1392,2408,1395,2410,1398,2412,1401],{},[841,2405,1388],{},[841,2407,1350],{},[841,2409,1354],{},[841,2411,1224],{},[841,2413,964],{},[815,2415,1404,2416,1408],{},[841,2417,1407],{},[864,2419,2420],{"className":866,"code":1411,"language":5,"meta":868,"style":868},[841,2421,2422,2426,2444,2462,2478],{"__ignoreMap":868},[872,2423,2424],{"class":874,"line":875},[872,2425,879],{"class":878},[872,2427,2428,2430,2432,2434,2436,2438,2440,2442],{"class":874,"line":882},[872,2429,886],{"class":885},[872,2431,1424],{"class":889},[872,2433,893],{"class":885},[872,2435,897],{"class":896},[872,2437,901],{"class":900},[872,2439,1433],{"class":904},[872,2441,893],{"class":900},[872,2443,910],{"class":878},[872,2445,2446,2448,2450,2452,2454,2456,2458,2460],{"class":874,"line":913},[872,2447,886],{"class":885},[872,2449,1444],{"class":889},[872,2451,893],{"class":885},[872,2453,897],{"class":896},[872,2455,901],{"class":900},[872,2457,1453],{"class":904},[872,2459,893],{"class":900},[872,2461,910],{"class":878},[872,2463,2464,2466,2468,2470,2472,2474,2476],{"class":874,"line":938},[872,2465,886],{"class":885},[872,2467,1464],{"class":889},[872,2469,893],{"class":885},[872,2471,897],{"class":896},[872,2473,901],{"class":900},[872,2475,1473],{"class":904},[872,2477,1476],{"class":900},[872,2479,2480],{"class":874,"line":959},[872,2481,1016],{"class":878},[822,2483],{},[825,2485,1486],{"id":1485},[815,2487,1489],{},[815,2489,1492],{},[815,2491,1495,2492,1498,2494,1502,2496,1505],{},[841,2493,1277],{},[841,2495,1501],{},[841,2497,1252],{},[815,2499,1508],{},[815,2501,1511,2502,1515,2504,1519,2506,1522],{},[841,2503,1514],{},[841,2505,1518],{},[841,2507,857],{},[864,2509,2510],{"className":1053,"code":1525,"language":1055,"meta":868,"style":868},[841,2511,2512,2516,2542,2546,2550,2568],{"__ignoreMap":868},[872,2513,2514],{"class":874,"line":875},[872,2515,1532],{"class":1133},[872,2517,2518,2520,2522,2524,2526,2528,2530,2532,2534,2536,2538,2540],{"class":874,"line":882},[872,2519,1096],{"class":1095},[872,2521,1539],{"class":1099},[872,2523,1103],{"class":896},[872,2525,1544],{"class":1062},[872,2527,1547],{"class":1106},[872,2529,1124],{"class":878},[872,2531,1552],{"class":1069},[872,2533,1392],{"class":878},[872,2535,1557],{"class":1095},[872,2537,1560],{"class":878},[872,2539,1563],{"class":1095},[872,2541,1566],{"class":878},[872,2543,2544],{"class":874,"line":913},[872,2545,1571],{"class":1133},[872,2547,2548],{"class":874,"line":938},[872,2549,1576],{"class":1133},[872,2551,2552,2554,2556,2558,2560,2562,2564,2566],{"class":874,"line":959},[872,2553,1581],{"class":1062},[872,2555,1544],{"class":1062},[872,2557,1586],{"class":1106},[872,2559,1124],{"class":878},[872,2561,1591],{"class":1069},[872,2563,1392],{"class":878},[872,2565,1596],{"class":1069},[872,2567,1599],{"class":878},[872,2569,2570],{"class":874,"line":980},[872,2571,1604],{"class":878},[815,2573,1607],{},[815,2575,1610],{},[822,2577],{},[825,2579,1616],{"id":1615},[815,2581,1619],{},[815,2583,2584],{},[836,2585,1624],{},[815,2587,1627,2588,1630,2590,1634],{},[841,2589,1031],{},[841,2591,1633],{},[815,2593,1637,2594,1640,2596,1643,2598,1646,2600,1649],{},[841,2595,1209],{},[841,2597,1197],{},[841,2599,1339],{},[841,2601,1252],{},[815,2603,2604],{},[836,2605,1654],{},[815,2607,1657,2608,1660,2610,1663,2612,1666,2614,1670],{},[841,2609,1501],{},[841,2611,1339],{},[841,2613,1252],{},[841,2615,1669],{},[815,2617,1673],{},[822,2619],{},[825,2621,1679],{"id":1678},[815,2623,1682],{},[815,2625,2626,1691,2630,1694,2632,1698,2634,1701,2636,1705],{},[836,2627,1687,2628,1690],{},[841,2629,1240],{},[841,2631,1388],{},[841,2633,1697],{},[841,2635,1697],{},[841,2637,1704],{},[815,2639,2640,1713,2644,1716,2646,1720,2648,1723],{},[836,2641,1710,2642,1690],{},[841,2643,1261],{},[841,2645,1224],{},[841,2647,1719],{},[841,2649,1261],{},[815,2651,1726,2652,1729,2654,1732],{},[841,2653,1240],{},[841,2655,1261],{},[864,2657,2659],{"className":2658,"code":1737,"language":1738},[1736],[841,2660,1737],{"__ignoreMap":868},[815,2662,1743],{},[822,2664],{},[825,2666,1749],{"id":1748},[815,2668,1752,2669,1756,2671,1759],{},[841,2670,1755],{},[841,2672,1518],{},[815,2674,2675,1764,2677,1768],{},[841,2676,1518],{},[841,2678,1767],{},[1154,2680,2681,2689],{},[1157,2682,2683],{},[1160,2684,2685,2687],{},[1163,2686,1777],{},[1163,2688,1780],{},[1170,2690,2691,2697,2705,2713,2719,2727],{},[1160,2692,2693,2695],{},[1175,2694,1787],{},[1175,2696,1790],{},[1160,2698,2699,2703],{},[1175,2700,2701,1798],{},[841,2702,1797],{},[1175,2704,1801],{},[1160,2706,2707,2711],{},[1175,2708,2709],{},[841,2710,1808],{},[1175,2712,1811],{},[1160,2714,2715,2717],{},[1175,2716,1816],{},[1175,2718,1811],{},[1160,2720,2721,2725],{},[1175,2722,2723,1826],{},[841,2724,1825],{},[1175,2726,1829],{},[1160,2728,2729,2731],{},[1175,2730,1834],{},[1175,2732,1837],{},[815,2734,1840,2735,1843,2737,1847],{},[841,2736,714],{},[841,2738,1846],{},[864,2740,2741],{"className":1053,"code":1850,"language":1055,"meta":868,"style":868},[841,2742,2743,2765,2769,2773,2777,2781,2785,2811,2821],{"__ignoreMap":868},[872,2744,2745,2747,2749,2751,2753,2755,2757,2759,2761,2763],{"class":874,"line":875},[872,2746,1857],{"class":1062},[872,2748,1860],{"class":1062},[872,2750,1863],{"class":1106},[872,2752,1124],{"class":878},[872,2754,1557],{"class":1095},[872,2756,1870],{"class":878},[872,2758,1874],{"class":1873},[872,2760,1130],{"class":878},[872,2762,1563],{"class":1095},[872,2764,1566],{"class":878},[872,2766,2767],{"class":874,"line":882},[872,2768,1885],{"class":1133},[872,2770,2771],{"class":874,"line":913},[872,2772,1890],{"class":1133},[872,2774,2775],{"class":874,"line":938},[872,2776,1895],{"class":1133},[872,2778,2779],{"class":874,"line":959},[872,2780,1900],{"class":1133},[872,2782,2783],{"class":874,"line":980},[872,2784,1905],{"class":1133},[872,2786,2787,2789,2791,2793,2795,2797,2799,2801,2803,2805,2807,2809],{"class":874,"line":998},[872,2788,1910],{"class":1095},[872,2790,1066],{"class":878},[872,2792,1915],{"class":1099},[872,2794,1392],{"class":878},[872,2796,918],{"class":1099},[872,2798,1073],{"class":878},[872,2800,1924],{"class":896},[872,2802,1927],{"class":1069},[872,2804,1118],{"class":878},[872,2806,1932],{"class":1069},[872,2808,1118],{"class":878},[872,2810,1937],{"class":1069},[872,2812,2813,2815,2817,2819],{"class":874,"line":1013},[872,2814,1581],{"class":1062},[872,2816,1066],{"class":878},[872,2818,1915],{"class":1069},[872,2820,1948],{"class":878},[872,2822,2823],{"class":874,"line":1951},[872,2824,1604],{"class":878},[815,2826,1956],{},[822,2828],{},[825,2830,1962],{"id":1961},[815,2832,1965],{},[815,2834,1968],{},[815,2836,1971,2837,1974],{},[841,2838,1261],{},[815,2840,1977],{},[815,2842,1980,2843,1985],{},[1982,2844,1984],{"href":367},[815,2846,1988,2847,1991],{},[1982,2848,20],{"href":103},[1993,2850,1995],{},{"title":868,"searchDepth":882,"depth":882,"links":2852},[2853,2854,2855,2856,2863,2864,2865,2866,2867],{"id":827,"depth":882,"text":828},{"id":1037,"depth":882,"text":1038},{"id":1142,"depth":882,"text":1143},{"id":1267,"depth":882,"text":1268,"children":2857},[2858,2859,2860,2861,2862],{"id":1291,"depth":913,"text":1292},{"id":1302,"depth":913,"text":1303},{"id":1333,"depth":913,"text":1334},{"id":1364,"depth":913,"text":1365},{"id":1381,"depth":913,"text":1382},{"id":1485,"depth":882,"text":1486},{"id":1615,"depth":882,"text":1616},{"id":1678,"depth":882,"text":1679},{"id":1748,"depth":882,"text":1749},{"id":1961,"depth":882,"text":1962},{},{"title":806,"description":2014},[366,38],1780564516881]