[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"header-categories":3,"post-understanding-oauth2-openid-connect":73,"parsed-post-30b664b7-1fa9-44fd-8ab5-4c9b02cb97f1":148},{"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},"30b664b7-1fa9-44fd-8ab5-4c9b02cb97f1","understanding-oauth2-openid-connect","Understanding OAuth 2.0 and OpenID Connect for Web Apps","Demystify OAuth 2.0 flows, tokens, and OpenID Connect. Learn which flow to use for your app and how to implement it securely.","## OAuth 2.0 in Plain English\n\nOAuth 2.0 answers: \"How can App A access User's data on Service B, without User giving App A their password?\"\n\nThe analogy: a hotel key card (token) grants access to specific rooms (scopes) for a limited time, without giving away the master key (password).\n\n## The Players\n\n- **Resource Owner** — The user\n- **Client** — Your application\n- **Authorization Server** — Issues tokens (Google, Auth0, etc.)\n- **Resource Server** — Has the data (API)\n\n## Authorization Code Flow (Recommended)\n\nFor server-rendered apps and SPAs with a backend:\n\n```\n1. User clicks \"Login with Google\"\n2. App redirects to Google's auth endpoint\n3. User logs in and consents\n4. Google redirects back with an authorization CODE\n5. App's backend exchanges code for tokens (server-to-server)\n6. App receives access_token + refresh_token + id_token\n```\n\n```typescript\n\u002F\u002F Step 2: Redirect to auth server\nconst authUrl = new URL('https:\u002F\u002Faccounts.google.com\u002Fo\u002Foauth2\u002Fv2\u002Fauth')\nauthUrl.searchParams.set('client_id', CLIENT_ID)\nauthUrl.searchParams.set('redirect_uri', 'https:\u002F\u002Fmyapp.com\u002Fcallback')\nauthUrl.searchParams.set('response_type', 'code')\nauthUrl.searchParams.set('scope', 'openid email profile')\nauthUrl.searchParams.set('state', generateRandomState()) \u002F\u002F CSRF protection\n\n\u002F\u002F Step 5: Exchange code for tokens (backend)\nconst tokens = await fetch('https:\u002F\u002Foauth2.googleapis.com\u002Ftoken', {\n  method: 'POST',\n  body: new URLSearchParams({\n    code: authorizationCode,\n    client_id: CLIENT_ID,\n    client_secret: CLIENT_SECRET,\n    redirect_uri: 'https:\u002F\u002Fmyapp.com\u002Fcallback',\n    grant_type: 'authorization_code',\n  }),\n})\n```\n\n::callout{icon=\"i-lucide-shield\" color=\"warning\"}\nNever expose `client_secret` to the browser. The code-to-token exchange MUST happen server-side. For SPAs without a backend, use PKCE.\n::\n\n## PKCE: For Public Clients\n\n```typescript\n\u002F\u002F Generate a code verifier and challenge\nconst codeVerifier = generateRandomString(64)\nconst codeChallenge = base64url(sha256(codeVerifier))\n\n\u002F\u002F Include in auth request\nauthUrl.searchParams.set('code_challenge', codeChallenge)\nauthUrl.searchParams.set('code_challenge_method', 'S256')\n\n\u002F\u002F Include verifier in token exchange (proves you started the flow)\ntokenRequest.set('code_verifier', codeVerifier)\n```\n\n## OpenID Connect: Identity Layer\n\nOAuth 2.0 = Authorization (access to resources)\nOIDC = Authentication (who is the user?)\n\nOIDC adds:\n- `id_token` — JWT with user identity claims\n- `\u002Fuserinfo` endpoint — Additional user data\n- Standard scopes: `openid`, `email`, `profile`\n\n```typescript\n\u002F\u002F Decode the id_token (after verification!)\n{\n  \"sub\": \"110169484474386276334\",  \u002F\u002F Unique user ID\n  \"email\": \"mbeah@example.com\",\n  \"name\": \"Mbeah Essilfie\",\n  \"picture\": \"https:\u002F\u002F...\",\n  \"iss\": \"https:\u002F\u002Faccounts.google.com\",\n  \"exp\": 1700000000\n}\n```\n\n## Token Storage\n\n| Token | Store Where | Lifetime |\n|-------|-------------|----------|\n| Access Token | Memory \u002F httpOnly cookie | 5-60 min |\n| Refresh Token | httpOnly cookie | Days-weeks |\n| ID Token | Memory (for display only) | Same as access |\n\n## Which Flow to Use?\n\n- **Server-rendered app (Nuxt SSR)** → Authorization Code\n- **SPA with backend** → Authorization Code\n- **SPA without backend** → Authorization Code + PKCE\n- **Machine-to-machine** → Client Credentials\n- **Mobile app** → Authorization Code + PKCE\n\nNever use the Implicit flow — it's deprecated for security reasons.\n","published","public","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1516116216624-53e697fedbea?w=1200&q=80",11,489,"2026-05-28T06:03:53.576Z","2026-07-24T06:03:53.579Z","2026-07-28T17:13:38.842Z","Understanding OAuth 2.0 and OpenID Connect for Web Apps | 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},"nodejs","Node.js","#339933","JavaScript runtime built on V8",{"id":103,"name":104,"color":105,"description":106},"security","Security","#d63031","Web security best practices",{"id":108,"name":109,"color":110,"description":111},"api-design","API Design","#009688","RESTful and GraphQL API patterns",[113,114],{"id":64,"name":65,"description":66},{"id":49,"name":50,"description":51},[],{"comments":117},0,[119,128,138],{"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",1922,"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":135,"publishedAt":136,"author":137},"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",257,10,"2026-07-21T06:02:49.268Z",{"id":89,"name":93,"avatarUrl":94},{"id":139,"slug":140,"title":141,"excerpt":142,"featuredImage":143,"viewCount":144,"readingTime":145,"publishedAt":146,"author":147},"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",990,12,"2026-07-19T06:02:51.753Z",{"id":89,"name":93,"avatarUrl":94},{"data":149,"body":151,"toc":528},{"title":150,"description":150},"",{"type":152,"children":153},"root",[154,163,169,174,180,226,232,237,249,260,279,285,293,299,304,309,360,368,374,461,467,518,523],{"type":155,"tag":156,"props":157,"children":159},"element","h2",{"id":158},"oauth-20-in-plain-english",[160],{"type":161,"value":162},"text","OAuth 2.0 in Plain English",{"type":155,"tag":164,"props":165,"children":166},"p",{},[167],{"type":161,"value":168},"OAuth 2.0 answers: \"How can App A access User's data on Service B, without User giving App A their password?\"",{"type":155,"tag":164,"props":170,"children":171},{},[172],{"type":161,"value":173},"The analogy: a hotel key card (token) grants access to specific rooms (scopes) for a limited time, without giving away the master key (password).",{"type":155,"tag":156,"props":175,"children":177},{"id":176},"the-players",[178],{"type":161,"value":179},"The Players",{"type":155,"tag":181,"props":182,"children":183},"ul",{},[184,196,206,216],{"type":155,"tag":185,"props":186,"children":187},"li",{},[188,194],{"type":155,"tag":189,"props":190,"children":191},"strong",{},[192],{"type":161,"value":193},"Resource Owner",{"type":161,"value":195}," — The user",{"type":155,"tag":185,"props":197,"children":198},{},[199,204],{"type":155,"tag":189,"props":200,"children":201},{},[202],{"type":161,"value":203},"Client",{"type":161,"value":205}," — Your application",{"type":155,"tag":185,"props":207,"children":208},{},[209,214],{"type":155,"tag":189,"props":210,"children":211},{},[212],{"type":161,"value":213},"Authorization Server",{"type":161,"value":215}," — Issues tokens (Google, Auth0, etc.)",{"type":155,"tag":185,"props":217,"children":218},{},[219,224],{"type":155,"tag":189,"props":220,"children":221},{},[222],{"type":161,"value":223},"Resource Server",{"type":161,"value":225}," — Has the data (API)",{"type":155,"tag":156,"props":227,"children":229},{"id":228},"authorization-code-flow-recommended",[230],{"type":161,"value":231},"Authorization Code Flow (Recommended)",{"type":155,"tag":164,"props":233,"children":234},{},[235],{"type":161,"value":236},"For server-rendered apps and SPAs with a backend:",{"type":155,"tag":238,"props":239,"children":243},"pre",{"className":240,"code":242,"language":161},[241],"language-text","1. User clicks \"Login with Google\"\n2. App redirects to Google's auth endpoint\n3. User logs in and consents\n4. Google redirects back with an authorization CODE\n5. App's backend exchanges code for tokens (server-to-server)\n6. App receives access_token + refresh_token + id_token\n",[244],{"type":155,"tag":245,"props":246,"children":247},"code",{"__ignoreMap":150},[248],{"type":161,"value":242},{"type":155,"tag":238,"props":250,"children":255},{"className":251,"code":252,"language":253,"meta":150,"style":254},"language-typescript","\u002F\u002F Step 2: Redirect to auth server\nconst authUrl = new URL('https:\u002F\u002Faccounts.google.com\u002Fo\u002Foauth2\u002Fv2\u002Fauth')\nauthUrl.searchParams.set('client_id', CLIENT_ID)\nauthUrl.searchParams.set('redirect_uri', 'https:\u002F\u002Fmyapp.com\u002Fcallback')\nauthUrl.searchParams.set('response_type', 'code')\nauthUrl.searchParams.set('scope', 'openid email profile')\nauthUrl.searchParams.set('state', generateRandomState()) \u002F\u002F CSRF protection\n\n\u002F\u002F Step 5: Exchange code for tokens (backend)\nconst tokens = await fetch('https:\u002F\u002Foauth2.googleapis.com\u002Ftoken', {\n  method: 'POST',\n  body: new URLSearchParams({\n    code: authorizationCode,\n    client_id: CLIENT_ID,\n    client_secret: CLIENT_SECRET,\n    redirect_uri: 'https:\u002F\u002Fmyapp.com\u002Fcallback',\n    grant_type: 'authorization_code',\n  }),\n})\n","typescript","undefined",[256],{"type":155,"tag":245,"props":257,"children":258},{"__ignoreMap":150},[259],{"type":161,"value":252},{"type":155,"tag":261,"props":262,"children":265},"callout",{"color":263,"icon":264},"warning","i-lucide-shield",[266],{"type":155,"tag":164,"props":267,"children":268},{},[269,271,277],{"type":161,"value":270},"Never expose ",{"type":155,"tag":245,"props":272,"children":274},{"className":273},[],[275],{"type":161,"value":276},"client_secret",{"type":161,"value":278}," to the browser. The code-to-token exchange MUST happen server-side. For SPAs without a backend, use PKCE.",{"type":155,"tag":156,"props":280,"children":282},{"id":281},"pkce-for-public-clients",[283],{"type":161,"value":284},"PKCE: For Public Clients",{"type":155,"tag":238,"props":286,"children":288},{"className":251,"code":287,"language":253,"meta":150,"style":254},"\u002F\u002F Generate a code verifier and challenge\nconst codeVerifier = generateRandomString(64)\nconst codeChallenge = base64url(sha256(codeVerifier))\n\n\u002F\u002F Include in auth request\nauthUrl.searchParams.set('code_challenge', codeChallenge)\nauthUrl.searchParams.set('code_challenge_method', 'S256')\n\n\u002F\u002F Include verifier in token exchange (proves you started the flow)\ntokenRequest.set('code_verifier', codeVerifier)\n",[289],{"type":155,"tag":245,"props":290,"children":291},{"__ignoreMap":150},[292],{"type":161,"value":287},{"type":155,"tag":156,"props":294,"children":296},{"id":295},"openid-connect-identity-layer",[297],{"type":161,"value":298},"OpenID Connect: Identity Layer",{"type":155,"tag":164,"props":300,"children":301},{},[302],{"type":161,"value":303},"OAuth 2.0 = Authorization (access to resources)\nOIDC = Authentication (who is the user?)",{"type":155,"tag":164,"props":305,"children":306},{},[307],{"type":161,"value":308},"OIDC adds:",{"type":155,"tag":181,"props":310,"children":311},{},[312,323,334],{"type":155,"tag":185,"props":313,"children":314},{},[315,321],{"type":155,"tag":245,"props":316,"children":318},{"className":317},[],[319],{"type":161,"value":320},"id_token",{"type":161,"value":322}," — JWT with user identity claims",{"type":155,"tag":185,"props":324,"children":325},{},[326,332],{"type":155,"tag":245,"props":327,"children":329},{"className":328},[],[330],{"type":161,"value":331},"\u002Fuserinfo",{"type":161,"value":333}," endpoint — Additional user data",{"type":155,"tag":185,"props":335,"children":336},{},[337,339,345,347,353,354],{"type":161,"value":338},"Standard scopes: ",{"type":155,"tag":245,"props":340,"children":342},{"className":341},[],[343],{"type":161,"value":344},"openid",{"type":161,"value":346},", ",{"type":155,"tag":245,"props":348,"children":350},{"className":349},[],[351],{"type":161,"value":352},"email",{"type":161,"value":346},{"type":155,"tag":245,"props":355,"children":357},{"className":356},[],[358],{"type":161,"value":359},"profile",{"type":155,"tag":238,"props":361,"children":363},{"className":251,"code":362,"language":253,"meta":150,"style":254},"\u002F\u002F Decode the id_token (after verification!)\n{\n  \"sub\": \"110169484474386276334\",  \u002F\u002F Unique user ID\n  \"email\": \"mbeah@example.com\",\n  \"name\": \"Mbeah Essilfie\",\n  \"picture\": \"https:\u002F\u002F...\",\n  \"iss\": \"https:\u002F\u002Faccounts.google.com\",\n  \"exp\": 1700000000\n}\n",[364],{"type":155,"tag":245,"props":365,"children":366},{"__ignoreMap":150},[367],{"type":161,"value":362},{"type":155,"tag":156,"props":369,"children":371},{"id":370},"token-storage",[372],{"type":161,"value":373},"Token Storage",{"type":155,"tag":375,"props":376,"children":377},"table",{},[378,402],{"type":155,"tag":379,"props":380,"children":381},"thead",{},[382],{"type":155,"tag":383,"props":384,"children":385},"tr",{},[386,392,397],{"type":155,"tag":387,"props":388,"children":389},"th",{},[390],{"type":161,"value":391},"Token",{"type":155,"tag":387,"props":393,"children":394},{},[395],{"type":161,"value":396},"Store Where",{"type":155,"tag":387,"props":398,"children":399},{},[400],{"type":161,"value":401},"Lifetime",{"type":155,"tag":403,"props":404,"children":405},"tbody",{},[406,425,443],{"type":155,"tag":383,"props":407,"children":408},{},[409,415,420],{"type":155,"tag":410,"props":411,"children":412},"td",{},[413],{"type":161,"value":414},"Access Token",{"type":155,"tag":410,"props":416,"children":417},{},[418],{"type":161,"value":419},"Memory \u002F httpOnly cookie",{"type":155,"tag":410,"props":421,"children":422},{},[423],{"type":161,"value":424},"5-60 min",{"type":155,"tag":383,"props":426,"children":427},{},[428,433,438],{"type":155,"tag":410,"props":429,"children":430},{},[431],{"type":161,"value":432},"Refresh Token",{"type":155,"tag":410,"props":434,"children":435},{},[436],{"type":161,"value":437},"httpOnly cookie",{"type":155,"tag":410,"props":439,"children":440},{},[441],{"type":161,"value":442},"Days-weeks",{"type":155,"tag":383,"props":444,"children":445},{},[446,451,456],{"type":155,"tag":410,"props":447,"children":448},{},[449],{"type":161,"value":450},"ID Token",{"type":155,"tag":410,"props":452,"children":453},{},[454],{"type":161,"value":455},"Memory (for display only)",{"type":155,"tag":410,"props":457,"children":458},{},[459],{"type":161,"value":460},"Same as access",{"type":155,"tag":156,"props":462,"children":464},{"id":463},"which-flow-to-use",[465],{"type":161,"value":466},"Which Flow to Use?",{"type":155,"tag":181,"props":468,"children":469},{},[470,480,489,499,509],{"type":155,"tag":185,"props":471,"children":472},{},[473,478],{"type":155,"tag":189,"props":474,"children":475},{},[476],{"type":161,"value":477},"Server-rendered app (Nuxt SSR)",{"type":161,"value":479}," → Authorization Code",{"type":155,"tag":185,"props":481,"children":482},{},[483,488],{"type":155,"tag":189,"props":484,"children":485},{},[486],{"type":161,"value":487},"SPA with backend",{"type":161,"value":479},{"type":155,"tag":185,"props":490,"children":491},{},[492,497],{"type":155,"tag":189,"props":493,"children":494},{},[495],{"type":161,"value":496},"SPA without backend",{"type":161,"value":498}," → Authorization Code + PKCE",{"type":155,"tag":185,"props":500,"children":501},{},[502,507],{"type":155,"tag":189,"props":503,"children":504},{},[505],{"type":161,"value":506},"Machine-to-machine",{"type":161,"value":508}," → Client Credentials",{"type":155,"tag":185,"props":510,"children":511},{},[512,517],{"type":155,"tag":189,"props":513,"children":514},{},[515],{"type":161,"value":516},"Mobile app",{"type":161,"value":498},{"type":155,"tag":164,"props":519,"children":520},{},[521],{"type":161,"value":522},"Never use the Implicit flow — it's deprecated for security reasons.",{"type":155,"tag":524,"props":525,"children":526},"style",{},[527],{"type":161,"value":150},{"title":150,"searchDepth":529,"depth":529,"links":530},2,[531,532,533,534,535,536,537],{"id":158,"depth":529,"text":162},{"id":176,"depth":529,"text":179},{"id":228,"depth":529,"text":231},{"id":281,"depth":529,"text":284},{"id":295,"depth":529,"text":298},{"id":370,"depth":529,"text":373},{"id":463,"depth":529,"text":466}]