[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"header-categories":3,"post-authentication-patterns-spa":73,"parsed-post-5daa7f62-cd30-4a05-9fcf-a6fa6604e332":147},{"success":4,"data":5},true,{"items":6,"pagination":70},[7,16,24,32,40,48,56,63],{"id":8,"name":9,"description":10,"parentId":11,"createdAt":12,"updatedAt":12,"parent":11,"children":13,"_count":14},"ai-future","AI & The Future","Artificial intelligence and emerging technology",null,"2026-07-24T06:02:45.990Z",[],{"posts":15},1,{"id":17,"name":18,"description":19,"parentId":11,"createdAt":20,"updatedAt":20,"parent":11,"children":21,"_count":22},"tools-productivity","Tools & Productivity","Developer tools and workflow optimization","2026-07-24T06:02:45.712Z",[],{"posts":23},7,{"id":25,"name":26,"description":27,"parentId":11,"createdAt":28,"updatedAt":28,"parent":11,"children":29,"_count":30},"career","Career & Growth","Professional development for developers","2026-07-24T06:02:45.486Z",[],{"posts":31},3,{"id":33,"name":34,"description":35,"parentId":11,"createdAt":36,"updatedAt":36,"parent":11,"children":37,"_count":38},"opinion","Opinion & Analysis","Industry analysis and technical opinions","2026-07-24T06:02:45.272Z",[],{"posts":39},6,{"id":41,"name":42,"description":43,"parentId":11,"createdAt":44,"updatedAt":44,"parent":11,"children":45,"_count":46},"tutorials","Tutorials","Step-by-step learning guides","2026-07-24T06:02:44.965Z",[],{"posts":47},21,{"id":49,"name":50,"description":51,"parentId":11,"createdAt":52,"updatedAt":52,"parent":11,"children":53,"_count":54},"software-engineering","Software Engineering","Best practices, patterns, and principles","2026-07-24T06:02:44.689Z",[],{"posts":55},24,{"id":57,"name":58,"description":59,"parentId":11,"createdAt":60,"updatedAt":60,"parent":11,"children":61,"_count":62},"devops-cloud","DevOps & Cloud","Infrastructure, deployment, and cloud computing","2026-07-24T06:02:44.453Z",[],{"posts":39},{"id":64,"name":65,"description":66,"parentId":11,"createdAt":67,"updatedAt":67,"parent":11,"children":68,"_count":69},"web-development","Web Development","Frontend and backend web development tutorials and guides","2026-07-24T06:02:44.169Z",[],{"posts":55},{"page":15,"limit":71,"total":71,"totalPages":15,"hasNext":72,"hasPrev":72},8,false,{"success":4,"data":74},{"id":75,"slug":76,"title":77,"excerpt":78,"content":79,"status":80,"visibility":81,"featuredImage":82,"canonicalUrl":11,"readingTime":83,"viewCount":84,"commentEnabled":4,"publishedAt":85,"scheduledAt":11,"createdAt":86,"updatedAt":87,"seoTitle":88,"seoDescription":78,"seoKeywords":11,"authorId":89,"author":90,"coAuthors":95,"tags":96,"categories":112,"comments":115,"_count":116,"relatedPosts":118},"5daa7f62-cd30-4a05-9fcf-a6fa6604e332","authentication-patterns-spa","Authentication Patterns for Single-Page Applications","Implement secure authentication in SPAs with token rotation, silent refresh, secure storage, and protection against common attacks.","## The SPA Auth Challenge\n\nSPAs run in the browser — an inherently untrusted environment. Tokens stored in JavaScript are vulnerable to XSS. Cookies have CSRF risks. Here's how to navigate this securely.\n\n## The Recommended Pattern: HttpOnly Cookies\n\n```typescript\n\u002F\u002F server\u002Fapi\u002Fauth\u002Flogin.post.ts\nexport default defineEventHandler(async (event) => {\n  const { email, password } = await readBody(event)\n\n  const user = await authenticateUser(email, password)\n  if (!user) throw createError({ statusCode: 401 })\n\n  const { accessToken, refreshToken } = generateTokens(user)\n\n  \u002F\u002F Set tokens as HttpOnly cookies — inaccessible to JavaScript\n  setCookie(event, 'access_token', accessToken, {\n    httpOnly: true,\n    secure: true,\n    sameSite: 'lax',\n    maxAge: 900, \u002F\u002F 15 minutes\n    path: '\u002F',\n  })\n\n  setCookie(event, 'refresh_token', refreshToken, {\n    httpOnly: true,\n    secure: true,\n    sameSite: 'lax',\n    maxAge: 604800, \u002F\u002F 7 days\n    path: '\u002Fapi\u002Fauth\u002Frefresh', \u002F\u002F Only sent to refresh endpoint\n  })\n\n  return { user: { id: user.id, name: user.name, email: user.email } }\n})\n```\n\n::callout{icon=\"i-lucide-shield\" color=\"success\"}\nHttpOnly cookies cannot be read by JavaScript (immune to XSS). SameSite=Lax prevents CSRF on state-changing requests. This is the most secure pattern for SPAs with a same-origin API.\n::\n\n## Silent Token Refresh\n\n```typescript\n\u002F\u002F server\u002Fapi\u002Fauth\u002Frefresh.post.ts\nexport default defineEventHandler(async (event) => {\n  const refreshToken = getCookie(event, 'refresh_token')\n  if (!refreshToken) throw createError({ statusCode: 401 })\n\n  try {\n    const payload = verifyRefreshToken(refreshToken)\n    const user = await getUser(payload.sub)\n\n    \u002F\u002F Rotate refresh token (one-time use)\n    const newTokens = generateTokens(user)\n\n    setCookie(event, 'access_token', newTokens.accessToken, {\n      httpOnly: true, secure: true, sameSite: 'lax', maxAge: 900, path: '\u002F',\n    })\n    setCookie(event, 'refresh_token', newTokens.refreshToken, {\n      httpOnly: true, secure: true, sameSite: 'lax', maxAge: 604800, path: '\u002Fapi\u002Fauth\u002Frefresh',\n    })\n\n    return { user: { id: user.id, name: user.name } }\n  } catch {\n    \u002F\u002F Invalid refresh token — force re-login\n    deleteCookie(event, 'access_token')\n    deleteCookie(event, 'refresh_token')\n    throw createError({ statusCode: 401 })\n  }\n})\n```\n\n## Client-Side Auth Composable\n\n```typescript\n\u002F\u002F composables\u002FuseAuth.ts\nexport function useAuth() {\n  const user = useState\u003CUser | null>('auth-user', () => null)\n  const isAuthenticated = computed(() => !!user.value)\n\n  async function login(email: string, password: string) {\n    const { user: userData } = await $fetch('\u002Fapi\u002Fauth\u002Flogin', {\n      method: 'POST',\n      body: { email, password },\n    })\n    user.value = userData\n  }\n\n  async function logout() {\n    await $fetch('\u002Fapi\u002Fauth\u002Flogout', { method: 'POST' })\n    user.value = null\n    navigateTo('\u002Flogin')\n  }\n\n  async function refreshSession() {\n    try {\n      const { user: userData } = await $fetch('\u002Fapi\u002Fauth\u002Frefresh', { method: 'POST' })\n      user.value = userData\n    } catch {\n      user.value = null\n    }\n  }\n\n  return { user, isAuthenticated, login, logout, refreshSession }\n}\n```\n\n## Route Protection\n\n```typescript\n\u002F\u002F middleware\u002Fauth.ts\nexport default defineNuxtRouteMiddleware(async (to) => {\n  const { isAuthenticated, refreshSession } = useAuth()\n\n  if (!isAuthenticated.value) {\n    await refreshSession()\n  }\n\n  if (!isAuthenticated.value && to.meta.requiresAuth) {\n    return navigateTo(`\u002Flogin?redirect=${to.fullPath}`)\n  }\n})\n```\n\n## Common Attacks and Mitigations\n\n| Attack | Mitigation |\n|--------|-----------|\n| XSS stealing tokens | HttpOnly cookies (JS can't read them) |\n| CSRF | SameSite=Lax + CSRF tokens for mutations |\n| Token theft | Short expiry + rotation |\n| Brute force | Rate limiting + account lockout |\n| Session fixation | Generate new session on login |\n\n## Summary\n\n1. Store tokens in HttpOnly, Secure, SameSite cookies\n2. Short-lived access tokens (15 min) + longer refresh tokens (7 days)\n3. Rotate refresh tokens on every use (detect reuse = breach)\n4. Server-side session validation on every request\n5. Clear all tokens on logout (both client and server)\n","published","public","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1551288049-bebda4e38f71?w=1200&q=80",10,1226,"2026-04-18T06:04:41.020Z","2026-07-24T06:04:41.024Z","2026-07-28T12:16:24.566Z","Authentication Patterns for Single-Page Applications | BitBlog","fddb5d93-7a2c-4d86-a06a-fa32e73a01c6",{"email":91,"bio":92,"id":89,"name":93,"avatarUrl":94},"mbeahessilfieprince@gmail.com","Fullstack Software Developer ","Mbeah Essilfie","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F93322394?v=4",[],[97,102,107],{"id":98,"name":99,"color":100,"description":101},"javascript","JavaScript","#f7df1e","The language of the web",{"id":103,"name":104,"color":105,"description":106},"vue","Vue","#4fc08d","The Progressive JavaScript Framework",{"id":108,"name":109,"color":110,"description":111},"security","Security","#d63031","Web security best practices",[113,114],{"id":64,"name":65,"description":66},{"id":49,"name":50,"description":51},[],{"comments":117},0,[119,128,137],{"id":120,"slug":121,"title":122,"excerpt":123,"featuredImage":124,"viewCount":125,"readingTime":71,"publishedAt":126,"author":127},"2997028f-4d22-4eb7-9d86-894a54cb559a","building-type-safe-apis-nuxt3-prisma","Building Type-Safe APIs with Nuxt 3 and Prisma","Learn how to build end-to-end type-safe APIs using Nuxt 3 server routes and Prisma ORM for a seamless developer experience.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1498050108023-c5249f4df085?w=1200&q=80",1920,"2026-07-23T06:02:46.910Z",{"id":89,"name":93,"avatarUrl":94},{"id":129,"slug":130,"title":131,"excerpt":132,"featuredImage":133,"viewCount":134,"readingTime":83,"publishedAt":135,"author":136},"da9e4553-8b0d-411e-b4ec-f9684017e163","mastering-tailwind-css-design-systems","Mastering Tailwind CSS: From Utility Classes to Design Systems","Go beyond basic utility classes and learn how to build cohesive, maintainable design systems with Tailwind CSS.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1555066931-4365d14bab8c?w=1200&q=80",255,"2026-07-21T06:02:49.268Z",{"id":89,"name":93,"avatarUrl":94},{"id":138,"slug":139,"title":140,"excerpt":141,"featuredImage":142,"viewCount":143,"readingTime":144,"publishedAt":145,"author":146},"50add5fc-4026-4267-b917-7f71ea9e35b0","complete-guide-vue3-composables","The Complete Guide to Vue 3 Composables","Understand the power of Vue 3's Composition API through practical, real-world composable patterns that you can use today.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1517694712202-14dd9538aa97?w=1200&q=80",988,12,"2026-07-19T06:02:51.753Z",{"id":89,"name":93,"avatarUrl":94},{"data":148,"body":150,"toc":380},{"title":149,"description":149},"",{"type":151,"children":152},"root",[153,162,168,174,187,198,204,212,218,226,232,240,246,339,345,375],{"type":154,"tag":155,"props":156,"children":158},"element","h2",{"id":157},"the-spa-auth-challenge",[159],{"type":160,"value":161},"text","The SPA Auth Challenge",{"type":154,"tag":163,"props":164,"children":165},"p",{},[166],{"type":160,"value":167},"SPAs run in the browser — an inherently untrusted environment. Tokens stored in JavaScript are vulnerable to XSS. Cookies have CSRF risks. Here's how to navigate this securely.",{"type":154,"tag":155,"props":169,"children":171},{"id":170},"the-recommended-pattern-httponly-cookies",[172],{"type":160,"value":173},"The Recommended Pattern: HttpOnly Cookies",{"type":154,"tag":175,"props":176,"children":181},"pre",{"className":177,"code":178,"language":179,"meta":149,"style":180},"language-typescript","\u002F\u002F server\u002Fapi\u002Fauth\u002Flogin.post.ts\nexport default defineEventHandler(async (event) => {\n  const { email, password } = await readBody(event)\n\n  const user = await authenticateUser(email, password)\n  if (!user) throw createError({ statusCode: 401 })\n\n  const { accessToken, refreshToken } = generateTokens(user)\n\n  \u002F\u002F Set tokens as HttpOnly cookies — inaccessible to JavaScript\n  setCookie(event, 'access_token', accessToken, {\n    httpOnly: true,\n    secure: true,\n    sameSite: 'lax',\n    maxAge: 900, \u002F\u002F 15 minutes\n    path: '\u002F',\n  })\n\n  setCookie(event, 'refresh_token', refreshToken, {\n    httpOnly: true,\n    secure: true,\n    sameSite: 'lax',\n    maxAge: 604800, \u002F\u002F 7 days\n    path: '\u002Fapi\u002Fauth\u002Frefresh', \u002F\u002F Only sent to refresh endpoint\n  })\n\n  return { user: { id: user.id, name: user.name, email: user.email } }\n})\n","typescript","undefined",[182],{"type":154,"tag":183,"props":184,"children":185},"code",{"__ignoreMap":149},[186],{"type":160,"value":178},{"type":154,"tag":188,"props":189,"children":192},"callout",{"color":190,"icon":191},"success","i-lucide-shield",[193],{"type":154,"tag":163,"props":194,"children":195},{},[196],{"type":160,"value":197},"HttpOnly cookies cannot be read by JavaScript (immune to XSS). SameSite=Lax prevents CSRF on state-changing requests. This is the most secure pattern for SPAs with a same-origin API.",{"type":154,"tag":155,"props":199,"children":201},{"id":200},"silent-token-refresh",[202],{"type":160,"value":203},"Silent Token Refresh",{"type":154,"tag":175,"props":205,"children":207},{"className":177,"code":206,"language":179,"meta":149,"style":180},"\u002F\u002F server\u002Fapi\u002Fauth\u002Frefresh.post.ts\nexport default defineEventHandler(async (event) => {\n  const refreshToken = getCookie(event, 'refresh_token')\n  if (!refreshToken) throw createError({ statusCode: 401 })\n\n  try {\n    const payload = verifyRefreshToken(refreshToken)\n    const user = await getUser(payload.sub)\n\n    \u002F\u002F Rotate refresh token (one-time use)\n    const newTokens = generateTokens(user)\n\n    setCookie(event, 'access_token', newTokens.accessToken, {\n      httpOnly: true, secure: true, sameSite: 'lax', maxAge: 900, path: '\u002F',\n    })\n    setCookie(event, 'refresh_token', newTokens.refreshToken, {\n      httpOnly: true, secure: true, sameSite: 'lax', maxAge: 604800, path: '\u002Fapi\u002Fauth\u002Frefresh',\n    })\n\n    return { user: { id: user.id, name: user.name } }\n  } catch {\n    \u002F\u002F Invalid refresh token — force re-login\n    deleteCookie(event, 'access_token')\n    deleteCookie(event, 'refresh_token')\n    throw createError({ statusCode: 401 })\n  }\n})\n",[208],{"type":154,"tag":183,"props":209,"children":210},{"__ignoreMap":149},[211],{"type":160,"value":206},{"type":154,"tag":155,"props":213,"children":215},{"id":214},"client-side-auth-composable",[216],{"type":160,"value":217},"Client-Side Auth Composable",{"type":154,"tag":175,"props":219,"children":221},{"className":177,"code":220,"language":179,"meta":149,"style":180},"\u002F\u002F composables\u002FuseAuth.ts\nexport function useAuth() {\n  const user = useState\u003CUser | null>('auth-user', () => null)\n  const isAuthenticated = computed(() => !!user.value)\n\n  async function login(email: string, password: string) {\n    const { user: userData } = await $fetch('\u002Fapi\u002Fauth\u002Flogin', {\n      method: 'POST',\n      body: { email, password },\n    })\n    user.value = userData\n  }\n\n  async function logout() {\n    await $fetch('\u002Fapi\u002Fauth\u002Flogout', { method: 'POST' })\n    user.value = null\n    navigateTo('\u002Flogin')\n  }\n\n  async function refreshSession() {\n    try {\n      const { user: userData } = await $fetch('\u002Fapi\u002Fauth\u002Frefresh', { method: 'POST' })\n      user.value = userData\n    } catch {\n      user.value = null\n    }\n  }\n\n  return { user, isAuthenticated, login, logout, refreshSession }\n}\n",[222],{"type":154,"tag":183,"props":223,"children":224},{"__ignoreMap":149},[225],{"type":160,"value":220},{"type":154,"tag":155,"props":227,"children":229},{"id":228},"route-protection",[230],{"type":160,"value":231},"Route Protection",{"type":154,"tag":175,"props":233,"children":235},{"className":177,"code":234,"language":179,"meta":149,"style":180},"\u002F\u002F middleware\u002Fauth.ts\nexport default defineNuxtRouteMiddleware(async (to) => {\n  const { isAuthenticated, refreshSession } = useAuth()\n\n  if (!isAuthenticated.value) {\n    await refreshSession()\n  }\n\n  if (!isAuthenticated.value && to.meta.requiresAuth) {\n    return navigateTo(`\u002Flogin?redirect=${to.fullPath}`)\n  }\n})\n",[236],{"type":154,"tag":183,"props":237,"children":238},{"__ignoreMap":149},[239],{"type":160,"value":234},{"type":154,"tag":155,"props":241,"children":243},{"id":242},"common-attacks-and-mitigations",[244],{"type":160,"value":245},"Common Attacks and Mitigations",{"type":154,"tag":247,"props":248,"children":249},"table",{},[250,269],{"type":154,"tag":251,"props":252,"children":253},"thead",{},[254],{"type":154,"tag":255,"props":256,"children":257},"tr",{},[258,264],{"type":154,"tag":259,"props":260,"children":261},"th",{},[262],{"type":160,"value":263},"Attack",{"type":154,"tag":259,"props":265,"children":266},{},[267],{"type":160,"value":268},"Mitigation",{"type":154,"tag":270,"props":271,"children":272},"tbody",{},[273,287,300,313,326],{"type":154,"tag":255,"props":274,"children":275},{},[276,282],{"type":154,"tag":277,"props":278,"children":279},"td",{},[280],{"type":160,"value":281},"XSS stealing tokens",{"type":154,"tag":277,"props":283,"children":284},{},[285],{"type":160,"value":286},"HttpOnly cookies (JS can't read them)",{"type":154,"tag":255,"props":288,"children":289},{},[290,295],{"type":154,"tag":277,"props":291,"children":292},{},[293],{"type":160,"value":294},"CSRF",{"type":154,"tag":277,"props":296,"children":297},{},[298],{"type":160,"value":299},"SameSite=Lax + CSRF tokens for mutations",{"type":154,"tag":255,"props":301,"children":302},{},[303,308],{"type":154,"tag":277,"props":304,"children":305},{},[306],{"type":160,"value":307},"Token theft",{"type":154,"tag":277,"props":309,"children":310},{},[311],{"type":160,"value":312},"Short expiry + rotation",{"type":154,"tag":255,"props":314,"children":315},{},[316,321],{"type":154,"tag":277,"props":317,"children":318},{},[319],{"type":160,"value":320},"Brute force",{"type":154,"tag":277,"props":322,"children":323},{},[324],{"type":160,"value":325},"Rate limiting + account lockout",{"type":154,"tag":255,"props":327,"children":328},{},[329,334],{"type":154,"tag":277,"props":330,"children":331},{},[332],{"type":160,"value":333},"Session fixation",{"type":154,"tag":277,"props":335,"children":336},{},[337],{"type":160,"value":338},"Generate new session on login",{"type":154,"tag":155,"props":340,"children":342},{"id":341},"summary",[343],{"type":160,"value":344},"Summary",{"type":154,"tag":346,"props":347,"children":348},"ol",{},[349,355,360,365,370],{"type":154,"tag":350,"props":351,"children":352},"li",{},[353],{"type":160,"value":354},"Store tokens in HttpOnly, Secure, SameSite cookies",{"type":154,"tag":350,"props":356,"children":357},{},[358],{"type":160,"value":359},"Short-lived access tokens (15 min) + longer refresh tokens (7 days)",{"type":154,"tag":350,"props":361,"children":362},{},[363],{"type":160,"value":364},"Rotate refresh tokens on every use (detect reuse = breach)",{"type":154,"tag":350,"props":366,"children":367},{},[368],{"type":160,"value":369},"Server-side session validation on every request",{"type":154,"tag":350,"props":371,"children":372},{},[373],{"type":160,"value":374},"Clear all tokens on logout (both client and server)",{"type":154,"tag":376,"props":377,"children":378},"style",{},[379],{"type":160,"value":149},{"title":149,"searchDepth":381,"depth":381,"links":382},2,[383,384,385,386,387,388,389],{"id":157,"depth":381,"text":161},{"id":170,"depth":381,"text":173},{"id":200,"depth":381,"text":203},{"id":214,"depth":381,"text":217},{"id":228,"depth":381,"text":231},{"id":242,"depth":381,"text":245},{"id":341,"depth":381,"text":344}]