{"id":2351,"date":"2025-05-16T20:10:08","date_gmt":"2025-05-16T19:10:08","guid":{"rendered":"https:\/\/discovery.cevolution.co.uk\/ciam\/?p=2351"},"modified":"2026-04-05T14:03:57","modified_gmt":"2026-04-05T13:03:57","slug":"vibe-coded-authn","status":"publish","type":"post","link":"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/05\/16\/vibe-coded-authn\/","title":{"rendered":"Vibe Coding Authentication via Authorization Code Flow"},"content":{"rendered":"<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\"> 15<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span>\n<p>Developers often ask the question, &#8220;How do I go about integrating <span class=\"popup-trigger popmake-1185 \" data-popup-id=\"1185\" data-do-default=\"0\">CIAM<\/span> standards like <span class=\"popup-trigger popmake-407\" data-popup-id=\"407\" data-do-default=\"0\">OIDC<\/span> and <span class=\"popup-trigger popmake-467\" data-popup-id=\"467\" data-do-default=\"0\">OAuth 2.0<\/span> into my application?&#8221; Invariably, for the majority of use cases, the answer is &#8220;Integrate via an application-independent IdP, using <em>Authorization Code Flow<\/em> (a.k.a. the OAuth 2.0 <em>Authorization Flow<\/em> grant type)&#8221;. But what does that actually mean? What is <em>Authorization Code Flow<\/em>, and how do you go about integrating it into your code?<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-0334809ec9bee69982d14408abba8c49 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>Other flows \u2014 as in other grant types \u2014 are available, and you can read more about those <a href=\"https:\/\/oauth.net\/2\/grant-types\/\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>. However, using Authorization Code Flow is the recommended best practice for most user interactive situations (i.e. <span class=\"popup-trigger popmake-1437\" data-popup-id=\"1437\" data-do-default=\"0\">Login<\/span>).<\/em><\/p>\n<\/div>\n\n\n\n<p>In a previous article, I talked about <a href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/04\/09\/architecting-a-modern-ciam-solution\/\" target=\"_blank\" rel=\"noreferrer noopener\">architecting a CIAM solution<\/a> and how a Buy or DIY (Deploy It Yourself) approach is typically preferable to a Build-it-yourself one. My name&#8217;s <span class=\"popup-trigger popmake-378\" data-popup-id=\"378\" data-do-default=\"0\">Peter Fernandez<\/span>, and whichever approach you prefer, in this article \u2014 together with its accompanying video \u2014 I&#8217;m going to talk about using Authorization Code Flow and how to utilise it &#8220;a-la Vibe style&#8221; when integrating <span class=\"popup-trigger popmake-407\" data-popup-id=\"407\" data-do-default=\"0\">OIDC<\/span> to provide CIAM <a href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/authenticate\/\" data-type=\"page\" data-id=\"6\" target=\"_blank\" rel=\"noreferrer noopener\">Authentication<\/a> in your <span class=\"popup-trigger popmake-1354\" data-popup-id=\"1354\" data-do-default=\"0\">B2C <\/span>or <span class=\"popup-trigger popmake-418\" data-popup-id=\"418\" data-do-default=\"0\">B2B<\/span> SaaS solution \ud83d\ude0e<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-def9e7c1f560c18636eab11f1f0f8912 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>For B2C and B2B SaaS solutions, the use of Authorization Code Flow is also the recommended best practice when it comes to integrating <span class=\"popup-trigger popmake-2149\" data-popup-id=\"2149\" data-do-default=\"0\">Delegated Authorization<\/span> and <a href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/authorize\/access-control\/\" data-type=\"page\" data-id=\"509\" target=\"_blank\" rel=\"noreferrer noopener\">Access Control<\/a>, but for now, we&#8217;ll just stick with Authentication.<\/em><\/p>\n<\/div>\n\n\n\n<figure class=\"wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Vibe Coding Authentication via Authorization Code Flow\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/MQkF9cWYokc?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n<h2 class=\"wp-block-heading\" id=\"what-is-authorization-code-flow\">What is Authorization Code Flow?<\/h2>\n\n\n<p><span class=\"popup-trigger popmake-3653 \" data-popup-id=\"3653\" data-do-default=\"0\">Authorization Code Flow<\/span> is the name given to the workflow typically used in both the <span class=\"popup-trigger popmake-407\" data-popup-id=\"407\" data-do-default=\"0\">OIDC<\/span> protocol (to obtain an ID Token) and in the <span class=\"popup-trigger popmake-467\" data-popup-id=\"467\" data-do-default=\"0\">OAuth 2.0<\/span> protocol to obtain an <em><span class=\"popup-trigger popmake-1400\" data-popup-id=\"1400\" data-do-default=\"0\">Access Token<\/span><\/em>. Its use is a recommended best practice, at least for <span class=\"popup-trigger popmake-2852\" data-popup-id=\"2852\" data-do-default=\"0\">web applications<\/span> and <span class=\"popup-trigger popmake-2828\" data-popup-id=\"2828\" data-do-default=\"0\">SPAs<\/span>, and it&#8217;s described in more detail in the following article:<\/p>\n\n\n\n<div class=\"wp-block-group is-content-justification-center is-nowrap is-layout-flex wp-container-core-group-is-layout-23441af8 wp-block-group-is-layout-flex\">\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-discover-ciam wp-block-embed-discover-ciam\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"4jpmnldfhf\"><a href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/03\/07\/oidc-saml-and-oauth-2-0\/\">OIDC, SAML and OAuth 2.0<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;OIDC, SAML and OAuth 2.0&#8221; &#8212; Discover CIAM\" src=\"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/03\/07\/oidc-saml-and-oauth-2-0\/embed\/#?secret=ML63cmruhy#?secret=4jpmnldfhf\" data-secret=\"4jpmnldfhf\" width=\"500\" height=\"282\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n<\/div>\n\n\n\n<p>It describes a <strong>protocol<\/strong> for obtaining an OIDC <span class=\"popup-trigger popmake-1393\" data-popup-id=\"1393\" data-do-default=\"0\"><em>ID Token<\/em> <\/span>and an (optional) OAuth 2.0 <em><span class=\"popup-trigger popmake-1400\" data-popup-id=\"1400\" data-do-default=\"0\">Access Token<\/span><\/em> as part of a modern CIAM integration. As such, it&#8217;s completely technology agnostic: your application can be written in <strong>Python<\/strong>, <strong>C#<\/strong>, <strong>Java<\/strong>, <strong>PHP<\/strong>, <strong>Javascript<\/strong>, etc; leverage technology stack frameworks such as <strong>Laravel<\/strong>, <strong>.NET<\/strong>, <strong>Next.js<\/strong>, <strong>Django<\/strong>, <strong>Spring<\/strong> or <strong>React<\/strong>; technology stack scaffolding such as <strong>WordPress<\/strong>, <strong>Drupal<\/strong> or <strong>Shopify<\/strong> and utilize some (vendor-provided) <span class=\"popup-trigger popmake-3335 \" data-popup-id=\"3335\" data-do-default=\"0\">SDK<\/span> and\/or some bespoke implementation.<\/p>\n\n\n\n<p>Authorization Code Flow facilitates Authentication \u2014 i.e. <span class=\"popup-trigger popmake-1437\" data-popup-id=\"1437\" data-do-default=\"0\">Login<\/span> \u2014 and also authorisation in a <span class=\"popup-trigger popmake-1087\" data-popup-id=\"1087\" data-do-default=\"0\">User <\/span>context from the perspective of <a data-type=\"page\" data-id=\"1146\" href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/authorize\/consent\/\" target=\"_blank\" rel=\"noreferrer noopener\">Consent<\/a>. Both the <em>ID Token<\/em> and the (optional) <em>Access Token<\/em> returned can be used to provide user information, informing both the UI and any prospective <span class=\"popup-trigger popmake-2876\" data-popup-id=\"2876\" data-do-default=\"0\">API<\/span>. <\/p>\n\n\n\n<p>The diagram below shows how a user would interact with the application, and in turn, how the application will interact with the application-independent <span class=\"popup-trigger popmake-415\" data-popup-id=\"415\" data-do-default=\"0\">IdP<\/span>\/Authorization Server, typically implemented as part of this workflow:<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-16750ec4577abfe6abf5430e8e145ad1 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>The diagram below illustrates Authorization Code Flow with <span class=\"popup-trigger popmake-1895\" data-popup-id=\"1895\" data-do-default=\"0\">PKCE<\/span>, a variation using the best-practice Proof Key for Code Exchange (PKCE) mechanism, mandatory for public clients and recommended generally to mitigate <span class=\"popup-trigger popmake-2365\" data-popup-id=\"2365\" data-do-default=\"0\">MITM<\/span> attacks.<\/em><\/p>\n<\/div>\n\n\n\n<figure class=\"wp-block-image aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"665\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/03\/05120716\/Authorization-Code-Flow-With-PKCE-1024x665.png\" alt=\"\" class=\"wp-image-1404\" style=\"width:1024px;height:auto\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/03\/05120716\/Authorization-Code-Flow-With-PKCE-1024x665.png 1024w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/03\/05120716\/Authorization-Code-Flow-With-PKCE-300x195.png 300w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/03\/05120716\/Authorization-Code-Flow-With-PKCE-768x499.png 768w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/03\/05120716\/Authorization-Code-Flow-With-PKCE.png 1444w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Image courtesy of <a href=\"https:\/\/cloudentity.com\/developers\/basics\/oauth-grant-types\/authorization-code-flow\/\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudentity<\/a><\/figcaption><\/figure>\n\n\n<h2 class=\"wp-block-heading\" id=\"what-is-vibe-coding\">What is Vibe Coding?<\/h2>\n\n\n<p>Vibe coding&#8221; refers to a new approach to software development where developers use natural language prompts to interact with large language models (LLMs) to generate code, rather than writing code directly. Essentially, instead of writing lines of code, developers describe the desired functionality, and the AI handles the code generation.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-471fe7a5442c346b2bb45084858b0336 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>Vibe coding is an AI-dependent programming technique, but there&#8217;s nothing to say you have to build <strong>all<\/strong> of an application using it. Describing a specific problem in a sentence or two, as a prompt to a large language model (LLM), ideally tuned for coding, is arguably just as valuable.<\/em><\/p>\n<\/div>\n\n\n\n<p id=\"livestream\">I recently watched an episode of the live stream that an ex-colleague of mine (Juan) does, and the episode I watched was entitled &#8220;<a href=\"https:\/\/www.linkedin.com\/posts\/bajcmartinez_ai-dev-thursdays-vibe-coding-an-ai-app-activity-7308414298911911936-QMdr\" target=\"_blank\" rel=\"noreferrer noopener\">Vibe coding an AI app<\/a>&#8221; (below). In the episode, Juan explores building an application using the AI Vibe technique, with varying results, and the various challenges faced are readily apparent.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/www.linkedin.com\/posts\/bajcmartinez_ai-dev-thursdays-vibe-coding-an-ai-app-activity-7308414298911911936-QMdr?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAAAGoTVYBuw2YOwho7AyTfYvZvv7VigZ9eIk\" target=\"_blank\" rel=\" noreferrer noopener\"><img loading=\"lazy\" decoding=\"async\" width=\"797\" height=\"447\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/04\/14125442\/image-11-e1744631881801.png\" alt=\"\" class=\"wp-image-3153\" style=\"box-shadow:var(--wp--preset--shadow--deep)\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/04\/14125442\/image-11-e1744631881801.png 797w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/04\/14125442\/image-11-e1744631881801-300x168.png 300w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/04\/14125442\/image-11-e1744631881801-768x431.png 768w\" sizes=\"auto, (max-width: 797px) 100vw, 797px\" \/><\/a><figcaption class=\"wp-element-caption\"><em>Click to watch via LinkedIn<\/em><\/figcaption><\/figure>\n\n\n\n<p>I&#8217;ve not tried Vibe coding before, but my experience with AI has taught me that the more discrete the problem, and the more precise you can be at describing what you&#8217;re trying to achieve \u2014 arguably easier to do with a discrete situation \u2014 the more likely it is that (generative) AI will be able to help. So I thought it would be fun to try vibe coding the integration of just the user authentication piece using OIDC via Authorization Code Flow into a Next.js app. <\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-1beb3bcb9328e44cb4cc24b990469a27 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>Next.js has become a popular option for building <span class=\"popup-trigger popmake-2946\" data-popup-id=\"2946\" data-do-default=\"0\">SaaS<\/span> applications, and it&#8217;s relatively straightforward to get started and get a basic app running in no time at all.<\/em><\/p>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"creating-a-nextjs-app\">Creating a Next.js App<\/h2>\n\n\n<p>The first job is to create a simple Next.js application, and this can easily be done using something similar to the command below. You&#8217;ll need a compatible version of <strong>Node.js<\/strong> installed in your development environment, which is relatively straightforward to do, no matter which OS development platform you&#8217;re using (see <a href=\"https:\/\/nodejs.org\/en\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a> for details).<\/p>\n\n\n\n<pre class=\"wp-block-code has-base-color has-contrast-background-color has-text-color has-background has-link-color wp-elements-dc0a35ea7414cf3838c2475cdd05d906\"><code>npx create-next-app@latest<\/code><\/pre>\n\n\n\n<p>Once you&#8217;ve answered a few questions (you can see my answers below; I just went with the defaults), <code>create-next-app<\/code> will go away and provision everything you need to get started. In my case, this resulted in a  <code>my-nextjs-app<\/code> folder created under my Keycloak project folder; I&#8217;d created it in the Keycloak folder for no other reason than it was just the way I wanted the project organised. All I then needed to do was <code>cd<\/code> into it.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-f8fd8abc4eff76ed0876821c54841c77 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>I use Keycloak as part of my CIAM integration needs, which you can read about in my article <a href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/03\/26\/wordpress-openfga-and-keycloak\/\" data-type=\"post\" data-id=\"1350\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>. I&#8217;m also sure I could have Vibe&#8217;d the creation of the app, but I&#8217;m comfortable doing that by myself before jumping into the Vibe process.<\/em><\/p>\n<\/div>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"2228\" height=\"1507\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113240\/image-4-e1747053982133.png\" alt=\"\" class=\"wp-image-3556\" style=\"box-shadow:var(--wp--preset--shadow--natural)\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113240\/image-4-e1747053982133.png 2228w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113240\/image-4-e1747053982133-300x203.png 300w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113240\/image-4-e1747053982133-1024x693.png 1024w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113240\/image-4-e1747053982133-768x519.png 768w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113240\/image-4-e1747053982133-1536x1039.png 1536w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113240\/image-4-e1747053982133-2048x1385.png 2048w\" sizes=\"auto, (max-width: 2228px) 100vw, 2228px\" \/><\/figure>\n\n\n\n<p>Having <code>cd<\/code>&#8216;d into the <code>my-nextjs-app<\/code> I ran the following independent commands, letting each complete before issuing the next; the first two to make sure everything was installing and building OK, and the latter to spin up a local instance of the app, which I can test, debug and build upon. Navigating via the browser to <code>localhost:3000<\/code> resulted in the default page you get presented with when you first create a Next.js application<\/p>\n\n\n\n<pre class=\"wp-block-code has-base-color has-contrast-background-color has-text-color has-background has-link-color wp-elements-d91c71052fc84246e651b7b78b6d8e39\"><code>npm install<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code has-base-color has-contrast-background-color has-text-color has-background has-link-color wp-elements-9c2f3749b4d48b19dc0daa2767a69dc5\"><code>npm run build<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code has-base-color has-contrast-background-color has-text-color has-background has-link-color wp-elements-3d77ccd14b611699f182677b8a83140b\"><code>npm run dev<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"2560\" height=\"1427\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113812\/image-5-scaled.png\" alt=\"\" class=\"wp-image-3558\" style=\"box-shadow:var(--wp--preset--shadow--natural)\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113812\/image-5-scaled.png 2560w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113812\/image-5-300x167.png 300w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113812\/image-5-1024x571.png 1024w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113812\/image-5-768x428.png 768w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113812\/image-5-1536x856.png 1536w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10113812\/image-5-2048x1142.png 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><\/figure>\n\n\n<h2 class=\"wp-block-heading\" id=\"editing\">Editing The Code<\/h2>\n\n\n<p>Similar to most, I typically use <a href=\"https:\/\/code.visualstudio.com\/\">Visual Studio Code<\/a> (VSCode) as my edit\/integrated development environment of choice. VSCode comes with the integrated Copilot feature, and when using Copilot <em>Chat,<\/em> you have various options available.<\/p>\n\n\n\n<p>One of those options reflects the LLM \u2014 the Large Language Model \u2014 and I chose to go with the latest version of GPT (<em>GPT-41 <\/em>at the time of writing). Others, like <em>Claude 3.5 Sonnet<\/em> are available, (again at least at the time of writing), and you can configure to add premium models, but as I wanted to keep things simple \u2014 and as cheap as possible (in this case, read &#8220;free&#8221;) \u2014 I just stuck with one of the defaults.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-5702c5f60e670b158a5738923f83d799 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>Rather than trying to describe every single step of the process in this article, I&#8217;ve created an accompanying video <a href=\"https:\/\/youtu.be\/MQkF9cWYokc\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a> in my Customer Identity series that shows the entire process.<\/em><\/p>\n<\/div>\n\n\n\n<div class=\"wp-block-columns are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-65e523f9 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:66.66%\">\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"830\" height=\"952\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/04\/14105305\/image-6.png\" alt=\"\" class=\"wp-image-3136\" style=\"box-shadow:var(--wp--preset--shadow--natural)\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/04\/14105305\/image-6.png 830w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/04\/14105305\/image-6-262x300.png 262w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/04\/14105305\/image-6-768x881.png 768w\" sizes=\"auto, (max-width: 830px) 100vw, 830px\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-vertically-aligned-center has-global-padding is-layout-constrained wp-container-core-column-is-layout-7a3688e9 wp-block-column-is-layout-constrained\" style=\"flex-basis:33.33%\">\n<figure class=\"wp-block-image aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"397\" height=\"1024\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10112223\/image-3-397x1024.png\" alt=\"\" class=\"wp-image-3555\" style=\"box-shadow:var(--wp--preset--shadow--natural);object-fit:cover\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10112223\/image-3-397x1024.png 397w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10112223\/image-3-116x300.png 116w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10112223\/image-3.png 399w\" sizes=\"auto, (max-width: 397px) 100vw, 397px\" \/><\/figure>\n<\/div>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"describing-the-problem-to-ai\">Describing The Problem to AI<\/h3>\n\n\n<p>Having selected <code>page.tsx<\/code> in VSCode <em>Explorer<\/em> \u2014 I don&#8217;t know if I needed to select <code>page.tsx<\/code> per se, but as with most things AI, giving as much context as possible is key, and <code>page.tsx<\/code> represented a page I wanted to protect \u2014 I opened up a Copilot <em>Chat<\/em> window and typed the first request to integrate OIDC authentication into my app, which went something like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>add authentication via OIDC to my Next.js 15.3.2 application   <\/code><\/pre>\n\n\n\n<p>As you can see from the <a href=\"https:\/\/youtu.be\/MQkF9cWYokc\" target=\"_blank\" rel=\"noopener\" title=\"\">accompanying video<\/a>, Copilot made some changes to various files and added some dependencies, and you can see the modifications that have been made. There are a couple of issues, but let&#8217;s just go ahead and accept all the modifications for the moment.<\/p>\n\n\n<h3 class=\"wp-block-heading\" id=\"fixes\">Code Fixing With AI<\/h3>\n\n\n<p>The main issue with the changes Copilot added is that <code>\"next-auth\/providers\"<\/code> can&#8217;t be found, so having selected <code>route.ts<\/code> let&#8217;s go ahead and ask Copilot to fix the problem. For the duration of a chat, Copilot will retain context, so it should be cognisant of what&#8217;s already been done:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>update to use Keycloak as the OIDC provider   <\/code><\/pre>\n\n\n\n<p>As you can see in the video, it&#8217;s made an attempt, and I&#8217;ve accepted the changes it suggested. However, it didn&#8217;t fully fix the problem; I could have probably tried another Copilot prompt, but instead, I chose to intervene and manually update to use the Keycloak <a href=\"https:\/\/next-auth.js.org\/providers\/keycloak\" target=\"_blank\" rel=\"noreferrer noopener\">out-of-box definition<\/a> provided by the <em>NextAuth<\/em> SDK \u2014 at the same time taking the opportunity to clean up the code by removing some of the options not required (in this case, just accepting the defaults as documented <a href=\"https:\/\/github.com\/nextauthjs\/next-auth\/blob\/v4\/packages\/next-auth\/src\/providers\/keycloak.ts\">here<\/a>).<\/p>\n\n\n<h4 class=\"wp-block-heading\" id=\"module-resolution\">Module Resolution<\/h4>\n\n\n<p>The other problem I ran into was that the Next.js development runtime couldn&#8217;t resolve access to the <code>next-auth<\/code> module. This turned out to be because the Copilot page installation, whilst having installed <code>next-auth<\/code>, hadn&#8217;t actually added this to the modules list in <code>package.json<\/code> (on which the Next.js development runtime is dependent). Again, I fixed this manually, by simply rerunning the command Copilot said it had run (i.e. <code>npm install next-auth<\/code>), this time from the command line.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"704\" height=\"178\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10123851\/image-6.png\" alt=\"\" class=\"wp-image-3563\" style=\"box-shadow:var(--wp--preset--shadow--natural)\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10123851\/image-6.png 704w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10123851\/image-6-300x76.png 300w\" sizes=\"auto, (max-width: 704px) 100vw, 704px\" \/><\/figure>\n\n\n<h4 class=\"wp-block-heading\" id=\"ssr\">Server-Side Rendering<\/h4>\n\n\n<p>By default, Copilot AI has also added client-side rendering functionality for the buttons for <span class=\"popup-trigger popmake-1437\" data-popup-id=\"1437\" data-do-default=\"0\">Sign In<\/span> and Sign Out, which is incompatible with server-side rendering. Again, though, it&#8217;s a relatively easy fix, which, with the page.tsx file selected, can be accomplished by using another Copilot prompt:<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-9bb4ccf5de59d390f29e2217c36dfab6 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>Next.js projects are, by default, <span class=\"popup-trigger popmake-2852\" data-popup-id=\"2852\" data-do-default=\"0\">regular web applications<\/span>, performing server-side rendering. For more details on this and the various rendering options available to a Next.js application, visit the official documentation <a href=\"https:\/\/nextjs.org\/docs\/pages\/building-your-application\/rendering\">here<\/a>. <\/em><\/p>\n<\/div>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"2560\" height=\"1371\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10144140\/image-8-scaled.png\" alt=\"\" class=\"wp-image-3577\" style=\"box-shadow:var(--wp--preset--shadow--natural)\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10144140\/image-8-scaled.png 2560w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10144140\/image-8-300x161.png 300w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10144140\/image-8-1024x548.png 1024w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10144140\/image-8-768x411.png 768w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10144140\/image-8-1536x822.png 1536w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/10144140\/image-8-2048x1097.png 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>fix signIn and signOut for server side rendering   <\/code><\/pre>\n\n\n<h2 class=\"wp-block-heading\" id=\"dependencies\">Dependencies <\/h2>\n\n\n<p>Generative AI is quite &#8220;human&#8221; in that it&#8217;s not necessarily deterministic \u2014 i.e. doesn&#8217;t always come up with the same result(s) for the same prompt. Interestingly, in my experiments, at least when using the <em>GPT-41<\/em> LLM, Copilot comes back with a narrative that almost always suggests using <em>NextAuth<\/em> SDK as a dependency for a CIAM integration, and most of the time picks <em>Keycloak<\/em> as the <span class=\"popup-trigger popmake-415\" data-popup-id=\"415\" data-do-default=\"0\">IdP<\/span> (amongst others like <em><a href=\"https:\/\/auth0.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Auth0<\/a><\/em>). Somewhat egotistically, at least from the perspective of choosing an IdP, I was left wondering if the AI had been &#8220;reading&#8221; my previous article \ud83d\ude09:<\/p>\n\n\n\n<div class=\"wp-block-group is-content-justification-center is-nowrap is-layout-flex wp-container-core-group-is-layout-23441af8 wp-block-group-is-layout-flex\">\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-discover-ciam wp-block-embed-discover-ciam\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"2pFLuxEtyM\"><a href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/02\/22\/diy-or-buy\/\">Build, Buy or DIY your CIAM Solution?<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;Build, Buy or DIY your CIAM Solution?&#8221; &#8212; Discover CIAM\" src=\"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/02\/22\/diy-or-buy\/embed\/#?secret=EvVy5TiyCh#?secret=2pFLuxEtyM\" data-secret=\"2pFLuxEtyM\" width=\"500\" height=\"282\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n<\/div>\n\n\n\n<p>Of course, if you don&#8217;t have a <em>Keycloak<\/em> instance set up, then you&#8217;ll have some additional work to do. You could opt to follow the official Keycloak documentation <a href=\"https:\/\/www.keycloak.org\/getting-started\/getting-started-docker\">here<\/a>, or check out <a href=\"https:\/\/www.youtube.com\/@MilanJovanovicTech\">Milan Jovanovi\u0107<\/a>&#8216;s video <a href=\"https:\/\/youtu.be\/fvxQ8bW0vO8?si=KOiNqd1JJpupIVoc\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>, which does a really nice job of illustrating how to get a Keycloak instance up and running for authentication. <\/p>\n\n\n\n<p>Alternatively, a 3rd-party hosting service like <em><a href=\"https:\/\/phasetwo.io\/\">Phase Two<\/a><\/em> offers a free &#8220;Starter&#8221; deployment on their shared cluster; being primarily an enterprise-level <span class=\"popup-trigger popmake-3257\" data-popup-id=\"3257\" data-do-default=\"0\">IAM<\/span> vendor, Phase Two managed hosting has arguably aggressive pricing for production-level deployments, however, their free tier should be good enough for prototyping and testing (if nothing else). <\/p>\n\n\n\n<p>Last but not least, you can always use a 3rd party <span class=\"popup-trigger popmake-2946\" data-popup-id=\"2946\" data-do-default=\"0\">SaaS<\/span> CIAM solution provider like <a href=\"https:\/\/auth0.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Auth0<\/a>, as Copilot suggests, particularly if the DIY route isn&#8217;t one you&#8217;re looking to take.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-50e914bbec3e184720dae2586367c44d is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>I&#8217;ve been working on my own set of articles about Keycloak, and my journey setting up a production-ready Keycloak deployment for the B2C and B2B SaaS projects I&#8217;m building. Those, however, are stories to share another time \ud83e\udd17 In the meantime, Phase Two free tier for Keycloak works well, and they even have a blog post about securing Next.js apps using it too: see <a href=\"https:\/\/phasetwo.io\/blog\/instant-user-managemenet-and-sso-for-nextjs\/\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a> for more details. <\/em> <\/p>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"sdk\">Using an SDK<\/h3>\n\n\n<p>Copilot installs the <em>NextAuth<\/em> <span class=\"popup-trigger popmake-3335\" data-popup-id=\"3335\" data-do-default=\"0\">SDK<\/span>, and you can read more about <em>NextAuth<\/em> at <a href=\"https:\/\/next-auth.js.org\">https:\/\/next-auth.js.org<\/a> (or the new <a href=\"https:\/\/authjs.dev\">https:\/\/authjs.dev<\/a> site, which also provides easy access to some of the other framework integrations). The <em>NextAuth<\/em> SDK is free of charge and is also CIAM platform-agnostic, as in it should work no matter which <span class=\"popup-trigger popmake-415\" data-popup-id=\"415\" data-do-default=\"0\">IdP<\/span> you choose to use. In most cases, a (vendor-supplied) SDK will be offered free of charge and\/or provided as an open-source component\/library. <\/p>\n\n\n\n<p>If a third-party vendor charges for or requires the use of an SDK, the chances of vendor lock-in will likely increase; situations such as these most likely signal the use of some non-standard vendor-specific functionality that may not be available if you choose to move to another provider.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-3d657c226f2ac88e0915951e965a3397 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>The <a href=\"https:\/\/connect2id.com\/products\/nimbus-oauth-openid-connect-sdk\">Nimbus OAuth 2.0 SDK with OpenID Connect extensions<\/a> by <a href=\"https:\/\/connect2id.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Connect2id<\/a> is another CIAM platform-agnostic SDK, in this case for Java. However, it&#8217;s not one I&#8217;ve personally used.<\/em><\/p>\n<\/div>\n\n\n\n<p>A Software Development Kit, or SDK as it&#8217;s better known, typically provides an efficient out-of-box solution when it comes to integrating functionality within an application, and using one should make your life easier as a developer. At least for the majority of cases. <\/p>\n\n\n\n<p>There are cases, however, where an SDK doesn&#8217;t quite provide the level of seamless efficiency a developer would hope for \u2014 e.g. where an SDK doesn&#8217;t quite fit the execution model required, or where a breaking change in an upgrade of an SDK is either unavoidable or unintended (as illustrated in a recent post).<\/p>\n\n\n\n<div class=\"wp-block-group is-content-justification-center is-nowrap is-layout-flex wp-container-core-group-is-layout-23441af8 wp-block-group-is-layout-flex\">\n<figure class=\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\"><div class=\"wp-block-embed__wrapper\">\n<div class=\"embed-twitter\"><blockquote class=\"twitter-tweet\" data-width=\"500\" data-dnt=\"true\"><p lang=\"en\" dir=\"ltr\"><a href=\"https:\/\/twitter.com\/nextjs?ref_src=twsrc%5Etfw\">@nextjs<\/a> v15 requires upgrade to <a href=\"https:\/\/twitter.com\/auth0?ref_src=twsrc%5Etfw\">@auth0<\/a> nextjs sdk v4. Plenty of open tickets, none acked since 3 weeks ago. v4 migration is horrible and breaks shit. <br><br>Stay away from this auth provider.<a href=\"https:\/\/twitter.com\/rauchg?ref_src=twsrc%5Etfw\">@rauchg<\/a> I know it&#39;s not in your hands, but maybe you have channels?<\/p>&mdash; Bernd Strehl (@strehldev) <a href=\"https:\/\/twitter.com\/strehldev\/status\/1904463529068933425?ref_src=twsrc%5Etfw\">March 25, 2025<\/a><\/blockquote><script async src=\"https:\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script><\/div>\n<\/div><\/figure>\n<\/div>\n\n\n\n<p>Whilst leveraging an SDK can arguably be extremely useful, particularly when it comes to speed of integration and\/or isolation from the subtle nuances of using an implementation \u2014 API parameters and parameter formats, for example \u2014 standards-based approaches (particularly those involving the use of an API; see my article entitled <em><a href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/04\/04\/an-api-first-approach-to-ciam\/\" target=\"_blank\" rel=\"noreferrer noopener\">An API-First Approach to CIAM<\/a><\/em> for more on that) shouldn&#8217;t require you to use any particular or specific SDK.<\/p>\n\n\n\n<p>Also, when it comes to best-practice recommendations in terms of the secure storage of <em><span class=\"popup-trigger popmake-1393\" data-popup-id=\"1393\" data-do-default=\"0\">ID Tokens<\/span><\/em>, <em><span class=\"popup-trigger popmake-1400\" data-popup-id=\"1400\" data-do-default=\"0\">Access Tokens<\/span><\/em> and other security artefacts, using an SDK should shield you from the associated subtle vagaries of standards and the like, and also provide a clear explanation when it comes to changes in policy regarding these. Arguably, for this reason alone, it&#8217;s usually preferable to use a (vendor-provided) SDK \u2014 though sometimes it doesn&#8217;t always feel that way.<\/p>\n\n\n\n<div class=\"wp-block-group is-content-justification-center is-nowrap is-layout-flex wp-container-core-group-is-layout-23441af8 wp-block-group-is-layout-flex\">\n<figure class=\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\"><div class=\"wp-block-embed__wrapper\">\n<div class=\"embed-twitter\"><blockquote class=\"twitter-tweet\" data-width=\"500\" data-dnt=\"true\"><p lang=\"en\" dir=\"ltr\">Yesterday, a customer insisted that Auth0 recommends localstorage for long-lived session tokens in React<br><br>I could have sworn they strongly advise against it \ud83e\udd14<br><br>Wayback Machine shows they changed their guidance, and removed the link to OWASP that says not use localstorage \ud83d\udc40 <a href=\"https:\/\/t.co\/1FPZ4JDp0L\">pic.twitter.com\/1FPZ4JDp0L<\/a><\/p>&mdash; Colin | clerk.com (@tweetsbycolin) <a href=\"https:\/\/twitter.com\/tweetsbycolin\/status\/1868044046381568275?ref_src=twsrc%5Etfw\">December 14, 2024<\/a><\/blockquote><script async src=\"https:\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script><\/div>\n<\/div><\/figure>\n<\/div>\n\n\n<h4 class=\"wp-block-heading\" id=\"sdks-vs-plugins\">SDKs vs Plugins<\/h4>\n\n\n<p>Similar to using an SDK, some environments \u2014 particularly those associated with technology stack frameworks or scaffolding, e.g. WordPress \u2014 use <em>Plugins<\/em>, as components to extend or enhance system functionality. These shouldn&#8217;t be confused with SDKs as they serve a different purpose; third-party vendors, like Auth0 and <a href=\"https:\/\/plugins.miniorange.com\/wordpress-sso\" target=\"_blank\" rel=\"noreferrer noopener\">MiniOrange<\/a>, provide OIDC-compatible services via plugin, however, the topic of plugins is beyond the scope of this article. <\/p>\n\n\n<h4 class=\"wp-block-heading\" id=\"bespoke-implementation\">Bespoke Implementation<\/h4>\n\n\n<p>As an alternative to using an SDK or a Plugin \u2014 or in some cases, in addition to using either of these \u2014 a bespoke implementation can also be created by following the standards-based workflow as defined by the <em>Authorization Code Flow<\/em> protocol.<\/p>\n\n\n<h3 class=\"wp-block-heading\" id=\"keycloak\">Configuring Keycloak<\/h3>\n\n\n<p>With the changes Copilot has made, I now get something that looks like the following when I navigate to my application (i.e. <code>https:\/\/localhost:3000<\/code>). It appears we still have an issue, but it&#8217;s an issue with using &#8220;legacy behaviour&#8221;, so let&#8217;s just ignore it for the moment; besides, I&#8217;ve left this as one of the <a href=\"#exercises\">follow-up exercises<\/a> should you be up for the challenge \ud83d\ude01<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-28b0ae18f8eddf6ee881d3c6867d8f86 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>As previously mentioned, rather than trying to describe every single step of the process in this article, I&#8217;ve created an accompanying video <a href=\"https:\/\/youtu.be\/MQkF9cWYokc\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a> in my Customer Identity series that shows the entire process. If you&#8217;re not following along, or you&#8217;ve not yet checked it out, I&#8217;d highly recommend you do when you have the chance.<\/em><\/p>\n<\/div>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"2560\" height=\"1362\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12091244\/image-9-scaled.png\" alt=\"\" class=\"wp-image-3638\" style=\"box-shadow:var(--wp--preset--shadow--natural)\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12091244\/image-9-scaled.png 2560w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12091244\/image-9-300x160.png 300w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12091244\/image-9-1024x545.png 1024w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12091244\/image-9-768x408.png 768w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12091244\/image-9-1536x817.png 1536w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12091244\/image-9-2048x1089.png 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><\/figure>\n\n\n\n<p>For the eagle-eyed among you who&#8217;ve watched (or are watching) my accompanying video, you&#8217;ll recall that Copilot asked me to let it know if I needed help with Keycloak setup; here&#8217;s the excerpt from the chat I&#8217;m talking about, together with my next request presented to Copilot (remember the Copilot chat retains context, so I can leverage the conversation I&#8217;ve already been having):<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"706\" height=\"608\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12100111\/image-10.png\" alt=\"\" class=\"wp-image-3640\" style=\"box-shadow:var(--wp--preset--shadow--natural)\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12100111\/image-10.png 706w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12100111\/image-10-300x258.png 300w\" sizes=\"auto, (max-width: 706px) 100vw, 706px\" \/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>Help me setup Keycloak for my application <\/code><\/pre>\n\n\n\n<p>Below is the salient part of the response provided. There are a few things that are beyond the scope of this article \u2014 i.e. setting up a Keycloak server and configuring a <em>Realm<\/em> \u2014 which I&#8217;ll be covering in more detail in the future, so for now let&#8217;s just concentrate on creating and configuring a Keycloak <em>Client<\/em> that we can use for the remainder of the project.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-dbb0e99e7071aefd78d8a800f07d8239 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>You can read more about Keycloak configuration in the official documentation <a href=\"https:\/\/www.keycloak.org\/getting-started\/getting-started-docker\">here<\/a>, or check out <a href=\"https:\/\/www.youtube.com\/@MilanJovanovicTech\">Milan Jovanovi\u0107<\/a>&#8216;s video <a href=\"https:\/\/youtu.be\/fvxQ8bW0vO8?si=KOiNqd1JJpupIVoc\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>, which does a nice job of illustrating how to get a Keycloak instance up and running for authentication. As previously mentioned, Phase Two also offers guidance in the blog post <a href=\"https:\/\/phasetwo.io\/blog\/instant-user-managemenet-and-sso-for-nextjs\/\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>. <\/em><\/p>\n<\/div>\n\n\n\n<figure class=\"wp-block-image aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"657\" height=\"1024\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12100946\/image-11-657x1024.png\" alt=\"\" class=\"wp-image-3641\" style=\"box-shadow:var(--wp--preset--shadow--natural)\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12100946\/image-11-657x1024.png 657w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12100946\/image-11-193x300.png 193w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/12100946\/image-11.png 710w\" sizes=\"auto, (max-width: 657px) 100vw, 657px\" \/><\/figure>\n\n\n<h4 class=\"wp-block-heading\" id=\"client-definition\">Client Definition<\/h4>\n\n\n<p>In the context of Authorization Code Flow (and <span class=\"popup-trigger popmake-407\" data-popup-id=\"407\" data-do-default=\"0\">OIDC<\/span>\/<span class=\"popup-trigger popmake-467\" data-popup-id=\"467\" data-do-default=\"0\">OAuth 2.0<\/span> in general), a <em>Client <\/em>is generally defined as an application that requests access to protected resources on behalf of a user (or resource owner). This could be a <span class=\"popup-trigger popmake-2852\" data-popup-id=\"2852\" data-do-default=\"0\">web application<\/span>, a <span class=\"popup-trigger popmake-2862\" data-popup-id=\"2862\" data-do-default=\"0\">mobile app<\/span>, etc. <\/p>\n\n\n\n<p>More specifically, a <em>Client<\/em> represents a thing that requests access to protected resources on behalf of a user (or indeed resources belonging to it, in its own right), which might be an application, a specific context within an application (such as a specific route), or something else entirely. What a Client is, or could be, is beyond the scope of this article, so let&#8217;s just go with application for now.<\/p>\n\n\n\n<p>Let&#8217;s also go with the recommendations made by Copilot for now. However, in a future article, I&#8217;m going to explain why opting for a more obfuscated <em>Client ID<\/em> is a better practice, particularly when it comes to hardening your CIAM integration. <\/p>\n\n\n\n<p>Here are a couple of screenshots taken from the Keycloak management dashboard (for my <code>Cevolution<\/code> realm; something I&#8217;ll be covering in future articles), showing the Client definition I created based on the Copilot steps described above.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"2560\" height=\"1309\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13093633\/image-12-scaled.png\" alt=\"\" class=\"wp-image-3686\" style=\"box-shadow:var(--wp--preset--shadow--natural)\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13093633\/image-12-scaled.png 2560w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13093633\/image-12-300x153.png 300w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13093633\/image-12-1024x524.png 1024w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13093633\/image-12-768x393.png 768w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13093633\/image-12-1536x786.png 1536w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13093633\/image-12-2048x1047.png 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"2560\" height=\"1314\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13094452\/image-14-scaled.png\" alt=\"\" class=\"wp-image-3689\" style=\"box-shadow:var(--wp--preset--shadow--natural)\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13094452\/image-14-scaled.png 2560w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13094452\/image-14-300x154.png 300w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13094452\/image-14-1024x526.png 1024w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13094452\/image-14-768x394.png 768w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13094452\/image-14-1536x788.png 1536w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13094452\/image-14-2048x1051.png 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><\/figure>\n\n\n\n<p>There are numerous configuration options for a Client within Keycloak, ranging from specific definitions that control the theme used for Login and the various other user interface screens, to available <em>scopes<\/em>, <em>roles<\/em> (from an <span class=\"popup-trigger popmake-1623\" data-popup-id=\"1623\" data-do-default=\"0\">RBAC<\/span> perspective) and the Keycloak <em>events<\/em> that get triggered as part of user interaction. <\/p>\n\n\n\n<p>All of these are beyond the scope of this article, but I will be sharing more regarding these in the future. However, with the information we have, we can now update the <code>env.local<\/code> that Copilot created, as described in step 6 (above), and in doing so, there are a few points to note:<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-accent-4-background-color has-text-color has-background has-link-color wp-elements-efc70605b38b49c06bd651068fa18348 is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:20px\">\n<p class=\"has-text-align-center\" style=\"padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)\"><em>I really like that Copilot created an <code>env.local<\/code> because this is typically a file you <strong>won&#8217;t <\/strong>check into version control. That&#8217;s important because it signifies that there is security-sensitive<\/em> information that should never be tracked outside of the environment in which the application is running! +1 for security-conscious<em> AI! \ud83e\udd16  <\/em><\/p>\n<\/div>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>We haven&#8217;t generated the <code>NEXTAUTH_SECRET<\/code><\/strong>. I&#8217;m not actually sure what this is for \ud83e\udd37\ud83c\udffb\u200d\u2642\ufe0f If you know, please comment below. It will likely be used by NextAuth.js as part of the artefacts \u2014 i.e. cookies, et al \u2014 it generates, and as this is a non-production application, you can leave it as-is for now. Or, alternatively, you can use a site like <a href=\"https:\/\/randomkeygen.com\" target=\"_blank\" rel=\"noreferrer noopener\"><em>RandomKeygen<\/em><\/a> to quickly generate something that can be used as a random secret.<\/li>\n\n\n\n<li><strong>We don&#8217;t yet know the <code>KEYCLOAK_ISSUER<\/code><\/strong>. This is the realm-qualified URL on which Keycloak responds. Depending on how you are employing Keycloak (as discussed in the dependencies section <a href=\"#dependencies\">above<\/a>), this could be anything from <code>http:\/\/localhost:8080\/realms\/myrealm<\/code>, for a locally hosted Docker deployment to something like <code>https:\/\/euc1.auth.ac\/realms\/myrealm<\/code> for a hosted solution.<\/li>\n\n\n\n<li><strong>We don&#8217;t yet have the <code>KEYCLOAK_CLIENT_SECRET<\/code><\/strong>. In fact, we don&#8217;t actually need it at this point. Contrary to the NextAuth.js documentation for Keycloak (<a href=\"https:\/\/next-auth.js.org\/providers\/keycloak\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>), which recommends configuration as a <em><span class=\"popup-trigger popmake-578\" data-popup-id=\"578\" data-do-default=\"0\">confidential client<\/span><\/em>, the latest version of the NextAuth.js SDK I was using seems to treat the application as a <em><span class=\"popup-trigger popmake-582\" data-popup-id=\"582\" data-do-default=\"0\">public client<\/span><\/em> and prefers to use <span class=\"popup-trigger popmake-1895\" data-popup-id=\"1895\" data-do-default=\"0\">PKCE<\/span> (as opposed to the <em>Client Secret<\/em>) when executing Authorization Code Flow. The latest version of Keycloak automatically detects the use of PKCE and adjusts accordingly; if I change the definition of the Client to require <code>Client authentication<\/code> (illustrated in the image below, and the way you tell Keycloak it&#8217;s a confidential client and that it should generate\/require a <em>Client Secret<\/em>), the authentication will fail at the point of code exchange.<\/li>\n<\/ol>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"2560\" height=\"1312\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13115829\/image-13-scaled.png\" alt=\"\" class=\"wp-image-3697\" style=\"box-shadow:var(--wp--preset--shadow--natural)\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13115829\/image-13-scaled.png 2560w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13115829\/image-13-300x154.png 300w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13115829\/image-13-1024x525.png 1024w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13115829\/image-13-768x394.png 768w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13115829\/image-13-1536x787.png 1536w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/13115829\/image-13-2048x1050.png 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><\/figure>\n\n\n<h2 class=\"wp-block-heading\" id=\"authorization-code-flow-in-action\">Authorization Code Flow in Action <\/h2>\n\n\n<p>With everything updated let&#8217;s see the whole thing in action; I&#8217;m going to let my video self take it from here and you can watch the Vibe coded CIAM integration via Authorization Code Flow unfold in my <a href=\"https:\/\/youtu.be\/MQkF9cWYokc\" target=\"_blank\" rel=\"noopener\" title=\"\">accompanying video<\/a> in the <em>Customer Identity<\/em> series. <\/p>\n\n\n\n<p>In the video, the Keycloak installation shown follows the DIY approach to <span class=\"popup-trigger popmake-1185\" data-popup-id=\"1185\" data-do-default=\"0\">CIAM<\/span> that I&#8217;m taking, and as discussed in a previous article entitled <em><strong><a href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/02\/22\/diy-or-buy\/\" target=\"_blank\" rel=\"noreferrer noopener\">Build, Buy or DIY your CIAM Solution<\/a><\/strong><\/em>. I&#8217;m also illustrating aspects of SSO that you can also read more about in another one of my previous articles:<\/p>\n\n\n\n<div class=\"wp-block-group is-content-justification-center is-nowrap is-layout-flex wp-container-core-group-is-layout-23441af8 wp-block-group-is-layout-flex\">\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-discover-ciam wp-block-embed-discover-ciam\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"imWgAs2Jay\"><a href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/03\/05\/the-benefits-of-single-sign-on-sso\/\">The Benefits of SSO in a CIAM Integration<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;The Benefits of SSO in a CIAM Integration&#8221; &#8212; Discover CIAM\" src=\"https:\/\/discovery.cevolution.co.uk\/ciam\/2025\/03\/05\/the-benefits-of-single-sign-on-sso\/embed\/#?secret=KRIQJxrZtN#?secret=imWgAs2Jay\" data-secret=\"imWgAs2Jay\" width=\"500\" height=\"282\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n<\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><span class=\"popup-trigger popmake-2232\" data-popup-id=\"2232\" data-do-default=\"0\">Account Linking<\/span><\/strong>: to ensure that a user profile is consistent, no matter how they choose to authenticate. Account linking also provides users with options should any one of their preferred <span class=\"popup-trigger popmake-2228\" data-popup-id=\"2228\" data-do-default=\"0\">first-factor authentication<\/span> choices \u2014 in this case, upstream Social <span class=\"popup-trigger popmake-415\" data-popup-id=\"415\" data-do-default=\"0\">IdP<\/span>&#8216;s \u2014 be unavailable for whatever reason.<\/li>\n\n\n\n<li><strong><span class=\"popup-trigger popmake-523\" data-popup-id=\"523\" data-do-default=\"0\">Social<\/span> Authentication<\/strong>: to provide a password-free solution as part of the <span class=\"popup-trigger popmake-1437\" data-popup-id=\"1437\" data-do-default=\"0\">login<\/span> process. This isn&#8217;t <a href=\"https:\/\/discovery.cevolution.co.uk\/ciam\/authenticate\/login\/passwordless\/\" data-type=\"page\" data-id=\"1149\" target=\"_blank\" rel=\"noreferrer noopener\">passwordless authentication<\/a> per se \u2014 as a user would experience with the likes of <span class=\"popup-trigger popmake-1879\" data-popup-id=\"1879\" data-do-default=\"0\">Passkeys<\/span> et al \u2014 but instead provides for userid and password processing by an upstream 3rd party <span class=\"popup-trigger popmake-415\" data-popup-id=\"415\" data-do-default=\"0\">IdP<\/span>&#8230;.thus potentially reducing costs whether your are going down the route of leveraging a <span class=\"popup-trigger popmake-1185\" data-popup-id=\"1185\" data-do-default=\"0\">CIAM<\/span> solution provided by a 3rd party SaaS vendor, or taking a DIY approach.<\/li>\n<\/ul>\n\n\n<h2 class=\"wp-block-heading\" id=\"exercises\">Followup Exercises<\/h2>\n\n\n<p>Hopefully, this is just the start of your vibe coding journey \ud83d\ude0e For now though, I&#8217;m going to leave a few follow up exercises for you to consider and &#8220;get your teeth into&#8221;, as it were, at your leisure \u2014 ideally working with the Copilot AI collabroatively to try and solve the challenges:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Removing NextAuth.js Signin\/Signout Screens<\/strong>. By default, the NextAuth SDK provides an integrated UX (User Experience) that isn&#8217;t typically required when leveraging an application-independent <span class=\"popup-trigger popmake-415\" data-popup-id=\"415\" data-do-default=\"0\">IdP<\/span> \u2014 after all, it&#8217;s a best practice for the IdP to provide the UI (User Interface) that offers the choice of <span class=\"popup-trigger popmake-2228\" data-popup-id=\"2228\" data-do-default=\"0\">first-factor<\/span> authenticator rather than this being embedded within the application. Fortunately, this default behaviour can be modified, and the NextAuth.js documentation <a href=\"https:\/\/next-auth.js.org\/configuration\/pages\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a> may well provide a helping hand.<\/li>\n\n\n\n<li><strong>Sign out from the IdP when signing out from the application<\/strong>. As standard, a session will typically remain with the IdP once an application logout occurs; this is often the desired behaviour and a cornerstone of <span class=\"popup-trigger popmake-397\" data-popup-id=\"397\" data-do-default=\"0\">SSO<\/span>. However, it&#8217;s not always the case that this is what&#8217;s required \u2014 with some applications, you may actually want the user to perform interactive <span class=\"popup-trigger popmake-1437\" data-popup-id=\"1437\" data-do-default=\"0\">login<\/span> again once an application logout has occurred.<\/li>\n\n\n\n<li><strong>Locally hosting via <code>HTTPS<\/code><\/strong>. Whilst Next.js defaults to using HTTP when doing local development, it&#8217;s always a recommended best practice to use <span class=\"popup-trigger popmake-3397\" data-popup-id=\"3397\" data-do-default=\"0\">TLS<\/span> \u2014 i.e. HTTPS \u2014 when deploying to production. Fortunately, Next.js does provide an experimental facility to allow you to test locally using HTTPS, so perhaps give that a try to verify that there&#8217;ll be no unforeseen issues when you publish your application; check out the Next.js docs <a href=\"https:\/\/vercel.com\/guides\/access-nextjs-localhost-https-certificate-self-signed\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a> for more details.<\/li>\n\n\n\n<li><strong>Fixing Legacy Behaviour<\/strong>. As discussed in the section <a href=\"#keycloak\">above<\/a>, there&#8217;s a remaining <code>legacyBehaviour<\/code> depreciation notice reported by the Next.js development runtime (illustrated below), which should be a relatively easy fix to accomplish in a vibe-coded style with the aid of Copilot \ud83e\udd17<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"2560\" height=\"1280\" src=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/16171310\/image-15-scaled.png\" alt=\"\" class=\"wp-image-3763\" style=\"box-shadow:var(--wp--preset--shadow--natural)\" srcset=\"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/16171310\/image-15-scaled.png 2560w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/16171310\/image-15-300x150.png 300w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/16171310\/image-15-1024x512.png 1024w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/16171310\/image-15-768x384.png 768w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/16171310\/image-15-1536x768.png 1536w, https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/05\/16171310\/image-15-2048x1024.png 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>Developers frequently inquire about integrating CIAM Authentication via a standard like OIDC. Using Authorization Code Flow as the recommended best practice, this article explores using Vibe coding with Copilot AI to addresses the practical steps required in a Next.js application, leveraging  NextAuth.js and Keycloak as part of the solution design.<\/p>\n","protected":false},"author":1,"featured_media":2415,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"authenticate":"","authentication":"","authenticatedMethod":"","authenticatedMember":"","authorizedPermissions":[],"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"_links_to":"","_links_to_target":""},"categories":[7],"tags":[72,60,28,71,70,18,69,68],"class_list":["post-2351","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-integration","tag-authentication","tag-autorizationcodeflow","tag-keycloak","tag-next","tag-nextauth","tag-oidc","tag-vibe","tag-vibe-coding"],"aioseo_notices":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/discovery-bucket-ha60ib.s3.eu-west-2.amazonaws.com\/wp-content\/uploads\/sites\/22\/2025\/03\/27155405\/create-a-highly-detailed-high-resolution-image-depicting-a-simplified-and-6.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/posts\/2351","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/comments?post=2351"}],"version-history":[{"count":232,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/posts\/2351\/revisions"}],"predecessor-version":[{"id":5637,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/posts\/2351\/revisions\/5637"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/media\/2415"}],"wp:attachment":[{"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/media?parent=2351"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/categories?post=2351"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/discovery.cevolution.co.uk\/ciam\/wp-json\/wp\/v2\/tags?post=2351"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}