Jekyll2022-10-10T02:01:14+00:00https://sdoxsee.github.io/atom.xmlStephen Doxsee’s BlogTechnical musings behind the work of <a href='https://simplestep.ca'>Simple Step Solutions</a>.Stephen DoxseeEnvironment Variable Generator for Spring Boot2021-03-30T00:00:00+00:002021-03-30T00:00:00+00:00https://sdoxsee.github.io/blog/2021/03/30/environment-variable-generator-for-spring-boot<p>I like setting and overriding Spring Boot app configuration using environment variables.</p>
<p>I got tired of creating them by hand, so I created a little <a href="https://env.simplestep.ca">web app</a> to generate them from Spring Boot YAML according to the relaxed-binding naming rules.</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">foo-bar</span><span class="pi">:</span>
<span class="na">baz</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">value1</span>
<span class="pi">-</span> <span class="s">value2</span>
<span class="na">enabled</span><span class="pi">:</span> <span class="no">false</span></code></pre></figure>
<p>becomes</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">FOOBAR_BAZ_0_</span><span class="o">=</span>value1
<span class="nv">FOOBAR_BAZ_1_</span><span class="o">=</span>value2
<span class="nv">FOOBAR_ENABLED</span><span class="o">=</span><span class="nb">false</span></code></pre></figure>
<p>Please try it and share it!</p>
<blockquote>
<p><a href="https://env.simplestep.ca">https://env.simplestep.ca</a></p>
</blockquote>
<p>Hope it helps you too!</p>stephenI like setting and overriding Spring Boot app configuration using environment variables.Multi-tenant OAuth 2.0 Resource Servers (with Spring Security 5)2021-03-22T00:00:00+00:002021-03-22T00:00:00+00:00https://sdoxsee.github.io/blog/2021/03/22/multi-tenant-oauth-2.0-resource-servers<h2 id="tldr">TLDR;</h2>
<p>Check out the <a href="https://github.com/sdoxsee/examples/tree/master/multi-tenant-jwt-resourceserver">github repo</a>.</p>
<div id="entry-table-of-contents" class="toc-wrapper">
<h2 id="toc-toggle" class="no_toc">
Table of Contents <i class="toc-toggle-icon fas fa-chevron-down"></i>
</h2>
<ol id="markdown-toc">
<li><a href="#tldr" id="markdown-toc-tldr">TLDR;</a></li>
<li><a href="#user-pools-and-tenants" id="markdown-toc-user-pools-and-tenants">User pools and tenants</a></li>
<li><a href="#when-to-use-more-than-one-tenant" id="markdown-toc-when-to-use-more-than-one-tenant">When to use more than one tenant</a></li>
<li><a href="#challenges-of-supporting-multitenancy" id="markdown-toc-challenges-of-supporting-multitenancy">Challenges of supporting multitenancy</a></li>
<li><a href="#github-examples" id="markdown-toc-github-examples">Github examples</a></li>
<li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ol>
</div>
<p>It used to be common for organizations to have only internal users that would access systems and software on-premise. With the advent of mobile devices and the explosion of external-facing web applications, those norms have been disrupted.</p>
<p>Organizations differentiate themselves by offering their external users (e.g. customers) the ability to self-serve–often with their own user accounts and applications that pull data from the same source systems that internal users would work from.</p>
<h2 id="user-pools-and-tenants">User pools and tenants</h2>
<p>It’s often advantageous for both internal and external users to access the organization’s data from the same APIs. Depending on how users are setup, you may have internal and external users in the same pool of users–differentiated only by roles or the applications they are associated with. Another common approach is to have internal and external users in completely separate user pools.</p>
<p>In IAM (Identity and Access Management), a single “identity provider” (or “tenant”) is associated with a single user pool. If you have a single user pool for both your internal and external users, you may experience a challenge if those users can be both internal and external. Take a group benefits company, for example, that uses itself for group benefits. You have internal users in sales, new business, customer service, etc. You also have external users that act as the plan administrator for the company and the plan members themselves. In this case, there would be overlap between the two logical user groups. Do you really want users to sign in with their regular plan member “hat” on with the same credentials they use when they’re wearing their customer service “hat”? Perhaps, but it might make sense to keep such user pools separate.</p>
<p><img src="/assets/images/multi-tenant-oauth-2.0-resource-servers/multi-tenant.svg" alt="multi-tenant" /></p>
<h2 id="when-to-use-more-than-one-tenant">When to use more than one tenant</h2>
<p>It really depends. The above scenario is perhaps a case for a multi-tenant solution. Another might be where you offer whitelabeled SaaS software and your customers have customers themselves (e.g. think Shopify, etc.). You probably don’t want to mix those user pools! FusionAuth (a pretty decent identity provider for small to medium sized organizations), has some worthwhile <a href="https://fusionauth.io/learn/expert-advice/identity-basics/multi-tenancy-vs-single-tenant-idaas-solutions/">articles</a> on the subject.</p>
<p>Because of the complexities involved, you may want to seek some guidance with your IAM architecture–if so so, feel free to get in touch. It’s certainly not one-size-fits-all and it’s something that you want to get right. There are tradeoffs and nuances that need to be considered and, as the organization evolves, so too may your IAM decisions. It’s also important to understand where the multitenancy, if any, is taking place. Is it at the Client, the authorization server itself, the resource server only?</p>
<blockquote>
<p>Reasons for multitenancy will vary but the crux of multitenancy is dealing with separate user pools.</p>
</blockquote>
<h2 id="challenges-of-supporting-multitenancy">Challenges of supporting multitenancy</h2>
<p>Modern APIs are protected by means of OAuth 2.0. authorization servers (i.e. the identity provider, issuer, or tenant) commonly grant signed JWT access tokens to client applications once a user has authorized the client to make request on their behalf. Client applications can then make requests to resource servers (i.e APIs) by including the access token as a header. However, if you’re building APIs for core organizational services where you have users that come from different tenants, the issuer of the access tokens will be different and the user identifiers (usually the <code class="language-plaintext highlighter-rouge">sub</code> claim) in the tokens will come from completely different user pools. The main problem of tokens coming from different tenant issuers is that the resource server (or API) must first inspect the <code class="language-plaintext highlighter-rouge">iss</code> claim (i.e. the issuer) in the JWT to find out which public key to use to validate the signature on the token.</p>
<p>Many frameworks, whether in Java, Node, Rails, etc., have <em>basic</em> OAuth 2.0 support.</p>
<p><img src="/assets/images/multi-tenant-oauth-2.0-resource-servers/spring.svg" alt="Spring Security" /></p>
<p>Spring Security 5 has <em>excellent</em> support for OAuth 2.0. The default autoconfiguration for Spring Boot OAuth 2.0 resource server easily handles token validation for single tenant scenarios by leveraging the <code class="language-plaintext highlighter-rouge">spring.security.oauth2.resourceserver.jwt.issuer-uri</code> property.</p>
<p>But what if you have multiple tenants?</p>
<p>Spring Security’s suppport for resource server multitenancy is decent and evolving. They provide some <a href="https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2resourceserver-multitenancy">guidance</a> on options for implementing it if you need it. Unfortunately, there are some typos in the code snippets, and while code snippets are helpful, it still takes time to set things up–as there’s no working git repository example (that I know of) that puts all the pieces together–especially if you want to avoid parsing the JWT twice on every request!</p>
<h2 id="github-examples">Github examples</h2>
<p>That’s why I created an <a href="https://github.com/sdoxsee/examples/tree/master/multi-tenant-jwt-resourceserver">example repo</a> that can be cloned and illustrates the two competing approaches they outline for multitenancy on OAuth 2.0 resource servers. I won’t repeat the great <a href="https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2resourceserver-multitenancy">documentation</a> but essentially they have a simple (but less efficient) approach and a more complex (but more efficient and configurable) approach. I provide a multimodule project so that you can try out both.</p>
<p>You’ll, of course, need two tenants or OAuth 2.0 authorization servers–I use Google and Auth0 in the example, but you can use whatever you want. Why not try two separate tenants on Okta and create an OAuth 2.0 app on each. <a href="https://developer.okta.com/docs/guides/implement-oauth-for-okta/create-oauth-app/">Here’s how</a>.</p>
<p>Once you’ve created your two tenants, run either the simple or complex API by <code class="language-plaintext highlighter-rouge">cd</code>ing into your chosen module and running the following:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">APP_ISSUER_0_</span><span class="o">=</span>https://yourprovider.com/tenant0 <span class="se">\</span>
<span class="nv">APP_ISSUER_1_</span><span class="o">=</span>https://yourprovider.com/tenant1 <span class="se">\</span>
mvn spring-boot:run</code></pre></figure>
<p>You should get a 401 at <a href="http://localhost:8080">http://localhost:8080</a>–indicating your API is secured.</p>
<p><img src="/assets/images/multi-tenant-oauth-2.0-resource-servers/401.png" alt="401" /></p>
<p>Once you’ve finish your client registrations, use curl, Postman, or <a href="https://insomnia.rest/">Insomnia</a> as your OAuth 2.0 2.0 Client to fire requests at the example resource servers. Configure your client with the client id, client secret, redirect/callback URL, authorization and token endpoints, as well as any scopes you’ve defined. Depending on your choice of authorization server, you’ll also need to register an API with scopes that your client will need to reference in order to obtain tokens. (If this is confusing, please drop a comment and I can unpack this more)</p>
<p>Anyway, once you’re able to obtain access tokens, requests made with tokens issued from either tenant should give you 200 “hello” response. Simply set the <code class="language-plaintext highlighter-rouge">Authorization: Bearer <JWT></code> header with your JWT access token.</p>
<blockquote>
<p><strong>Tip</strong>: If your access token is a JWT, it will start with <code class="language-plaintext highlighter-rouge">ey...</code></p>
</blockquote>
<p><img src="/assets/images/multi-tenant-oauth-2.0-resource-servers/insomnia200.png" alt="401" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>I hope that’s been helpful and puts you a step ahead in handling multitenancy on your OAuth 2.0 resource server APIs.</p>stephenTLDR;Cat’s Nest! OpenID Connect Authentication with NestJS, React, and MongoDB2020-02-05T00:00:00+00:002020-02-05T00:00:00+00:00https://sdoxsee.github.io/blog/2020/02/05/cats-nest-nestjs-mongo-oidc<p>In this post, we’ll explore how to create a NestJS back-end that handles OpenID Connect authentication for a React app that it serves up with an express-session. The session store will share the MongoDB instance that is also used for storing cats.</p>
<p class="notice"><strong>Disclaimer</strong>: I’m not a cat guy. I’m using cats for this tutorial because the NestJS <a href="https://docs.nestjs.com/techniques/mongodb">documentation</a> did. However, I did take the picture for this post when we discovered 5 wild baby cats in our backyard! They’ve all since found loving homes :)</p>
<div id="entry-table-of-contents" class="toc-wrapper">
<h2 id="toc-toggle" class="no_toc">
Table of Contents <i class="toc-toggle-icon fas fa-chevron-down"></i>
</h2>
<ol id="markdown-toc">
<li><a href="#quickstart" id="markdown-toc-quickstart">Quickstart</a> <ol>
<li><a href="#start-the-back-end" id="markdown-toc-start-the-back-end">Start the back-end</a></li>
<li><a href="#start-the-front-end" id="markdown-toc-start-the-front-end">Start the front-end</a></li>
<li><a href="#try-it-out" id="markdown-toc-try-it-out">Try it out</a></li>
</ol>
</li>
<li><a href="#why-nestjs" id="markdown-toc-why-nestjs">Why NestJS</a></li>
<li><a href="#create-the-back-end" id="markdown-toc-create-the-back-end">Create the back-end</a> <ol>
<li><a href="#add-authmodule" id="markdown-toc-add-authmodule">Add <code class="language-plaintext highlighter-rouge">AuthModule</code></a></li>
<li><a href="#add-dependencies" id="markdown-toc-add-dependencies">Add dependencies</a></li>
<li><a href="#add-oidcstrategy" id="markdown-toc-add-oidcstrategy">Add <code class="language-plaintext highlighter-rouge">OidcStrategy</code></a> <ol>
<li><a href="#create-loginguard-to-handle-the-oidc-dance" id="markdown-toc-create-loginguard-to-handle-the-oidc-dance">Create <code class="language-plaintext highlighter-rouge">LoginGuard</code> to handle the OIDC dance</a></li>
<li><a href="#replace-authcontroller-with-this" id="markdown-toc-replace-authcontroller-with-this">Replace <code class="language-plaintext highlighter-rouge">AuthController</code> with this</a></li>
<li><a href="#add-sessionserializer" id="markdown-toc-add-sessionserializer">Add <code class="language-plaintext highlighter-rouge">SessionSerializer</code></a></li>
<li><a href="#update-authmodule" id="markdown-toc-update-authmodule">Update <code class="language-plaintext highlighter-rouge">AuthModule</code></a></li>
</ol>
</li>
<li><a href="#update-appmodule" id="markdown-toc-update-appmodule">Update <code class="language-plaintext highlighter-rouge">AppModule</code></a></li>
<li><a href="#add-some-cat-crud" id="markdown-toc-add-some-cat-crud">Add some Cat CRUD</a> <ol>
<li><a href="#add-authenticatedguard" id="markdown-toc-add-authenticatedguard">Add <code class="language-plaintext highlighter-rouge">AuthenticatedGuard</code></a></li>
<li><a href="#add-catsmodule" id="markdown-toc-add-catsmodule">Add <code class="language-plaintext highlighter-rouge">CatsModule</code></a></li>
<li><a href="#configure-mongodb" id="markdown-toc-configure-mongodb">Configure MongoDB</a></li>
</ol>
</li>
</ol>
</li>
<li><a href="#create-the-front-end" id="markdown-toc-create-the-front-end">Create the front-end</a> <ol>
<li><a href="#add-reactstrap" id="markdown-toc-add-reactstrap">Add reactstrap</a></li>
<li><a href="#add-cat-interface" id="markdown-toc-add-cat-interface">Add <code class="language-plaintext highlighter-rouge">Cat</code> interface</a></li>
<li><a href="#add-catstable-component" id="markdown-toc-add-catstable-component">Add <code class="language-plaintext highlighter-rouge">CatsTable</code> component</a></li>
<li><a href="#replace-apptsx-with" id="markdown-toc-replace-apptsx-with">Replace <code class="language-plaintext highlighter-rouge">App.tsx</code> with…</a></li>
<li><a href="#proxy-requests-to-back-end-in-development" id="markdown-toc-proxy-requests-to-back-end-in-development">Proxy requests to back-end in development</a></li>
</ol>
</li>
<li><a href="#start-everything-up" id="markdown-toc-start-everything-up">Start everything up</a></li>
<li><a href="#serve-static-resources-in-production" id="markdown-toc-serve-static-resources-in-production">Serve static resources in production</a></li>
<li><a href="#references" id="markdown-toc-references">References</a></li>
<li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ol>
</div>
<h1 id="quickstart">Quickstart</h1>
<p>The source can be found at <a href="https://github.com/sdoxsee/cats-nest">https://github.com/sdoxsee/cats-nest</a></p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">git clone git@github.com:sdoxsee/cats-nest.git
<span class="nb">cd </span>cats-nest</code></pre></figure>
<h2 id="start-the-back-end">Start the back-end</h2>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">cd </span>back-end
npm <span class="nb">install</span></code></pre></figure>
<p>Add `.env’ like this. I realize I’m sharing credentials for a Google OAuth 2.0 Client and a MongoDB user but that’s just for you to test this out easily. Please feel free create/use your own.</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER</span><span class="o">=</span>https://accounts.google.com
<span class="nv">OAUTH2_CLIENT_REGISTRATION_LOGIN_CLIENT_ID</span><span class="o">=</span>339164422929-m219hbgevdrkoajb2s38sr5gl8vk1nc8.apps.googleusercontent.com
<span class="nv">OAUTH2_CLIENT_REGISTRATION_LOGIN_CLIENT_SECRET</span><span class="o">=</span>XZHJ_WS1pdAgkwW5U5zFQZZd
<span class="nv">OAUTH2_CLIENT_REGISTRATION_LOGIN_SCOPE</span><span class="o">=</span>openid profile
<span class="nv">OAUTH2_CLIENT_REGISTRATION_LOGIN_REDIRECT_URI</span><span class="o">=</span>http://localhost:3001/callback
<span class="nv">OAUTH2_CLIENT_REGISTRATION_LOGIN_POST_LOGOUT_REDIRECT_URI</span><span class="o">=</span>http://localhost:3001
<span class="nv">SESSION_SECRET</span><span class="o">=</span>super+secret+session+key
<span class="nv">MONGODB_URL</span><span class="o">=</span>mongodb+srv://kitty:cat@simplestep-cluster.fypyc.mongodb.net/myFirstDatabase?retryWrites<span class="o">=</span><span class="nb">true</span>&w<span class="o">=</span>majority</code></pre></figure>
<p>Now you can start the backend on port 3000.</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">npm run start:dev</code></pre></figure>
<h2 id="start-the-front-end">Start the front-end</h2>
<p>In a new terminal</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">cd </span>front-end
npm <span class="nb">install
</span><span class="nv">PORT</span><span class="o">=</span>3001 npm start</code></pre></figure>
<h2 id="try-it-out">Try it out</h2>
<p>Your browser will open to <code class="language-plaintext highlighter-rouge">http://localhost:3001</code>. Click <code class="language-plaintext highlighter-rouge">Login</code>, sign in with your google credentials, give consent to “Cat’s Nest”, and you’ll see your name and any cats that are in the database!</p>
<p><img src="/assets/images/cats-nest/screenshot.png" alt="Cat's Nest Screenshot" /></p>
<p>Use <a href="https://httpie.org/">https://httpie.org/</a> to create/read/update cat records. Here are some examples</p>
<ul>
<li>Create: <code class="language-plaintext highlighter-rouge">http -f POST localhost:3000/cats name=Gladiator age=4 breed=General</code></li>
<li>Read: <code class="language-plaintext highlighter-rouge">http localhost:3000/cats</code></li>
<li>Update: <code class="language-plaintext highlighter-rouge">http -f PUT localhost:3000/cats/5e3859ab9ffdd4d02913e0fc name=Maximus</code></li>
</ul>
<p class="notice"><strong>Note</strong>: You’ll need to temporarily comment out <code class="language-plaintext highlighter-rouge">@UseGuards(AuthenticatedGuard)</code> on <code class="language-plaintext highlighter-rouge">CatsController</code> if you want to run the commands above since you’re not authenticated.</p>
<h1 id="why-nestjs">Why NestJS</h1>
<p>JavaScript development can be a bit like the wild west. There are lots of ways to skin a cat (sorry, bad joke). I’ve done a fair bit on the front-end with JavaScript before but not so much on the back-end. NestJS promises to bring best-practice architecture and consistency to Node applications. It actually feels a lot like Spring Boot development (which is a win from my perspective).</p>
<h1 id="create-the-back-end">Create the back-end</h1>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">npm i <span class="nt">-g</span> @nestjs/cli
<span class="nb">mkdir </span>cats-nest <span class="o">&&</span> <span class="nb">cd </span>cats-nest
nest new back-end <span class="o">&&</span> <span class="nb">cd </span>back-end</code></pre></figure>
<p>If you run <code class="language-plaintext highlighter-rouge">npm run start:dev</code> you’ll see “Hello, World!” on <code class="language-plaintext highlighter-rouge">http://localhost:3000</code>! Cool, but we’ll want to go further than that ;) Let’s start working on Authentication</p>
<h2 id="add-authmodule">Add <code class="language-plaintext highlighter-rouge">AuthModule</code></h2>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">nest g module auth
nest g controller auth
nest g service auth</code></pre></figure>
<p class="notice">We won’t be using <code class="language-plaintext highlighter-rouge">AuthService</code> in this tutorial but we’re creating an empty one as this is where you’d probably check the Identity Provider’s user against what you care about in your own database.</p>
<h2 id="add-dependencies">Add dependencies</h2>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">npm i <span class="nt">--save</span> @nestjs/passport passport openid-client @nestjs/config express-session @nestjs/mongoose mongoose connect-mongo</code></pre></figure>
<p>Here’s what those dependencies are about:</p>
<ul>
<li>@nestjs/passport - NestJS modules for working with passport</li>
<li>passport - Passport itself–authentication middleware for Node.js</li>
<li>openid-client - OIDC certified client library with a passport strategy</li>
<li>@nestjs/config - NestJS configuration support</li>
<li>express-session - For a session-based application</li>
<li>@nestjs/mongoose - NestJS modules for working with mongoose</li>
<li>mongoose - Mongoose itself–Node.js ODM for MongoDB</li>
<li>connect-mongo - Express session store that uses MongoDB</li>
</ul>
<h2 id="add-oidcstrategy">Add <code class="language-plaintext highlighter-rouge">OidcStrategy</code></h2>
<p>Let’s create an OpenID Connect passport strategy, based on the <code class="language-plaintext highlighter-rouge">node-openid-client</code> project.</p>
<figure class="highlight"><pre><code class="language-ts" data-lang="ts"><span class="c1">// auth/oidc.strategy.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">UnauthorizedException</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PassportStrategy</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/passport</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Strategy</span><span class="p">,</span> <span class="nx">Client</span><span class="p">,</span> <span class="nx">UserinfoResponse</span><span class="p">,</span> <span class="nx">TokenSet</span><span class="p">,</span> <span class="nx">Issuer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">openid-client</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./auth.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">buildOpenIdClient</span> <span class="o">=</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">TrustIssuer</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Issuer</span><span class="p">.</span><span class="nx">discover</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER</span><span class="p">}</span><span class="s2">/.well-known/openid-configuration`</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TrustIssuer</span><span class="p">.</span><span class="nx">Client</span><span class="p">({</span>
<span class="na">client_id</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">OAUTH2_CLIENT_REGISTRATION_LOGIN_CLIENT_ID</span><span class="p">,</span>
<span class="na">client_secret</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">OAUTH2_CLIENT_REGISTRATION_LOGIN_CLIENT_SECRET</span><span class="p">,</span>
<span class="p">});</span>
<span class="k">return</span> <span class="nx">client</span><span class="p">;</span>
<span class="p">};</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">OidcStrategy</span> <span class="kd">extends</span> <span class="nx">PassportStrategy</span><span class="p">(</span><span class="nx">Strategy</span><span class="p">,</span> <span class="dl">'</span><span class="s1">oidc</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nl">client</span><span class="p">:</span> <span class="nx">Client</span><span class="p">;</span>
<span class="kd">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="nx">authService</span><span class="p">:</span> <span class="nx">AuthService</span><span class="p">,</span> <span class="nx">client</span><span class="p">:</span> <span class="nx">Client</span><span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">({</span>
<span class="na">client</span><span class="p">:</span> <span class="nx">client</span><span class="p">,</span>
<span class="na">params</span><span class="p">:</span> <span class="p">{</span>
<span class="na">redirect_uri</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">OAUTH2_CLIENT_REGISTRATION_LOGIN_REDIRECT_URI</span><span class="p">,</span>
<span class="na">scope</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">OAUTH2_CLIENT_REGISTRATION_LOGIN_SCOPE</span><span class="p">,</span>
<span class="p">},</span>
<span class="na">passReqToCallback</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">usePKCE</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="p">});</span>
<span class="k">this</span><span class="p">.</span><span class="nx">client</span> <span class="o">=</span> <span class="nx">client</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">validate</span><span class="p">(</span><span class="nx">tokenset</span><span class="p">:</span> <span class="nx">TokenSet</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="kr">any</span><span class="o">></span> <span class="p">{</span>
<span class="kd">const</span> <span class="na">userinfo</span><span class="p">:</span> <span class="nx">UserinfoResponse</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">client</span><span class="p">.</span><span class="nx">userinfo</span><span class="p">(</span><span class="nx">tokenset</span><span class="p">);</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">id_token</span> <span class="o">=</span> <span class="nx">tokenset</span><span class="p">.</span><span class="nx">id_token</span>
<span class="kd">const</span> <span class="nx">access_token</span> <span class="o">=</span> <span class="nx">tokenset</span><span class="p">.</span><span class="nx">access_token</span>
<span class="kd">const</span> <span class="nx">refresh_token</span> <span class="o">=</span> <span class="nx">tokenset</span><span class="p">.</span><span class="nx">refresh_token</span>
<span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="p">{</span>
<span class="nx">id_token</span><span class="p">,</span>
<span class="nx">access_token</span><span class="p">,</span>
<span class="nx">refresh_token</span><span class="p">,</span>
<span class="nx">userinfo</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">user</span><span class="p">;</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">UnauthorizedException</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="create-loginguard-to-handle-the-oidc-dance">Create <code class="language-plaintext highlighter-rouge">LoginGuard</code> to handle the OIDC dance</h3>
<figure class="highlight"><pre><code class="language-ts" data-lang="ts"><span class="c1">// src/auth/login.guard.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ExecutionContext</span><span class="p">,</span> <span class="nx">Injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthGuard</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/passport</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">LoginGuard</span> <span class="kd">extends</span> <span class="nx">AuthGuard</span><span class="p">(</span><span class="dl">'</span><span class="s1">oidc</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">async</span> <span class="nx">canActivate</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">ExecutionContext</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="p">(</span><span class="k">await</span> <span class="k">super</span><span class="p">.</span><span class="nx">canActivate</span><span class="p">(</span><span class="nx">context</span><span class="p">))</span> <span class="k">as</span> <span class="nx">boolean</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">request</span> <span class="o">=</span> <span class="nx">context</span><span class="p">.</span><span class="nx">switchToHttp</span><span class="p">().</span><span class="nx">getRequest</span><span class="p">();</span>
<span class="k">await</span> <span class="k">super</span><span class="p">.</span><span class="nx">logIn</span><span class="p">(</span><span class="nx">request</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="replace-authcontroller-with-this">Replace <code class="language-plaintext highlighter-rouge">AuthController</code> with this</h3>
<figure class="highlight"><pre><code class="language-ts" data-lang="ts"><span class="c1">// auth/auth.controller.ts</span>
<span class="k">import</span> <span class="p">{</span>
<span class="nx">Controller</span><span class="p">,</span>
<span class="nx">Get</span><span class="p">,</span>
<span class="nx">Request</span><span class="p">,</span>
<span class="nx">Res</span><span class="p">,</span>
<span class="nx">UseGuards</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Response</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">LoginGuard</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./login.guard</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Issuer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">openid-client</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Controller</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthController</span> <span class="p">{</span>
<span class="p">@</span><span class="nd">UseGuards</span><span class="p">(</span><span class="nx">LoginGuard</span><span class="p">)</span>
<span class="p">@</span><span class="nd">Get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/login</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">login</span><span class="p">()</span> <span class="p">{}</span>
<span class="p">@</span><span class="nd">Get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/user</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">user</span><span class="p">(@</span><span class="nd">Request</span><span class="p">()</span> <span class="nx">req</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">UseGuards</span><span class="p">(</span><span class="nx">LoginGuard</span><span class="p">)</span>
<span class="p">@</span><span class="nd">Get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/callback</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">loginCallback</span><span class="p">(@</span><span class="nd">Res</span><span class="p">()</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">redirect</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">@</span><span class="nd">Get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/logout</span><span class="dl">'</span><span class="p">)</span>
<span class="k">async</span> <span class="nx">logout</span><span class="p">(@</span><span class="nd">Request</span><span class="p">()</span> <span class="nx">req</span><span class="p">,</span> <span class="p">@</span><span class="nd">Res</span><span class="p">()</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">id_token</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span> <span class="p">?</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">id_token</span> <span class="p">:</span> <span class="kc">undefined</span><span class="p">;</span>
<span class="nx">req</span><span class="p">.</span><span class="nx">logout</span><span class="p">();</span>
<span class="nx">req</span><span class="p">.</span><span class="nx">session</span><span class="p">.</span><span class="nx">destroy</span><span class="p">(</span><span class="k">async</span> <span class="p">(</span><span class="nx">error</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">TrustIssuer</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Issuer</span><span class="p">.</span><span class="nx">discover</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER</span><span class="p">}</span><span class="s2">/.well-known/openid-configuration`</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">end_session_endpoint</span> <span class="o">=</span> <span class="nx">TrustIssuer</span><span class="p">.</span><span class="nx">metadata</span><span class="p">.</span><span class="nx">end_session_endpoint</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">end_session_endpoint</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">redirect</span><span class="p">(</span><span class="nx">end_session_endpoint</span> <span class="o">+</span>
<span class="dl">'</span><span class="s1">?post_logout_redirect_uri=</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">OAUTH2_CLIENT_REGISTRATION_LOGIN_POST_LOGOUT_REDIRECT_URI</span> <span class="o">+</span>
<span class="p">(</span><span class="nx">id_token</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">&id_token_hint=</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">id_token</span> <span class="p">:</span> <span class="dl">''</span><span class="p">));</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">redirect</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>The two endpoints annotated with <code class="language-plaintext highlighter-rouge">@UseGuards(LoginGuard)</code> are those involved in the two kinds of authentication OAuth 2.0 authorization code flow requires (i.e. user and client)</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">/login</code>: redirects to authorization endpoint of Identity Provider for front-channel <em>user authentication</em></li>
<li><code class="language-plaintext highlighter-rouge">/callback</code>: receives the <code class="language-plaintext highlighter-rouge">code</code> grant from the Identity Provider and exchanges it, back-channel, for an <code class="language-plaintext highlighter-rouge">id_token</code> by means of <em>client credential authentication</em></li>
</ol>
<p>The <code class="language-plaintext highlighter-rouge">/logout</code> endpoint provides a way of using the <code class="language-plaintext highlighter-rouge">end_session_endpoint</code> if such an endpoint is discovered while the <code class="language-plaintext highlighter-rouge">req.user</code> object returned at the <code class="language-plaintext highlighter-rouge">/user</code> endpoint will be populated automatically by passport once the user is authenticated.</p>
<h3 id="add-sessionserializer">Add <code class="language-plaintext highlighter-rouge">SessionSerializer</code></h3>
<p>The way passport populates the <code class="language-plaintext highlighter-rouge">req.user</code> is by using a <code class="language-plaintext highlighter-rouge">PassportSerializer</code>. Passport serializes and deserializes user instances to and from the session using a <code class="language-plaintext highlighter-rouge">PassportSerializer</code>. Since we’re only using the user from the Identity Provider, we have a vanilla serializer that uses the user “as is”.</p>
<figure class="highlight"><pre><code class="language-ts" data-lang="ts"><span class="c1">// src/auth/session.serializer.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PassportSerializer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/passport</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Injectable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">SessionSerializer</span> <span class="kd">extends</span> <span class="nx">PassportSerializer</span> <span class="p">{</span>
<span class="nx">serializeUser</span><span class="p">(</span><span class="nx">user</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">done</span><span class="p">:</span> <span class="p">(</span><span class="nx">err</span><span class="p">:</span> <span class="nb">Error</span><span class="p">,</span> <span class="nx">user</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="k">void</span><span class="p">):</span> <span class="kr">any</span> <span class="p">{</span>
<span class="nx">done</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="nx">user</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">deserializeUser</span><span class="p">(</span><span class="nx">payload</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">done</span><span class="p">:</span> <span class="p">(</span><span class="nx">err</span><span class="p">:</span> <span class="nb">Error</span><span class="p">,</span> <span class="nx">payload</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=></span> <span class="k">void</span><span class="p">):</span> <span class="kr">any</span> <span class="p">{</span>
<span class="nx">done</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="nx">payload</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="update-authmodule">Update <code class="language-plaintext highlighter-rouge">AuthModule</code></h3>
<p>Let’s let our <code class="language-plaintext highlighter-rouge">AuthModule</code> know about our strategy, guard, and serializer.</p>
<figure class="highlight"><pre><code class="language-ts" data-lang="ts"><span class="c1">// src/auth/auth.module.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Module</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">PassportModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/passport</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">OidcStrategy</span><span class="p">,</span> <span class="nx">buildOpenIdClient</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./oidc.strategy</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">SessionSerializer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./session.serializer</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./auth.service</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthController</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./auth.controller</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">OidcStrategyFactory</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">provide</span><span class="p">:</span> <span class="dl">'</span><span class="s1">OidcStrategy</span><span class="dl">'</span><span class="p">,</span>
<span class="na">useFactory</span><span class="p">:</span> <span class="k">async</span> <span class="p">(</span><span class="na">authService</span><span class="p">:</span> <span class="nx">AuthService</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">buildOpenIdClient</span><span class="p">();</span> <span class="c1">// secret sauce! build the dynamic client before injecting it into the strategy for use in the constructor super call.</span>
<span class="kd">const</span> <span class="nx">strategy</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OidcStrategy</span><span class="p">(</span><span class="nx">authService</span><span class="p">,</span> <span class="nx">client</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">strategy</span><span class="p">;</span>
<span class="p">},</span>
<span class="na">inject</span><span class="p">:</span> <span class="p">[</span><span class="nx">AuthService</span><span class="p">]</span>
<span class="p">};</span>
<span class="p">@</span><span class="nd">Module</span><span class="p">({</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span>
<span class="nx">PassportModule</span><span class="p">.</span><span class="nx">register</span><span class="p">({</span> <span class="na">session</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">defaultStrategy</span><span class="p">:</span> <span class="dl">'</span><span class="s1">oidc</span><span class="dl">'</span> <span class="p">}),</span>
<span class="p">],</span>
<span class="na">controllers</span><span class="p">:</span> <span class="p">[</span><span class="nx">AuthController</span><span class="p">],</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span><span class="nx">OidcStrategyFactory</span><span class="p">,</span> <span class="nx">SessionSerializer</span><span class="p">,</span> <span class="nx">AuthService</span><span class="p">],</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthModule</span> <span class="p">{}</span></code></pre></figure>
<h2 id="update-appmodule">Update <code class="language-plaintext highlighter-rouge">AppModule</code></h2>
<p>And let’s be sure to let our <code class="language-plaintext highlighter-rouge">AppModule</code> know about our ConfigModule so that we can use our <code class="language-plaintext highlighter-rouge">.env</code> config file!</p>
<figure class="highlight"><pre><code class="language-ts" data-lang="ts"><span class="c1">// app.module.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Module</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AuthModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./auth/auth.module</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ConfigModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/config</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AppController</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./app.controller</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AppService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./app.service</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Module</span><span class="p">({</span>
<span class="na">imports</span><span class="p">:</span> <span class="p">[</span>
<span class="nx">ConfigModule</span><span class="p">.</span><span class="nx">forRoot</span><span class="p">(),</span> <span class="c1">// so that we can pull in config</span>
<span class="nx">AuthModule</span><span class="p">,</span>
<span class="p">],</span>
<span class="na">controllers</span><span class="p">:</span> <span class="p">[</span><span class="nx">AppController</span><span class="p">],</span>
<span class="na">providers</span><span class="p">:</span> <span class="p">[</span><span class="nx">AppService</span><span class="p">],</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">AppModule</span> <span class="p">{}</span></code></pre></figure>
<p>If you remember when we started our app originally, we just saw “Hello World!”. Let’s go to our AppController to add a link to login/logout and, if we have a user, display the user’s name instead!</p>
<figure class="highlight"><pre><code class="language-ts" data-lang="ts"><span class="k">import</span> <span class="p">{</span> <span class="nx">Controller</span><span class="p">,</span> <span class="nx">Get</span><span class="p">,</span> <span class="nx">Request</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AppService</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./app.service</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Controller</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">AppController</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="nx">appService</span><span class="p">:</span> <span class="nx">AppService</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">@</span><span class="nd">Get</span><span class="p">()</span>
<span class="nx">getHello</span><span class="p">(@</span><span class="nd">Request</span><span class="p">()</span> <span class="nx">req</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">user</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="dl">'</span><span class="s1">Hello, </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">userinfo</span><span class="p">.</span><span class="nx">name</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">! <a href="/logout">Logout</a></span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">appService</span><span class="p">.</span><span class="nx">getHello</span><span class="p">()</span> <span class="o">+</span> <span class="dl">'</span><span class="s1"> <a href="/login">Login</a></span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Finally, we need to replace <code class="language-plaintext highlighter-rouge">main.ts</code> with the following to configure our session and to use passport with it.</p>
<figure class="highlight"><pre><code class="language-ts" data-lang="ts"><span class="c1">// src/main.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">NestFactory</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/core</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">AppModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./app.module</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">session</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express-session</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">passport</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">passport</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">connectMongo</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">connect-mongo</span><span class="dl">'</span><span class="p">;</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">bootstrap</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">NestFactory</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="nx">AppModule</span><span class="p">);</span>
<span class="c1">// https://stackoverflow.com/a/39052956/1098564</span>
<span class="kd">const</span> <span class="nx">MongoStore</span> <span class="o">=</span> <span class="nx">connectMongo</span><span class="p">(</span><span class="nx">session</span><span class="p">);</span>
<span class="c1">// Authentication & Session</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">session</span><span class="p">({</span>
<span class="na">store</span><span class="p">:</span> <span class="k">new</span> <span class="nx">MongoStore</span><span class="p">({</span> <span class="na">url</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">MONGODB_URL</span><span class="p">}),</span> <span class="c1">// where session will be stored</span>
<span class="na">secret</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">SESSION_SECRET</span><span class="p">,</span> <span class="c1">// to sign session id</span>
<span class="na">resave</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1">// will default to false in near future: https://github.com/expressjs/session#resave</span>
<span class="na">saveUninitialized</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1">// will default to false in near future: https://github.com/expressjs/session#saveuninitialized</span>
<span class="na">rolling</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// keep session alive</span>
<span class="na">cookie</span><span class="p">:</span> <span class="p">{</span>
<span class="na">maxAge</span><span class="p">:</span> <span class="mi">30</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">,</span> <span class="c1">// session expires in 1hr, refreshed by `rolling: true` option.</span>
<span class="na">httpOnly</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// so that cookie can't be accessed via client-side script</span>
<span class="p">}</span>
<span class="p">}));</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">passport</span><span class="p">.</span><span class="nx">initialize</span><span class="p">());</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">passport</span><span class="p">.</span><span class="nx">session</span><span class="p">());</span>
<span class="k">await</span> <span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="mi">3000</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">bootstrap</span><span class="p">();</span></code></pre></figure>
<p>If we drop the <code class="language-plaintext highlighter-rouge">.env</code> file in and start the app with <code class="language-plaintext highlighter-rouge">npm run start:dev</code>, we’ll see an error</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node_modules/connect-mongo/src/types.d.ts:8:23 - error TS2688: Cannot find type definition file for 'express-session'.
</code></pre></div></div>
<p>We can fix that by adding type definitions for the <code class="language-plaintext highlighter-rouge">express-session</code> library</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">npm i <span class="nt">--save-dev</span> @types/express-session</code></pre></figure>
<p>Now, when we go to http://localhost:3000/login, we’ll be directed to Google for user authentication!</p>
<h2 id="add-some-cat-crud">Add some Cat CRUD</h2>
<h3 id="add-authenticatedguard">Add <code class="language-plaintext highlighter-rouge">AuthenticatedGuard</code></h3>
<p>In order to prevent unauthorized calls from disturbing our cats, let’s create a generic <code class="language-plaintext highlighter-rouge">AuthenticatedGuard</code> that only allows calls by authenticated users on the annotated controller class or method.</p>
<figure class="highlight"><pre><code class="language-ts" data-lang="ts"><span class="c1">// src/common/guards/authenticated.guard.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ExecutionContext</span><span class="p">,</span> <span class="nx">Injectable</span><span class="p">,</span> <span class="nx">CanActivate</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@nestjs/common</span><span class="dl">'</span><span class="p">;</span>
<span class="p">@</span><span class="nd">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthenticatedGuard</span> <span class="k">implements</span> <span class="nx">CanActivate</span> <span class="p">{</span>
<span class="k">async</span> <span class="nx">canActivate</span><span class="p">(</span><span class="nx">context</span><span class="p">:</span> <span class="nx">ExecutionContext</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">request</span> <span class="o">=</span> <span class="nx">context</span><span class="p">.</span><span class="nx">switchToHttp</span><span class="p">().</span><span class="nx">getRequest</span><span class="p">();</span>
<span class="k">return</span> <span class="nx">request</span><span class="p">.</span><span class="nx">isAuthenticated</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="add-catsmodule">Add <code class="language-plaintext highlighter-rouge">CatsModule</code></h3>
<p>Create the <code class="language-plaintext highlighter-rouge">CatsModule</code> and related controller and service</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">nest g module cats
nest g controller cats
nest g service cats</code></pre></figure>
<p>Now, copy paste from the NestJS sample app providing the basics, from controller to model, to create Cat collections on MongoDB. See <a href="https://github.com/nestjs/nest/tree/master/sample/06-mongoose/src/cats">https://github.com/nestjs/nest/tree/master/sample/06-mongoose/src/cats</a> and replace the generated files with the files from the repository.</p>
<p>Let’s annotate the <code class="language-plaintext highlighter-rouge">CatsController</code> with <code class="language-plaintext highlighter-rouge">@UseGuards(AuthenticatedGuard)</code> to protect its endpoints and add an <code class="language-plaintext highlighter-rouge">update</code> method.</p>
<figure class="highlight"><pre><code class="language-ts" data-lang="ts"> <span class="p">@</span><span class="nd">Put</span><span class="p">(</span><span class="dl">'</span><span class="s1">:id</span><span class="dl">'</span><span class="p">)</span>
<span class="k">async</span> <span class="nx">update</span><span class="p">(@</span><span class="nd">Param</span><span class="p">()</span> <span class="nx">params</span><span class="p">,</span> <span class="p">@</span><span class="nd">Body</span><span class="p">()</span> <span class="nx">createCatDto</span><span class="p">:</span> <span class="nx">CreateCatDto</span><span class="p">)</span> <span class="p">{</span>
<span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">catsService</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">params</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">createCatDto</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>and let’s add an <code class="language-plaintext highlighter-rouge">update</code> method to <code class="language-plaintext highlighter-rouge">CatsService</code></p>
<figure class="highlight"><pre><code class="language-ts" data-lang="ts"> <span class="k">async</span> <span class="nx">update</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">createCatDto</span><span class="p">:</span> <span class="nx">CreateCatDto</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">Cat</span><span class="o">></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">updatedCatDto</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">catModel</span><span class="p">.</span><span class="nx">findByIdAndUpdate</span><span class="p">(</span><span class="nx">id</span><span class="p">,</span> <span class="nx">createCatDto</span><span class="p">,</span> <span class="p">{</span> <span class="na">new</span><span class="p">:</span> <span class="kc">true</span> <span class="p">});</span>
<span class="k">return</span> <span class="nx">updatedCatDto</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<h3 id="configure-mongodb">Configure MongoDB</h3>
<p>Finally, we’ll add the following to our <code class="language-plaintext highlighter-rouge">imports</code> array in our <code class="language-plaintext highlighter-rouge">AppModule</code></p>
<figure class="highlight"><pre><code class="language-ts" data-lang="ts"><span class="nx">MongooseModule</span><span class="p">.</span><span class="nx">forRoot</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">MONGODB_URL</span><span class="p">),</span> <span class="c1">// so that we can use Mongoose</span></code></pre></figure>
<h1 id="create-the-front-end">Create the front-end</h1>
<p>For the front-end we’ll be using <code class="language-plaintext highlighter-rouge">create-react-app</code> to build a react application with TypeScript.</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">npx create-react-app front-end <span class="nt">--template</span> typescript <span class="nt">--use-npm</span>
<span class="nb">cd </span>front-end</code></pre></figure>
<h2 id="add-reactstrap">Add reactstrap</h2>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">npm i <span class="nt">--save</span> bootstrap reactstrap
npm i <span class="nt">--save-dev</span> @types/reactstrap</code></pre></figure>
<p>Add <code class="language-plaintext highlighter-rouge">import 'bootstrap/dist/css/bootstrap.min.css';</code> to <code class="language-plaintext highlighter-rouge">index.tsx</code></p>
<h2 id="add-cat-interface">Add <code class="language-plaintext highlighter-rouge">Cat</code> interface</h2>
<figure class="highlight"><pre><code class="language-tsx" data-lang="tsx"><span class="c1">// cat.interface.tsx</span>
<span class="k">export</span> <span class="kr">interface</span> <span class="nx">Cat</span> <span class="p">{</span>
<span class="k">readonly</span> <span class="nx">_id</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="k">readonly</span> <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="k">readonly</span> <span class="nx">age</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="k">readonly</span> <span class="nx">breed</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<h2 id="add-catstable-component">Add <code class="language-plaintext highlighter-rouge">CatsTable</code> component</h2>
<figure class="highlight"><pre><code class="language-tsx" data-lang="tsx"><span class="c1">// cats.table.tsx</span>
<span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Table</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">reactstrap</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Cat</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./cat.interface</span><span class="dl">'</span>
<span class="kr">interface</span> <span class="nx">Props</span> <span class="p">{</span>
<span class="nl">cats</span><span class="p">:</span> <span class="nx">Cat</span><span class="p">[]</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">CatsTable</span> <span class="o">=</span> <span class="p">(</span><span class="nx">props</span><span class="p">:</span> <span class="nx">Props</span><span class="p">)</span> <span class="o">=></span> <span class="p">(</span>
<span class="p"><</span><span class="nc">Table</span> <span class="na">hover</span> <span class="na">responsive</span><span class="p">></span>
<span class="p"><</span><span class="nt">thead</span><span class="p">></span>
<span class="p"><</span><span class="nt">tr</span><span class="p">></span>
<span class="p"><</span><span class="nt">th</span><span class="p">></span>ID<span class="p"></</span><span class="nt">th</span><span class="p">></span>
<span class="p"><</span><span class="nt">th</span><span class="p">></span>Name<span class="p"></</span><span class="nt">th</span><span class="p">></span>
<span class="p"><</span><span class="nt">th</span><span class="p">></span>Age<span class="p"></</span><span class="nt">th</span><span class="p">></span>
<span class="p"><</span><span class="nt">th</span><span class="p">></span>Breed<span class="p"></</span><span class="nt">th</span><span class="p">></span>
<span class="p"></</span><span class="nt">tr</span><span class="p">></span>
<span class="p"></</span><span class="nt">thead</span><span class="p">></span>
<span class="p"><</span><span class="nt">tbody</span><span class="p">></span>
<span class="si">{</span><span class="nx">props</span><span class="p">.</span><span class="nx">cats</span><span class="p">.</span><span class="nx">length</span> <span class="o">></span> <span class="mi">0</span> <span class="p">?</span> <span class="p">(</span>
<span class="nx">props</span><span class="p">.</span><span class="nx">cats</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">cat</span> <span class="o">=></span> <span class="p">(</span>
<span class="p"><</span><span class="nt">tr</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">cat</span><span class="p">.</span><span class="nx">_id</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nt">td</span><span class="p">></span><span class="si">{</span><span class="nx">cat</span><span class="p">.</span><span class="nx">_id</span><span class="si">}</span><span class="p"></</span><span class="nt">td</span><span class="p">></span>
<span class="p"><</span><span class="nt">td</span><span class="p">></span><span class="si">{</span><span class="nx">cat</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span><span class="p"></</span><span class="nt">td</span><span class="p">></span>
<span class="p"><</span><span class="nt">td</span><span class="p">></span><span class="si">{</span><span class="nx">cat</span><span class="p">.</span><span class="nx">age</span><span class="si">}</span><span class="p"></</span><span class="nt">td</span><span class="p">></span>
<span class="p"><</span><span class="nt">td</span><span class="p">></span><span class="si">{</span><span class="nx">cat</span><span class="p">.</span><span class="nx">breed</span><span class="si">}</span><span class="p"></</span><span class="nt">td</span><span class="p">></span>
<span class="p"></</span><span class="nt">tr</span><span class="p">></span>
<span class="p">))</span>
<span class="p">)</span> <span class="p">:</span> <span class="p">(</span>
<span class="p"><</span><span class="nt">tr</span><span class="p">></span>
<span class="p"><</span><span class="nt">td</span> <span class="na">colSpan</span><span class="p">=</span><span class="si">{</span><span class="mi">3</span><span class="si">}</span><span class="p">></span>No cats<span class="p"></</span><span class="nt">td</span><span class="p">></span>
<span class="p"></</span><span class="nt">tr</span><span class="p">></span>
<span class="p">)</span><span class="si">}</span>
<span class="p"></</span><span class="nt">tbody</span><span class="p">></span>
<span class="p"></</span><span class="nc">Table</span><span class="p">></span>
<span class="p">)</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">CatsTable</span></code></pre></figure>
<h2 id="replace-apptsx-with">Replace <code class="language-plaintext highlighter-rouge">App.tsx</code> with…</h2>
<figure class="highlight"><pre><code class="language-tsx" data-lang="tsx"><span class="c1">// App.tsx</span>
<span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">useEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">'</span><span class="s1">./App.css</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Cat</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./cat.interface</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">CatsTable</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./cats.table</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Jumbotron</span><span class="p">,</span> <span class="nx">Button</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">reactstrap</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// Data</span>
<span class="kd">const</span> <span class="na">catsData</span> <span class="p">:</span> <span class="nx">Cat</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[]</span>
<span class="kd">const</span> <span class="na">userData</span> <span class="p">:</span> <span class="nx">User</span> <span class="o">=</span> <span class="p">{}</span>
<span class="kr">interface</span> <span class="nx">User</span> <span class="p">{</span>
<span class="nx">id_token</span><span class="p">?:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">access_token</span><span class="p">?:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">refresh_token</span><span class="p">?:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">userinfo</span><span class="p">?:</span> <span class="nx">UserInfo</span>
<span class="p">}</span>
<span class="kr">interface</span> <span class="nx">UserInfo</span> <span class="p">{</span>
<span class="nx">name</span><span class="p">?:</span> <span class="kr">string</span>
<span class="p">}</span>
<span class="c1">// Setting state</span>
<span class="kd">const</span> <span class="p">[</span> <span class="nx">cats</span><span class="p">,</span> <span class="nx">setCats</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="nx">catsData</span><span class="p">)</span>
<span class="kd">const</span> <span class="p">[</span> <span class="nx">user</span><span class="p">,</span> <span class="nx">setUser</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="nx">userData</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">getCats</span> <span class="o">=</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/cats</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">cats</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">()</span>
<span class="nx">setCats</span><span class="p">(</span><span class="nx">cats</span><span class="p">)</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
<span class="c1">// add better error handling here (e.g. 401?)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// Create a scoped async function in the hook</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">runAsync</span><span class="p">()</span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/user</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">userResponse</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">text</span><span class="p">()</span>
<span class="nx">setUser</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">userResponse</span><span class="p">))</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">userResponse</span> <span class="o">!==</span> <span class="dl">''</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">getCats</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">catch</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// add better error handling here</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// Execute the created function directly</span>
<span class="nx">runAsync</span><span class="p">()</span>
<span class="c1">// https://stackoverflow.com/a/55854902/1098564 </span>
<span class="c1">// eslint-disable-next-line</span>
<span class="p">},</span> <span class="p">[])</span>
<span class="kd">const</span> <span class="nx">login</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span><span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">'</span><span class="s1">/login</span><span class="dl">'</span><span class="p">)}</span>
<span class="kd">const</span> <span class="nx">logout</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span><span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">'</span><span class="s1">/logout</span><span class="dl">'</span><span class="p">)}</span>
<span class="c1">// https://stackoverflow.com/a/32108184/1098564</span>
<span class="kd">const</span> <span class="nx">isEmpty</span> <span class="o">=</span> <span class="p">(</span><span class="na">obj</span><span class="p">:</span> <span class="nb">Object</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span><span class="k">return</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">obj</span><span class="p">).</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span> <span class="o">&&</span> <span class="nx">obj</span><span class="p">.</span><span class="kd">constructor</span> <span class="o">===</span> <span class="nb">Object</span><span class="p">}</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p"><</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nc">Jumbotron</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span> <span class="na">className</span><span class="p">=</span><span class="s">"display-3"</span><span class="p">></span>Cat's Nest! <span class="p"><</span><span class="nt">span</span> <span class="na">role</span><span class="p">=</span><span class="s">"img"</span> <span class="na">aria</span><span class="err">-</span><span class="na">label</span><span class="p">=</span><span class="s">"smiling cat"</span><span class="p">></span>😺<span class="p"></</span><span class="nt">span</span><span class="p">></</span><span class="nt">h1</span><span class="p">></span>
<span class="si">{</span><span class="nx">user</span> <span class="o">&&</span> <span class="nx">user</span><span class="p">.</span><span class="nx">userinfo</span> <span class="o">&&</span>
<span class="p"><</span><span class="nt">p</span> <span class="na">className</span><span class="p">=</span><span class="s">"lead"</span><span class="p">></span>Hey <span class="si">{</span><span class="nx">user</span><span class="p">.</span><span class="nx">userinfo</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span><span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="si">}</span>
<span class="p"><</span><span class="nt">p</span> <span class="na">className</span><span class="p">=</span><span class="s">"lead"</span><span class="p">></span>
<span class="si">{</span><span class="nx">isEmpty</span><span class="p">(</span><span class="nx">user</span><span class="p">)</span> <span class="p">?</span>
<span class="p"><</span><span class="nc">Button</span> <span class="na">color</span><span class="p">=</span><span class="s">"primary"</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">login</span><span class="si">}</span><span class="p">></span>Login<span class="p"></</span><span class="nc">Button</span><span class="p">></span>
<span class="p">:</span>
<span class="p"><</span><span class="nc">Button</span> <span class="na">color</span><span class="p">=</span><span class="s">"danger"</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">logout</span><span class="si">}</span><span class="p">></span>Logout<span class="p"></</span><span class="nc">Button</span><span class="p">></span>
<span class="si">}</span>
<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"></</span><span class="nc">Jumbotron</span><span class="p">></span>
<span class="si">{</span><span class="o">!</span><span class="nx">isEmpty</span><span class="p">(</span><span class="nx">user</span><span class="p">)</span> <span class="o">&&</span> <span class="p"><</span><span class="nc">CatsTable</span> <span class="na">cats</span><span class="p">=</span><span class="si">{</span><span class="nx">cats</span><span class="si">}</span><span class="p">/></span><span class="si">}</span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span></code></pre></figure>
<h2 id="proxy-requests-to-back-end-in-development">Proxy requests to back-end in development</h2>
<p>Finally, to get our client to proxy certain calls to our NestJS back-end on port 3000, we add <code class="language-plaintext highlighter-rouge">http-proxy-middleware</code> as per the <a href="https://create-react-app.dev/docs/proxying-api-requests-in-development/#configuring-the-proxy-manually">CRA docs</a></p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">npm i <span class="nt">--save-dev</span> http-proxy-middleware</code></pre></figure>
<p>And add the following <code class="language-plaintext highlighter-rouge">setupProxy.js</code> file to configure it</p>
<figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="c1">// setupProxy.js</span>
<span class="c1">// https://create-react-app.dev/docs/proxying-api-requests-in-development#configuring-the-proxy-manually</span>
<span class="c1">// https://github.com/chimurai/http-proxy-middleware</span>
<span class="kd">const</span> <span class="nx">proxy</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">http-proxy-middleware</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">app</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span>
<span class="p">[</span>
<span class="dl">'</span><span class="s1">/cats</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">/logout</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">/login</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">/user</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">/callback</span><span class="dl">'</span><span class="p">,</span>
<span class="p">],</span>
<span class="nx">proxy</span><span class="p">({</span>
<span class="na">target</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http://localhost:3000</span><span class="dl">'</span><span class="p">,</span>
<span class="na">changeOrigin</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">xfwd</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">})</span>
<span class="p">);</span>
<span class="p">};</span></code></pre></figure>
<h1 id="start-everything-up">Start everything up</h1>
<p class="notice"><strong>Note</strong>: Before we fire up the front-end on 3001, we need to change environment variables in the back-end’s <code class="language-plaintext highlighter-rouge">.env</code> file to point to 3001 instead of 3000. That’s because we want all redirects to come to the front-end in development. You’ll want to change the two environment variables back to 3000 when running this in production.</p>
<p>Start the back-end</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">npm run start:dev</code></pre></figure>
<p>In another terminal window, start the front-end</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">PORT</span><span class="o">=</span>3001 npm start</code></pre></figure>
<p>Watch the magic happen!</p>
<h1 id="serve-static-resources-in-production">Serve static resources in production</h1>
<p>To serve up static resources (like a React application) in production, add the dependency <code class="language-plaintext highlighter-rouge">@nestjs/serve-static</code></p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">npm i <span class="nt">--save</span> @nestjs/serve-static</code></pre></figure>
<p>and add the following to our <code class="language-plaintext highlighter-rouge">imports</code> array in <code class="language-plaintext highlighter-rouge">AppModule</code> to point to our front-end build.</p>
<figure class="highlight"><pre><code class="language-ts" data-lang="ts"> <span class="nx">ServeStaticModule</span><span class="p">.</span><span class="nx">forRoot</span><span class="p">({</span>
<span class="na">rootPath</span><span class="p">:</span> <span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">../..</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">front-end/build</span><span class="dl">'</span><span class="p">),</span>
<span class="p">}),</span> <span class="c1">// so that front-end can be served up by back-end</span></code></pre></figure>
<p class="notice"><strong>Note</strong>: You’ll also want to remove <code class="language-plaintext highlighter-rouge">AppController</code> and <code class="language-plaintext highlighter-rouge">AppService</code> from <code class="language-plaintext highlighter-rouge">AppModule</code> so that the “Hello, World!” endpoint won’t conflict with the static resources we’re serving up.</p>
<h1 id="references">References</h1>
<ul>
<li><a href="https://dev.to/nestjs/authentication-and-sessions-for-mvc-apps-with-nestjs-55a4">https://dev.to/nestjs/authentication-and-sessions-for-mvc-apps-with-nestjs-55a4</a></li>
<li><a href="https://medium.com/@nielsmeima/auth-in-nest-js-and-angular-463525b6e071">https://medium.com/@nielsmeima/auth-in-nest-js-and-angular-463525b6e071</a></li>
<li><a href="https://github.com/nestjs/docs.nestjs.com/issues/99#issuecomment-557878531">https://github.com/nestjs/docs.nestjs.com/issues/99#issuecomment-557878531</a> (<a href="https://github.com/hegelstad">Nikolai Hegelstad</a>)</li>
</ul>
<h1 id="conclusion">Conclusion</h1>
<p>Hope you enjoyed this tutorial. With NestJS and create-react-app, it’s pretty easy to create a clean session-based OIDC application!</p>
<p>Let me know what you think in the comments section.</p>
<p>Thanks for reading!</p>
<p class="notice">Please <strong>follow me</strong> on <a href="https://twitter.com/doxsees">twitter</a> or <a href="/atom.xml">subscribe</a> to be updated as each part of this series comes out.</p>
<p>If you’d like help with any of these things, find out how what I do and how you can <strong>hire me</strong> at <a href="https://simplestep.ca">Simple Step Solutions</a></p>stephenIn this post, we’ll explore how to create a NestJS back-end that handles OpenID Connect authentication for a React app that it serves up with an express-session. The session store will share the MongoDB instance that is also used for storing cats.Merry Microservices: Part 3 ‘Policy Service’–Managing application-specific authorization based on identity and permissions2020-01-12T00:00:00+00:002020-01-12T00:00:00+00:00https://sdoxsee.github.io/blog/2020/01/12/merry-microservices-part3-policy-service<p>This is Part 3 of the series “<a href="/blog/2019/12/17/merry-microservices-an-introduction">Merry Microservices</a>”</p>
<p>We’ll be building on the confidential note service from <a href="/blog/2019/12/17/merry-microservices-part1-resource-server">Part 1</a> and the UI gateway from <a href="/blog/2019/12/17/merry-microservices-part2-gateway">Part 2</a> but we’ll further our authorization, beyond what OAuth 2.0 provides, by calling a “policy service” where application-specific permissions are managed.</p>
<p>The source can be found on github at <a href="https://github.com/sdoxsee/merry-microservices/tree/part3">https://github.com/sdoxsee/merry-microservices/tree/part3</a>.</p>
<p><img border="0" src="/assets/images/merry-microservices/gift.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift2.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift3.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift2.svg" width="19%" /></p>
<div id="entry-table-of-contents" class="toc-wrapper">
<h2 id="toc-toggle" class="no_toc">
Table of Contents <i class="toc-toggle-icon fas fa-chevron-down"></i>
</h2>
<ol id="markdown-toc">
<li><a href="#screencast" id="markdown-toc-screencast">Screencast</a></li>
<li><a href="#preamble" id="markdown-toc-preamble">Preamble</a></li>
<li><a href="#customizing-our-note-service-for-more-fine-grained-authorization" id="markdown-toc-customizing-our-note-service-for-more-fine-grained-authorization">Customizing our note service for more fine-grained authorization</a></li>
<li><a href="#setting-up-the-policy-service" id="markdown-toc-setting-up-the-policy-service">Setting up the “policy service”</a> <ol>
<li><a href="#start-keycloak-and-policy-service" id="markdown-toc-start-keycloak-and-policy-service">Start Keycloak and “policy service”</a></li>
<li><a href="#log-in-to-policy-service" id="markdown-toc-log-in-to-policy-service">Log in to “policy service”</a></li>
<li><a href="#setup-our-a-note-policy-for-our-existing-note-service" id="markdown-toc-setup-our-a-note-policy-for-our-existing-note-service">Setup our a <code class="language-plaintext highlighter-rouge">note</code> policy for our existing note service</a></li>
<li><a href="#policy-setup-for-our-ui-gateway" id="markdown-toc-policy-setup-for-our-ui-gateway">Policy setup for our UI gateway</a></li>
</ol>
</li>
<li><a href="#customizing-our-ui-gateway-for-more-fine-grained-authorization" id="markdown-toc-customizing-our-ui-gateway-for-more-fine-grained-authorization">Customizing our UI gateway for more fine-grained authorization</a> <ol>
<li><a href="#configure-spring-cloud-gateway-to-proxy-policy-service-calls" id="markdown-toc-configure-spring-cloud-gateway-to-proxy-policy-service-calls">Configure Spring Cloud Gateway to proxy “policy service” calls</a></li>
<li><a href="#changing-the-react-ui-to-use-policy-service-permissions" id="markdown-toc-changing-the-react-ui-to-use-policy-service-permissions">Changing the React UI to use “policy service” permissions</a></li>
</ol>
</li>
<li><a href="#it-works" id="markdown-toc-it-works">It works!</a></li>
<li><a href="#more-policy-service-features" id="markdown-toc-more-policy-service-features">More “policy service” features</a></li>
<li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ol>
</div>
<h1 id="screencast">Screencast</h1>
<!-- 16:9 aspect ratio -->
<div class="responsive-embed responsive-embed-16by9">
<iframe class="responsive-embed-item" src="https://www.youtube.com/embed/ZLA6zBm7YHk" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>
<h1 id="preamble">Preamble</h1>
<p>In order to keep this introduction short, I recommend you first read “<a href="/blog/2020/01/03/stop-overloading-jwts-with-permission-claims">Stop overloading JWTs with permission claims</a>” as it provides the rationale for going this route for authorization.</p>
<p>But here’s a quick summary:</p>
<blockquote>
<p>We want to keep role and permission claims out of our JWT OAuth 2.0 access token because it should only be concerned with “identity authorization”. However, “identity authorization” isn’t enough because it’s only what the user has authorized the application to do on their behalf–not what the application authorizes the user to do! For that, we need more granular authorization and permissions in our application. Or, to avoid building the same services over and over, we’ll use a centralized “policy service” that we call from our applications that answers whether or not a user has a given permission.</p>
</blockquote>
<p>So, what “policy service” are we going to use? Well, there’s the community version of PolicyServer for .NET but that won’t work with anything other than Microsoft. The commercial version could serve our purposes but it</p>
<ol>
<li>doesn’t have a freemium model (other than the single application community version)</li>
<li>doesn’t have a SaaS experience (only docker or self-hosted after paying)</li>
<li>doesn’t advertise any public documentation</li>
<li>complicates policies by letting them be hierarchical</li>
</ol>
<p>As a result, I built my own with JHipster that I hope to make a hosted SaaS soon and/or open source. We’ll be interacting with that for the duration of this post.</p>
<p>Let’s dive right in.</p>
<h1 id="customizing-our-note-service-for-more-fine-grained-authorization">Customizing our note service for more fine-grained authorization</h1>
<p>Off the top of my head, we have a few approaches to authorization in Spring Security:</p>
<ol>
<li>Route-based authorization (like <code class="language-plaintext highlighter-rouge">ResourceServerConfig</code> below)</li>
<li>Method-based authorization (including Spring Security’s <code class="language-plaintext highlighter-rouge">@PreAuthorize</code> calls)</li>
<li>Imperative authorization where, based on business logic, we throw <code class="language-plaintext highlighter-rouge">AccessDeniedException</code> (that triggers a 403 response) or return a 403 response directly.</li>
</ol>
<p>I think usually you’d use #1 (route-based) for OAuth 2.0 scope authorization. I’m not a big fan of #2’s (method-based) <code class="language-plaintext highlighter-rouge">@PreAuthorize</code> because I find it SPEL awkward and limiting. #3 can be very expressive and testable allowing us to use policyService responses to <strong>control access</strong> and/or <strong>filter results</strong>.</p>
<p><a href="/blog/2019/12/17/merry-microservices-part1-resource-server">Previously</a> we only let Spring Security filters determine if we should return a “401 Unauthorized” or not simply by validating the JWT access token. Perhaps we should have also added route-based configuration to enforce scope authorization by ensuring that a scope like <code class="language-plaintext highlighter-rouge">authority-note</code> came with the access token or resulted in a “403 Forbidden” response. Although we’re not going to do that in this tutorial, here’s what it would have looked like:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@EnableWebSecurity</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ResourceServerConfig</span> <span class="kd">extends</span> <span class="nc">WebSecurityConfigurerAdapter</span> <span class="o">{</span>
<span class="c1">// @formatter:off</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">configure</span><span class="o">(</span><span class="nc">HttpSecurity</span> <span class="n">http</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">http</span>
<span class="o">.</span><span class="na">authorizeRequests</span><span class="o">(</span><span class="n">authorizeRequests</span> <span class="o">-></span>
<span class="n">authorizeRequests</span>
<span class="o">.</span><span class="na">mvcMatchers</span><span class="o">(</span><span class="s">"/**"</span><span class="o">).</span><span class="na">access</span><span class="o">(</span><span class="s">"hasAuthority('SCOPE_authority-note')"</span><span class="o">)</span>
<span class="o">.</span><span class="na">anyRequest</span><span class="o">().</span><span class="na">authenticated</span><span class="o">())</span>
<span class="o">.</span><span class="na">oauth2ResourceServer</span><span class="o">(</span><span class="n">oauth2ResourceServer</span> <span class="o">-></span>
<span class="n">oauth2ResourceServer</span>
<span class="o">.</span><span class="na">jwt</span><span class="o">());</span>
<span class="o">}</span></code></pre></figure>
<p>Instead, let’s go a bit futher by introducing a <code class="language-plaintext highlighter-rouge">PolicyService</code> client bean that will query our “policy service” application for more specific permissions in whatever way we decide to use it (be that route-based, method-based or imperatively).</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Service</span>
<span class="kd">class</span> <span class="nc">PolicyService</span> <span class="o">{</span>
<span class="nd">@Value</span><span class="o">(</span><span class="s">"${app.policy-name}"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">appPolicyName</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">WebClient</span> <span class="n">webClient</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">PolicyService</span><span class="o">(</span><span class="nc">WebClient</span> <span class="n">webClient</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">webClient</span> <span class="o">=</span> <span class="n">webClient</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Transactional</span><span class="o">(</span><span class="n">readOnly</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Mono</span><span class="o"><</span><span class="nc">Boolean</span><span class="o">></span> <span class="nf">hasPermission</span><span class="o">(</span><span class="nc">Jwt</span> <span class="n">jwt</span><span class="o">,</span> <span class="nc">String</span> <span class="n">permission</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">webClient</span>
<span class="o">.</span><span class="na">get</span><span class="o">()</span>
<span class="o">.</span><span class="na">uri</span><span class="o">(</span><span class="n">uriBuilder</span> <span class="o">-></span> <span class="n">uriBuilder</span>
<span class="o">.</span><span class="na">path</span><span class="o">(</span><span class="s">"/api/policy-evaluation"</span><span class="o">)</span>
<span class="o">.</span><span class="na">queryParam</span><span class="o">(</span><span class="s">"policy"</span><span class="o">,</span> <span class="n">appPolicyName</span><span class="o">)</span>
<span class="o">.</span><span class="na">queryParam</span><span class="o">(</span><span class="s">"permission"</span><span class="o">,</span> <span class="n">permission</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">())</span>
<span class="o">.</span><span class="na">headers</span><span class="o">(</span><span class="n">headers</span> <span class="o">-></span> <span class="n">headers</span><span class="o">.</span><span class="na">setBearerAuth</span><span class="o">(</span><span class="n">jwt</span><span class="o">.</span><span class="na">getTokenValue</span><span class="o">()))</span>
<span class="o">.</span><span class="na">retrieve</span><span class="o">()</span>
<span class="o">.</span><span class="na">bodyToMono</span><span class="o">(</span><span class="nc">Boolean</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<!-- **Note**: I've built a better client for checking roles, permissions on specific entity types and IDs, etc. but I've omitted those other methods and alternative method signatures for simplicity.
{: .notice} -->
<p>In the <code class="language-plaintext highlighter-rouge">PolicyService</code> class, we</p>
<ol>
<li>inject the <code class="language-plaintext highlighter-rouge">appPolicyName</code> from our properties. This is the handle/application/policy by which we namespace the roles and permissions to the policy we are configuring. Since we’re creting this bean specifically for the note service application, we set <code class="language-plaintext highlighter-rouge">app.policy-name: note</code> in our <code class="language-plaintext highlighter-rouge">application.yml</code> so that all policyService calls will be properly namespaced automatically.</li>
<li>inject our webClient that is configured to talk to the “policy service” application</li>
<li>implement a <code class="language-plaintext highlighter-rouge">hasPermission</code> method that takes the JWT from the request and verifies whether or not that user has the given permission. We make the call to the “policy service” by relaying the JWT as a header.</li>
</ol>
<p>Before we use our <code class="language-plaintext highlighter-rouge">PolicyService</code>, we must first create a <code class="language-plaintext highlighter-rouge">WebClient</code> bean to point to our “policy service” running on port 8080 so that we can inject it into our <code class="language-plaintext highlighter-rouge">PolicyService</code>.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="nd">@Bean</span>
<span class="nc">WebClient</span> <span class="nf">policyServiceWebClient</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">WebClient</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="s">"http://localhost:8080/"</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<p>Now we can modify our <code class="language-plaintext highlighter-rouge">NoteHandler</code> to use our <code class="language-plaintext highlighter-rouge">PolicyService</code>. For this post, we’ll just zero in on the <code class="language-plaintext highlighter-rouge">all</code> method that returns a <code class="language-plaintext highlighter-rouge">Flux</code> of <code class="language-plaintext highlighter-rouge">Note</code> objects in a <code class="language-plaintext highlighter-rouge">Mono<ServerResponse></code>. After we inject our <code class="language-plaintext highlighter-rouge">PolicyService</code>, our <code class="language-plaintext highlighter-rouge">all</code> method looks something like this:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="nc">Mono</span><span class="o"><</span><span class="nc">ServerResponse</span><span class="o">></span> <span class="nf">all</span><span class="o">(</span><span class="nc">ServerRequest</span> <span class="n">r</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">r</span><span class="o">.</span><span class="na">principal</span><span class="o">().</span><span class="na">flatMap</span><span class="o">((</span><span class="n">principal</span><span class="o">)</span> <span class="o">-></span> <span class="o">{</span>
<span class="nc">Jwt</span> <span class="n">jwt</span> <span class="o">=</span> <span class="o">((</span><span class="nc">JwtAuthenticationToken</span><span class="o">)</span> <span class="n">principal</span><span class="o">).</span><span class="na">getToken</span><span class="o">();</span>
<span class="k">return</span> <span class="n">policyService</span><span class="o">.</span><span class="na">hasPermission</span><span class="o">(</span><span class="n">jwt</span><span class="o">,</span> <span class="s">"CanRead"</span><span class="o">)</span>
<span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">canRead</span> <span class="o">-></span> <span class="n">canRead</span> <span class="o">?</span>
<span class="n">policyService</span><span class="o">.</span><span class="na">hasPermission</span><span class="o">(</span><span class="n">jwt</span><span class="o">,</span> <span class="s">"CanReadConfidentialNotes"</span><span class="o">)</span>
<span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">canReadConfidentialNotes</span> <span class="o">-></span> <span class="n">defaultReadResponse</span><span class="o">(</span>
<span class="n">canReadConfidentialNotes</span> <span class="o">?</span>
<span class="n">noteRepository</span><span class="o">.</span><span class="na">findAll</span><span class="o">()</span> <span class="o">:</span>
<span class="n">noteRepository</span><span class="o">.</span><span class="na">findByConfidentialFalse</span><span class="o">()</span>
<span class="o">)</span>
<span class="o">)</span> <span class="o">:</span>
<span class="nc">ServerResponse</span><span class="o">.</span><span class="na">status</span><span class="o">(</span><span class="nc">HttpStatus</span><span class="o">.</span><span class="na">FORBIDDEN</span><span class="o">).</span><span class="na">build</span><span class="o">());</span>
<span class="o">});</span>
<span class="o">}</span></code></pre></figure>
<p>By the way, I’m pretty new at coding with <a href="https://projectreactor.io/docs/core/release/reference/#intro-reactive">Project Reactor</a> so pardon my style and please leave a comment with any suggestions or improvements! For reference, the equivalent non-reactive code would look something like this:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">List</span><span class="o"><</span><span class="nc">Note</span><span class="o">></span> <span class="nf">getNotes</span><span class="o">(</span><span class="nc">Jwt</span> <span class="n">accessToken</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// for access</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">policyService</span><span class="o">.</span><span class="na">hasPermission</span><span class="o">(</span><span class="s">"note"</span><span class="o">,</span> <span class="n">jwt</span><span class="o">,</span> <span class="s">"CanRead"</span><span class="o">))</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">AccessDeniedException</span><span class="o">();</span> <span class="c1">// 403</span>
<span class="o">}</span>
<span class="c1">// for filtering</span>
<span class="k">if</span> <span class="o">(</span><span class="n">policyService</span><span class="o">.</span><span class="na">hasPermission</span><span class="o">(</span><span class="s">"note"</span><span class="o">,</span> <span class="n">jwt</span><span class="o">,</span> <span class="s">"CanReadConfidentialNotes"</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">noteRepository</span><span class="o">.</span><span class="na">findAll</span><span class="o">();</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">noteRepository</span><span class="o">.</span><span class="na">findByConfidentialFalse</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>In any case, we now have a <code class="language-plaintext highlighter-rouge">PolicyService</code> client and we’re using it imperatively in the <code class="language-plaintext highlighter-rouge">all</code> method of our <code class="language-plaintext highlighter-rouge">NoteHandler</code> to provide both</p>
<ul>
<li>access control (based on the <code class="language-plaintext highlighter-rouge">CanRead</code> permission) and</li>
<li>filtered results (based on the <code class="language-plaintext highlighter-rouge">CanReadConfidentialNotes</code> permission)</li>
</ul>
<p class="notice"><strong>Note</strong>: To be clear, the default response from <code class="language-plaintext highlighter-rouge">hasPermission</code> calls to the “policy service” is <code class="language-plaintext highlighter-rouge">false</code> so that if a permission isn’t setup, we don’t get unexpected results in our applications!</p>
<h1 id="setting-up-the-policy-service">Setting up the “policy service”</h1>
<h2 id="start-keycloak-and-policy-service">Start Keycloak and “policy service”</h2>
<p>Before we can setup the “policy service”, we need to start it up along with Keycloak. We’ve added a new <code class="language-plaintext highlighter-rouge">policyservice</code> service to the <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> to make it easy to start.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-compose up
</code></pre></div></div>
<h2 id="log-in-to-policy-service">Log in to “policy service”</h2>
<p>If we go to <code class="language-plaintext highlighter-rouge">http://localhost:8080</code> (where it should be running), we can login with username <code class="language-plaintext highlighter-rouge">admin</code> and password <code class="language-plaintext highlighter-rouge">admin</code>. The <code class="language-plaintext highlighter-rouge">admin</code> user is the only user in the default JHipster realm configuration for Keycloak has been given the identity role of <code class="language-plaintext highlighter-rouge">ROLE_ADMIN</code> (in addition to <code class="language-plaintext highlighter-rouge">ROLE_USER</code>) that our JHipster “policy service” needs to manage entities.</p>
<h2 id="setup-our-a-note-policy-for-our-existing-note-service">Setup our a <code class="language-plaintext highlighter-rouge">note</code> policy for our existing note service</h2>
<p>Once we’re authenticated, we need to add a <code class="language-plaintext highlighter-rouge">note</code> “Policy” entity that creates a namespace in which we can add “Role” and “Permission” entities for our note service.</p>
<p><img src="/assets/images/merry-microservices/part3/note-policy.png" alt="Add note policy" /></p>
<p>Next we’ll add two “Role” entities to our <code class="language-plaintext highlighter-rouge">note</code> “Policy”–<code class="language-plaintext highlighter-rouge">user</code> and <code class="language-plaintext highlighter-rouge">admin</code></p>
<p><img src="/assets/images/merry-microservices/part3/note-roles.png" alt="Add roles to note policy" /></p>
<p>We’ll then add two “Permission” entities, <code class="language-plaintext highlighter-rouge">CanRead</code> and <code class="language-plaintext highlighter-rouge">CanReadConfidentialNotes</code>, and associate them to the <code class="language-plaintext highlighter-rouge">user</code> and <code class="language-plaintext highlighter-rouge">admin</code> respectively.</p>
<p><img src="/assets/images/merry-microservices/part3/can-read-permission.png" alt="Add CanRead permission to user" /></p>
<p><img src="/assets/images/merry-microservices/part3/can-read-confidential-notes-permission.png" alt="Add CanReadConfidentialNotes permission to admin" /></p>
<p>Finally, because we’re lazy and don’t want to get into the batch provisioning of specific user roles and permissions, we’ll create two “Identity Role” entities, <code class="language-plaintext highlighter-rouge">ROLE_USER</code> and <code class="language-plaintext highlighter-rouge">ROLE_ADMIN</code>, and map them to the “Role” entities <code class="language-plaintext highlighter-rouge">user</code> and <code class="language-plaintext highlighter-rouge">admin</code> respectively. These identity roles will match those coming from Keycloak’s JWT access token and allow us to map default “Role” entities to any user with those identity roles.</p>
<p><img src="/assets/images/merry-microservices/part3/identity-roles.png" alt="Add identity roles" /></p>
<p>For example, since Keycloak’s <code class="language-plaintext highlighter-rouge">admin</code> user has the identity roles <code class="language-plaintext highlighter-rouge">ROLE_ADMIN</code> and <code class="language-plaintext highlighter-rouge">ROLE_USER</code> in its JWT access token, our “policy service” can automatically map the permissions from the respective <code class="language-plaintext highlighter-rouge">admin</code> and <code class="language-plaintext highlighter-rouge">user</code> “Role” entities as defined on our policy service. Since Keycloak’s <code class="language-plaintext highlighter-rouge">user</code> user only has the <code class="language-plaintext highlighter-rouge">ROLE_USER</code> identity role, our “policy service” will only map the roles and permissions that come from the <code class="language-plaintext highlighter-rouge">user</code> “Role” entity.</p>
<h2 id="policy-setup-for-our-ui-gateway">Policy setup for our UI gateway</h2>
<p class="notice"><strong>Note</strong>: Before we go any further, we need to make a small tweak to <code class="language-plaintext highlighter-rouge">note</code> and <code class="language-plaintext highlighter-rouge">gateway</code>. These applications haven’t been running in Docker containers so they could reach the exposed Keycloak Docker container (named <code class="language-plaintext highlighter-rouge">keycloak</code>) at <code class="language-plaintext highlighter-rouge">http://localhost:9080</code>. Since, to a Docker container, <code class="language-plaintext highlighter-rouge">localhost</code> means “the same container”, our “policy service” that <em>is</em> running in a Docker container, isn’t able to reach <code class="language-plaintext highlighter-rouge">keycloak</code> at <code class="language-plaintext highlighter-rouge">http://localhost:9080</code> like the other applications were. So, <code class="language-plaintext highlighter-rouge">policyservice</code> needs to reach Keycloak via its service name <code class="language-plaintext highlighter-rouge">keycloak</code>. But, our “policy service” needs to be able to reach Keycloak not only via backchannel (server to server), it must also be able to it frontchannel (in the browser). Changing the issuer of our “policy service” to reference <code class="language-plaintext highlighter-rouge">keycloak</code> breaks the browser redirection because, unless you add it to your machine’s <code class="language-plaintext highlighter-rouge">hosts</code> file, it’s unrecognized. So, we <a href="https://github.com/sdoxsee/merry-microservices-part3#dealing-with-an-identity-provider-in-docker">make <code class="language-plaintext highlighter-rouge">keycloak</code> recognizable</a>. We also need to change <code class="language-plaintext highlighter-rouge">application.yml</code> for both <code class="language-plaintext highlighter-rouge">gateway</code> and <code class="language-plaintext highlighter-rouge">note</code> to use <code class="language-plaintext highlighter-rouge">keycloak</code> instead of <code class="language-plaintext highlighter-rouge">localhost</code> so we change the value for <code class="language-plaintext highlighter-rouge">spring.security.oauth2.client.provider.keycloak.issuer-uri</code> accordingly. If you don’t, the issuer in the JWT will be considered invalid and you’ll get “401 unauthorized”.</p>
<p>Now, without any code changes on our UI gateway, we should already benefit from the changes made to the note service and our configured policy service. If we were to log in as <code class="language-plaintext highlighter-rouge">admin</code>, we’d be able to view <em>all</em> notes–confidential or not. If we didn’t get assigned the identity role <code class="language-plaintext highlighter-rouge">ROLE_USER</code> on Keycloak, we wouldn’t be assigned the <code class="language-plaintext highlighter-rouge">CanRead</code> permission and would therefore get a “403 Forbiden” on the <code class="language-plaintext highlighter-rouge">/api/notes</code> GET call. If we <em>do</em> have the identity role <code class="language-plaintext highlighter-rouge">ROLE_USER</code> on Keycloak, we’ll get a filtered list of only <em>non-confidential</em> notes because we don’t have the <code class="language-plaintext highlighter-rouge">CanReadConfidentialNotes</code> permission.</p>
<p>Try it out with <code class="language-plaintext highlighter-rouge">admin</code> and <code class="language-plaintext highlighter-rouge">user</code> users and you’ll see the difference. Cool eh?</p>
<p>But why should only the note services get policies? What about the UI gateway? We should be able to give each application has its own roles and permissions. The authorization concerns in the UI gateway might overlap with those of the note service but it probably has its own distinct ones as well. For example, say, hypothetically, that we wanted Canadian users (not based on locale or IP but via a role) to have a specific UI experience. Since Canadians (also sometimes called “Canucks”) love snow, we might want to add a snowstorm effect when they’re logged in :)</p>
<p>Let’s try it!</p>
<p>First we’ll add our <code class="language-plaintext highlighter-rouge">ui-gateway</code> policy.</p>
<p><img src="/assets/images/merry-microservices/part3/ui-gateway-policy.png" alt="Add ui-gateway policy" /></p>
<p>Next, we’ll add our <code class="language-plaintext highlighter-rouge">canuck</code> “Role” entity belonging to the <code class="language-plaintext highlighter-rouge">ui-gateway</code> policy.</p>
<p><img src="/assets/images/merry-microservices/part3/canuck-role.png" alt="Add canuck role" /></p>
<p>If we add a <code class="language-plaintext highlighter-rouge">Snowing</code> “Permission” entity associated to the <code class="language-plaintext highlighter-rouge">canuck</code> “Role” entity, then we’ll be able to use that to determine if we should show a snowstorm in the UI!</p>
<p><img src="/assets/images/merry-microservices/part3/snowing-permission.png" alt="Add Snowing permission" /></p>
<p>Now, how will our Keycloak users get this new <code class="language-plaintext highlighter-rouge">canuck</code> role? We could either</p>
<ol>
<li>map that specific user to the <code class="language-plaintext highlighter-rouge">canuck</code> “Role” entity or</li>
<li>map a Keycloak identity role, let’s say <code class="language-plaintext highlighter-rouge">ROLE_ADMIN</code>, to the <code class="language-plaintext highlighter-rouge">canuck</code> “Role” entity on our policy service</li>
</ol>
<p>Chosing the second option for simplicity, let’s blindly map all <code class="language-plaintext highlighter-rouge">ROLE_ADMIN</code> to <code class="language-plaintext highlighter-rouge">canuck</code> “Role” as well as the existing <code class="language-plaintext highlighter-rouge">admin</code> “Role”.</p>
<p><img src="/assets/images/merry-microservices/part3/identity-role-mapping-to-canuck.png" alt="Add identity role mapping to canuck" /></p>
<p>That’s it for our “policy service” configuration!</p>
<h1 id="customizing-our-ui-gateway-for-more-fine-grained-authorization">Customizing our UI gateway for more fine-grained authorization</h1>
<h2 id="configure-spring-cloud-gateway-to-proxy-policy-service-calls">Configure Spring Cloud Gateway to proxy “policy service” calls</h2>
<p>How would we call the “policy service” from our React app to check those permissions we just setup? We’ll make our Spring Cloud Gateway backend proxy and relay calls along with our access token to the policy service. First, let’s add a route to our <code class="language-plaintext highlighter-rouge">application.yml</code> under <code class="language-plaintext highlighter-rouge">spring.cloud.gateway.routes</code>:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">policy-service</span>
<span class="na">uri</span><span class="pi">:</span> <span class="s">http://localhost:8080</span>
<span class="na">predicates</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">Path=/api/policy-evaluation/**</span>
<span class="na">filters</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">TokenRelay=</span>
<span class="pi">-</span> <span class="s">RemoveRequestHeader=Cookie</span>
<span class="pi">-</span> <span class="s">RemoveResponseHeader=Set-Cookie</span> <span class="c1"># or else policy-service's XSRF-TOKEN clobbers the gateway's!</span></code></pre></figure>
<p class="notice"><strong>Tip</strong>: You’ll probably want to use a <strong>refresh token</strong> or else your calls will “401” after 5 minutes (the default access token expiry on the Keycloak instance). If so, you’ll want to add the <code class="language-plaintext highlighter-rouge">offline_access</code> scope to your client registration and, until an <a href="https://github.com/spring-cloud/spring-cloud-security/issues/175">issue</a> with <code class="language-plaintext highlighter-rouge">TokenRelayGatewayFilterFactory</code> is resolved, use an <a href="https://github.com/spring-cloud/spring-cloud-security/issues/175#issuecomment-557135243">alternate GatewayFilterFactory that supports refresh tokens</a> and refer to it, <code class="language-plaintext highlighter-rouge">TokenRelayWithTokenRefresh</code>, in your <code class="language-plaintext highlighter-rouge">application.yml</code>, as your filter instead of <code class="language-plaintext highlighter-rouge">TokenRelay</code>.</p>
<p>Now that our backend is configured, calls from our React UI, such as</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/api/policy-evaluation?policy=ui-gateway&permission=</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">permission</span><span class="p">);</span></code></pre></figure>
<p>will be automatically proxied along to the policy service with the access token set in the <code class="language-plaintext highlighter-rouge">Authorization</code> header!</p>
<h2 id="changing-the-react-ui-to-use-policy-service-permissions">Changing the React UI to use “policy service” permissions</h2>
<p>Let’s change our React application to use our <code class="language-plaintext highlighter-rouge">Snowing</code> permission. In order to display our snowstorm, we first need to add <code class="language-plaintext highlighter-rouge">react-snowstorm</code> to our package dependencies by going to our <code class="language-plaintext highlighter-rouge">src/main/app</code> directory in a terminal and running:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">npm <span class="nb">install</span> <span class="nt">--save</span> react-snowstorm</code></pre></figure>
<p>Now that we have our new dependency, let’s use it in <code class="language-plaintext highlighter-rouge">App.tsx</code>:</p>
<figure class="highlight"><pre><code class="language-tsx" data-lang="tsx"><span class="k">import</span> <span class="nx">SnowStorm</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react-snowstorm</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">[</span> <span class="nx">isSnowing</span><span class="p">,</span> <span class="nx">setSnowing</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">hasPermission</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="na">permission</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/api/policy-evaluation?policy=ui-gateway&permission=</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">permission</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">body</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">text</span><span class="p">()</span>
<span class="k">return</span> <span class="nx">body</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">true</span><span class="dl">'</span>
<span class="p">}</span>
<span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">runAsync</span><span class="p">()</span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="nx">setSnowing</span><span class="p">(</span><span class="k">await</span> <span class="nx">hasPermission</span><span class="p">(</span><span class="dl">'</span><span class="s1">Snowing</span><span class="dl">'</span><span class="p">))</span>
<span class="c1">// ... </span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">runAsync</span><span class="p">()</span>
<span class="p">},</span> <span class="p">[])</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p"><</span><span class="nc">Container</span><span class="p">></span>
<span class="p"><</span><span class="nc">Jumbotron</span> <span class="na">id</span><span class="p">=</span><span class="s">"jumbotron"</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>CRUD App with Hooks<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="si">{</span><span class="nx">isAuthenticated</span> <span class="p">?</span>
<span class="p"><></span>
<span class="p"><</span><span class="nc">Form</span> <span class="na">action</span><span class="p">=</span><span class="s">"/logout"</span> <span class="na">method</span><span class="p">=</span><span class="s">"POST"</span><span class="p">></span>
<span class="p"><</span><span class="nc">Input</span> <span class="na">type</span><span class="p">=</span><span class="s">"hidden"</span> <span class="na">name</span><span class="p">=</span><span class="s">"_csrf"</span> <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">cookies</span><span class="p">[</span><span class="dl">'</span><span class="s1">XSRF-TOKEN</span><span class="dl">'</span><span class="p">]</span><span class="si">}</span><span class="p">/></span>
<span class="p"><</span><span class="nt">h3</span><span class="p">></span>Welcome <span class="si">{</span><span class="nx">authenticatedUser</span><span class="si">}</span>!<span class="p"></</span><span class="nt">h3</span><span class="p">><</span><span class="nc">Button</span> <span class="na">color</span><span class="p">=</span><span class="s">"secondary"</span><span class="p">></span>Logout<span class="p"></</span><span class="nc">Button</span><span class="p">></span>
<span class="p"></</span><span class="nc">Form</span><span class="p">></span>
<span class="si">{</span><span class="nx">isSnowing</span> <span class="o">&&</span> <span class="p"><</span><span class="nc">SnowStorm</span> <span class="na">targetElement</span><span class="p">=</span><span class="s">"jumbotron"</span><span class="p">/></span><span class="si">}</span>
<span class="p"></></span> <span class="p">:</span>
<span class="p"><</span><span class="nc">Button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">login</span><span class="si">}</span><span class="p">></span>Login<span class="p"></</span><span class="nc">Button</span><span class="p">></span>
<span class="si">}</span>
<span class="p"></</span><span class="nc">Jumbotron</span><span class="p">></span>
<span class="p"><</span><span class="err">!--</span> <span class="err">...</span> <span class="err">--</span><span class="p">></span>
<span class="p"></</span><span class="nc">Container</span><span class="p">></span>
)
}</code></pre></figure>
<p>Above we’ve…</p>
<ul>
<li>imported the <code class="language-plaintext highlighter-rouge">SnowStorm</code> component from <code class="language-plaintext highlighter-rouge">react-snowstorm</code></li>
<li>used hooks to set the initial state of <code class="language-plaintext highlighter-rouge">isSnowing</code> to <code class="language-plaintext highlighter-rouge">false</code> and that <code class="language-plaintext highlighter-rouge">setSnowing</code> is the function with which one can set its new value</li>
<li>defined a <code class="language-plaintext highlighter-rouge">hasPermission</code> function with a string <code class="language-plaintext highlighter-rouge">permission</code> parameter that calls our “policy service” to determine if the user has that permission</li>
<li>wrapped our logout form with an empty <code class="language-plaintext highlighter-rouge"><></code> and added the <code class="language-plaintext highlighter-rouge">SnowStorm</code> component if <code class="language-plaintext highlighter-rouge">isSnowing</code> is <code class="language-plaintext highlighter-rouge">true</code></li>
<li>added an <code class="language-plaintext highlighter-rouge">id</code> to our <code class="language-plaintext highlighter-rouge">Jumbotron</code> component to reference via <code class="language-plaintext highlighter-rouge">targetElement</code> in our <code class="language-plaintext highlighter-rouge">SnowStorm</code> component</li>
</ul>
<p>Unfortunately, that’s not quite enough. Since <code class="language-plaintext highlighter-rouge">react-snowstorm</code> isn’t a typed dependency we can’t use it yet. We get the following error:</p>
<p><img src="/assets/images/merry-microservices/part3/typescript-error.png" alt="Failed to compile" /></p>
<p>The easiest way to (satisfy)[https://stackoverflow.com/a/40211915/1098564] TypeScript is to simply add the following to <code class="language-plaintext highlighter-rouge">src/main/app/src/react-app-env.d.ts</code>–letting TypeScript know that the <code class="language-plaintext highlighter-rouge">react-snowstorm</code> exists:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>declare module 'react-snowstorm';
</code></pre></div></div>
<p>Everything should be working now!</p>
<p class="notice"><strong>Note</strong>: you’ll probably need touch <code class="language-plaintext highlighter-rouge">App.tsx</code> to trigger recompilation compilation</p>
<h1 id="it-works">It works!</h1>
<p>When you sign in with <code class="language-plaintext highlighter-rouge">admin</code>, you’ll see all notes and the snow storm effect.</p>
<p><img src="/assets/images/merry-microservices/part3/admin.png" alt="`admin` with all notes and snow storm" /></p>
<p>When you sign in with <code class="language-plaintext highlighter-rouge">user</code>, you’ll see only <em>non-confidential</em> notes and <em>no</em> snow storm effect.</p>
<p><img src="/assets/images/merry-microservices/part3/user.png" alt="`user` with non-confidential notes and no snow storm" /></p>
<center><img src="https://i.giphy.com/media/QsJc1iCW3KfLG5Ol7o/giphy.webp" /></center>
<p><br /></p>
<p>Now we’ve got a microservices architecture that scales, uses a pretty great stack, and employs a “policy service” to control authorization for application-specific permissions!</p>
<p>There’s still a lot that can be done though…</p>
<ul>
<li>more services</li>
<li>a more complex UI with nested routes</li>
<li>caching</li>
<li>pagination</li>
</ul>
<p>Still. Not too shabby!</p>
<h1 id="more-policy-service-features">More “policy service” features</h1>
<p>There are <strong>more “policy service” features</strong> that we didn’t explore yet, including:</p>
<ul>
<li><strong>user records</strong> (for specific user permissions rather than identity role mappings)</li>
<li><strong>JWT settings</strong> to pick the claims you want to use for users and identity roles</li>
<li><strong>permission overrides</strong> that, for a specific user, let you add or remove permissions by overriding
<ul>
<li><strong>all defaults</strong> from identity role mappings</li>
<li>permissions for a particular <strong>entity type</strong> (e.g. all <code class="language-plaintext highlighter-rouge">Note</code> entities)</li>
<li>permissions for a particular <strong>entity id</strong> of a particular entity type (e.g. the <code class="language-plaintext highlighter-rouge">Note</code> with id <code class="language-plaintext highlighter-rouge">1234</code>)</li>
</ul>
</li>
<li>querying <strong>roles</strong> as well as permissions</li>
<li>more upcoming features to simplify the <strong>onboarding</strong> of large groups of users</li>
</ul>
<p class="notice--primary">If you’re interested in using or contributing to the “policy service”, <strong>direct message me</strong> on <a href="https://twitter.com/doxsees">twitter</a>. I’m hoping to offer it soon as a SaaS and/or open source it.</p>
<h1 id="conclusion">Conclusion</h1>
<p>In the next post (Part 4), we’ll show how you can get all of this with full CI/CD and running on Google’s Kubernetes (GKE) using Jenkins X!</p>
<p class="notice">Please <strong>follow me</strong> on <a href="https://twitter.com/doxsees">twitter</a> or <a href="/atom.xml">subscribe</a> to be updated as each part of this series comes out.</p>
<p>If you’d like help with any of these things, find out how what I do and how you can <strong>hire me</strong> at <a href="https://simplestep.ca">Simple Step Solutions</a></p>stephenThis is Part 3 of the series “Merry Microservices”Stop overloading JWTs with permission claims2020-01-06T00:00:00+00:002020-01-06T00:00:00+00:00https://sdoxsee.github.io/blog/2020/01/06/stop-overloading-jwts-with-permission-claims<p>Here’s why…</p>
<div id="entry-table-of-contents" class="toc-wrapper">
<h2 id="toc-toggle" class="no_toc">
Table of Contents <i class="toc-toggle-icon fas fa-chevron-down"></i>
</h2>
<ol id="markdown-toc">
<li><a href="#tldr" id="markdown-toc-tldr">TL;DR</a></li>
<li><a href="#authentication-and-authorization" id="markdown-toc-authentication-and-authorization">Authentication and authorization</a></li>
<li><a href="#what-kind-of-authorization-does-oauth-20-provide" id="markdown-toc-what-kind-of-authorization-does-oauth-20-provide">What kind of authorization does OAuth 2.0 provide?</a></li>
<li><a href="#the-evolution-of-jwt-access-tokens" id="markdown-toc-the-evolution-of-jwt-access-tokens">The evolution of JWT access tokens</a></li>
<li><a href="#conflating-identity-with-permissions" id="markdown-toc-conflating-identity-with-permissions">Conflating identity with permissions</a></li>
<li><a href="#consequences-of-identity-and-permission-conflation-in-jwts" id="markdown-toc-consequences-of-identity-and-permission-conflation-in-jwts">Consequences of identity and permission conflation in JWTs</a></li>
<li><a href="#so-i-shouldnt-put-permissions-into-my-jwts" id="markdown-toc-so-i-shouldnt-put-permissions-into-my-jwts">So, I shouldn’t put permissions into my JWTs?</a></li>
<li><a href="#the-case-for-a-policy-service" id="markdown-toc-the-case-for-a-policy-service">The case for a “policy service”</a></li>
<li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ol>
</div>
<h1 id="tldr">TL;DR</h1>
<p>The authorization that OAuth 2.0 provides is likely a subset of all the authorization you need. OAuth 2.0 deals with what I call “<strong>identity authorization</strong>”. I think that we’ve often misunderstood authorization by trying to make OAuth 2.0 do more authorization than it’s supposed to by customizing JWT access tokens (that should be about “identity”) with application-specific role and permission claims that don’t belong there.</p>
<h1 id="authentication-and-authorization">Authentication and authorization</h1>
<p>You’ve probably heard it said that OpenID Connect (OIDC) is about <em>authentication</em> while OAuth 2.0 is about <em>authorization</em>.</p>
<ul>
<li>Authentication is about <em>who you are</em></li>
<li>Authorization is about <em>what you can do</em></li>
</ul>
<p>This is all true. However, it’s not quite that simple.</p>
<h1 id="what-kind-of-authorization-does-oauth-20-provide">What kind of authorization does OAuth 2.0 provide?</h1>
<p>OAuth 2.0 is about authorizing (or delegating authority to) applications to do things on a user’s behalf without requiring them to hand over their credentials. In addition to this, OAuth 2.0 “scopes” let the user decide to grant all or some of the scopes requested by the application. But, you can’t simply trust a user! The application has its own rules about what the user can do that go beyond the scopes that the user granted the application. Application rules are not what OAuth 2.0 should be used for and scopes have a more specific <a href="https://auth0.com/blog/on-the-nature-of-oauth2-scopes/">purpose</a>.</p>
<blockquote>
<p>The application has <strong>its own rules</strong> about what the user can do that go <em>beyond</em> the scopes that the user granted the application</p>
</blockquote>
<p>OIDC and OAuth 2.0 are very related. You don’t need OIDC for authentication. Facebook, Github and many others just customize OAuth 2.0 for authentication in addition to authorization. What’s special about OIDC is that it standardizes authentication on top of OAuth 2.0 so that it can be handled consistently. With OIDC handling authentication, OAuth 2.0 can get back to the business of authorization–but specifically, the authorization of applications to act on a user’s behalf. Nothing more. Both are really about “<strong>identity</strong>” so I’d call OAuth 2.0 authorization “<strong>identity authorization</strong>.”</p>
<h1 id="the-evolution-of-jwt-access-tokens">The evolution of JWT access tokens</h1>
<p>OIDC introduced JWTs for their ID Token but, soon, applications began using JWTs for their access tokens as well. Even though the OAuth 2.0 spec never even had JWTs in view, JWTs have the advantage that they let us verify the token without having to go back to the authorization server–as we <em>must</em> for opaque tokens. I believe JWTs have become so popular because we don’t have to take the performance hit of continually going to another server to validate them and allows us to know who a request was made on behalf of, without having to keep session state on your server. We pack the access tokens with “claims” about the user’s identity and the scopes they’ve granted to the client application. Over time, however, we’ve packed more and more into those JWTs–including application roles and permissions.</p>
<p class="notice"><strong>Note</strong>: There’s a case to be made for “identity roles” as opposed to “application roles” being present in JWTs. Identity roles would be those roles that apply throughout the entire universe of services you have. In fact, they allow simple mappings from identity roles to application roles if needed so that you don’t have to explicitly handle mappings for each user. However, the concept of “identity roles” seems a bit short-sighted since a so-called “identity role” may cease to apply when the next service is incepted.</p>
<h1 id="conflating-identity-with-permissions">Conflating identity with permissions</h1>
<p>In adding application roles and permissions to our tokens, we’ve conflated identity with permissions. The access token given to us by the Authorization Server is really about identity or “identity authorization”. Yes, it’s confusing. How can an “authorization server” not be about authorization? Well, it still is. With OAuth 2.0, subsequent to “authentication” at the Identity Provider, the user then has the ability to grant authorization to the application to act on the their behalf. So despite it being “authorization”, it’s authorization to act as that “identity”–limited by the granted scopes.</p>
<h1 id="consequences-of-identity-and-permission-conflation-in-jwts">Consequences of identity and permission conflation in JWTs</h1>
<p>When we go beyond identity in our access tokens, we’re making them into something for which they were not intended and reap the consequences of doing so. Some problems of adding permission claims include:</p>
<ul>
<li>loss of on-demand access control and permission changes until access token expires</li>
<li>large JWT payloads</li>
<li>customizations to or reliance on Identity Providers that lock you in to their products</li>
<li>loss of single responsibility (i.e. Identity Provider also dealing with application permissions)</li>
<li>wrangling of resource server frameworks and libraries to parse and handle custom JWT claims.</li>
</ul>
<p>Even if you “namespace” your roles and permissions to specific applications in your JWT payload, there are still problems. Here’s a sample fragment from a decoded JWT with “identity roles” under <code class="language-plaintext highlighter-rouge">roles</code> and namespaced “application roles” under <code class="language-plaintext highlighter-rouge">resource_access</code> for the different application audiences of the token <code class="language-plaintext highlighter-rouge">account</code>, <code class="language-plaintext highlighter-rouge">client-a</code>, <code class="language-plaintext highlighter-rouge">resource-b</code>:</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
</span><span class="nl">"sub"</span><span class="p">:</span><span class="w"> </span><span class="s2">"c4af4e2f-b432-4c3b-8405-cca86cd5b97b"</span><span class="p">,</span><span class="w">
</span><span class="nl">"scope"</span><span class="p">:</span><span class="w"> </span><span class="s2">"openid email profile authority-b"</span><span class="p">,</span><span class="w">
</span><span class="nl">"preferred_username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"user"</span><span class="p">,</span><span class="w">
</span><span class="nl">"roles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"ROLE_USER"</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nl">"resource_access"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"account"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"roles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"manage-account"</span><span class="p">,</span><span class="w">
</span><span class="s2">"manage-account-links"</span><span class="p">,</span><span class="w">
</span><span class="s2">"view-profile"</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"client-a"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"roles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"do-client-a-user-stuff"</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"resource-b"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"roles"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"do-resource-b-user-stuff"</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span></code></pre></figure>
<p>While you can overload the JWT at the Identity Provider by namespacing application roles as we see in the above JWT fragment, it has all the drawbacks we mentioned earlier. Furthermore, while this is a modified example from Keycloak, the same applies to other Identity Providers like Okta, Auth0, and others and they’ll almost certainly all differ in structure and setup.</p>
<p>Even if you avoid overloading the JWT at the Identity Provider, beware of doing something worse. I’ve seen single API Gateways designed to receive the JWT, dispose of it, and create a new custom non-OAuth 2.0 token with roles and permissions baked in. In this case you get all the downsides of baking in application roles and permissions but also lose the access token (for refreshing, standardized libraries and frameworks, etc.) and make your API Gateway overly complex.</p>
<h1 id="so-i-shouldnt-put-permissions-into-my-jwts">So, I shouldn’t put permissions into my JWTs?</h1>
<p>Correct. Don’t do it.</p>
<blockquote>
<p>Permissions are <strong>application-specific</strong> and don’t belong on an Identity Provider</p>
</blockquote>
<p>Permissions are application-specific while access tokens are for any resource server listed in the <code class="language-plaintext highlighter-rouge">aud</code> claim. Resource servers could very well understand a role or permission differently from each other.</p>
<p>Dominick Baier gives the analogy of showing your drivers license (a kind of “identity” card) in a different country or jurisdiction. Your identity (i.e. Name, Date of Birth, etc.) doesn’t change but the laws of the land may differ regarding whether or not you’re allowed to purchase alcohol there. Countries having different laws are like applications or resource servers with different authorization rules. When your token reaches a resource server, the token is saying</p>
<blockquote>
<p>“I’m here representing user <em>x</em>’s “identity” and I’ve been granted the following subset of scopes.”</p>
</blockquote>
<p>The question then is, given your presented identity, what should the application or resource server let you do? That’s an application-specific question so the answer should be contextualized by the application to which the question is being asked.</p>
<h1 id="the-case-for-a-policy-service">The case for a “policy service”</h1>
<p>I’ve heard people go as far as to say that authorization is just business logic. Despite permissions being application-specific, they don’t have to be entirely “business logic” in your code. While I agree that it is largely, if not entirely, business logic, I also believe that a good chunk of that authorization can be done in a standardized or centralizable way that is easily mockable in tests.</p>
<p>For example, your application could ask a logical “policy service” a simple <em>yes</em> or <em>no</em> question:</p>
<blockquote>
<p>“Given a specific application (or ‘policy’), user, and permission name, do they have that permission?”</p>
</blockquote>
<p>For example, say you have a note service that stores notes. You could ask the policy service, “given policy ‘note’, user ‘susan’, and permission ‘CanRead’, do they have that permission?” so that if you were trying to get all notes that ‘susan’ had access to, your logic might look something like this:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">List</span><span class="o"><</span><span class="nc">Note</span><span class="o">></span> <span class="nf">getNotes</span><span class="o">(</span><span class="nc">Jwt</span> <span class="n">accessToken</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// for access</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">policyService</span><span class="o">.</span><span class="na">hasPermission</span><span class="o">(</span><span class="s">"note"</span><span class="o">,</span> <span class="n">jwt</span><span class="o">,</span> <span class="s">"CanRead"</span><span class="o">))</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">AccessDeniedException</span><span class="o">();</span> <span class="c1">// 403</span>
<span class="o">}</span>
<span class="c1">// for filtering</span>
<span class="k">if</span> <span class="o">(</span><span class="n">policyService</span><span class="o">.</span><span class="na">hasPermission</span><span class="o">(</span><span class="s">"note"</span><span class="o">,</span> <span class="n">jwt</span><span class="o">,</span> <span class="s">"CanReadConfidentialNotes"</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">noteRepository</span><span class="o">.</span><span class="na">findAll</span><span class="o">();</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">noteRepository</span><span class="o">.</span><span class="na">findByConfidentialFalse</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>where <code class="language-plaintext highlighter-rouge">policyService.hasPermission</code> could be mocked out in your tests to return <code class="language-plaintext highlighter-rouge">true</code> or <code class="language-plaintext highlighter-rouge">false</code> depending on the particular permission and the claims in your JWT (e.g. “susan” as the <code class="language-plaintext highlighter-rouge">preferred_username</code>). Sometimes, you’ll notice that the business logic is <em>more</em> than calling the policyService. In the above filtering example, you need to know</p>
<ol>
<li>if the user has permission to read confidential notes, but also</li>
<li>if the value of <code class="language-plaintext highlighter-rouge">confidential</code> on the notes you’re returning is <code class="language-plaintext highlighter-rouge">true</code> or <code class="language-plaintext highlighter-rouge">false</code> or at least request the right kind of notes from your repository</li>
</ol>
<p>The second requirement isn’t something you can easily store on a generic policy service–it’s business logic that requires querying your own domain objects. However, the permission in the first requirement can easily be generalized and handled by a policy service.</p>
<p>If you use a policy service in this way, then it becomes easier to see the division of responsibilities between the policy service and your own application business logic. The policy service would have the responsibility of managing these rules (who has what permission for what policy) and responding to requests like <code class="language-plaintext highlighter-rouge">hasPermission</code> from applications wanting to know the answers. You business logic can compose policy service calls along with domain-specific calls in order to achieve the access rules or filtering required. Hopefully the above example shows how they can fit together. Testing also should be fairly straighforward. A policy service application could also support a slightly more complex API to return roles, store permissions on specific entities or entity types, etc. but that’s beyond the scope of this post.</p>
<p>Many more questions arise from all this such as:</p>
<ul>
<li>what is the cost of all the calls to this centralized policy service?</li>
<li>what are the caching tradeoffs?</li>
<li>is it better to just be a side-car service–co-located with the application or even a “starter dependency” that you can include to your application to reduce boiler plate?</li>
<li>should the policy service have its own UI?</li>
<li>what policies should govern the policy server itself?</li>
<li>how is the provisioning of policies accomplished?</li>
</ul>
<p>I have opinions about these questions but they’ll have to wait for another post.</p>
<h1 id="conclusion">Conclusion</h1>
<p>In conclusion, baking application roles and permissions into JWTs may work for a simple app but the customizations made to do so will lock you into that pattern and limit your ability to spin off new services with the same Identity Provider. It’s just not smart.</p>
<!--
hasPermission(policy, userId, userIdRoles, permisionName, entityId, entityType)
hasRole(policy, userId, userIdRoles, roleName)
getPermissionsOnEntity(policy, userId, userIdRoles, entityId, entityType)
getEntitiesWithPermission(policy, userId, userIdRoles, permissionName, entityType)
getUsersWithPermissionOnEntity(policy, permissionName, entityId, entityType)
getUserIdRolesWithPermissionOnEntity(policy, permissionName, entityId, entityType)
getRoles
-->stephenHere’s why…Merry Microservices: An Introduction–Reactive, Full Stack and Policy-Driven on Kubernetes2019-12-17T00:00:00+00:002019-12-17T00:00:00+00:00https://sdoxsee.github.io/blog/2019/12/17/merry-microservices-an-introduction<!-- <img border="0" src="/assets/images/merry-microservices/holly-ivy.svg" width="19%"/> -->
<!-- <img border="0" src="/assets/images/merry-microservices/candy-cane.svg" width="19%"/> -->
<!-- <img border="0" src="/assets/images/merry-microservices/tree.svg" width="19%"/> -->
<p><img border="0" src="/assets/images/merry-microservices/gift.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift2.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift3.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift2.svg" width="19%" />
<!-- <img border="0" src="/assets/images/merry-microservices/wreath.svg" width="19%"/> --></p>
<h1 id="merry-microserices">Merry Microserices!</h1>
<p>This is the start of a blog series highlighting some of the architectural patterns and frameworks that, in my opinion, make for <strong>Merry Microservices</strong>.</p>
<!-- On the backend, we'll be using Spring Boot, Spring Webflux, Spring Cloud Gateway, R2DBC, Spring Security 5.2's OAuth 2.0 Client and Resource Server support, Keycloak, OpenID Connect and OAuth 2.0.
On the front-end, we'll creating a CRUD UI with Create React App, TypeScript and Hooks but leveraging the session from a thin server-side Spring Cloud Gateway proxy.
We'll be introducing a "policy service" application that manages authorizations for specific applications based on user identity and the permissions they are granted on the "policy service". -->
<ul>
<li>
<p><a href="/blog/2019/12/17/merry-microservices-part1-resource-server"><strong>Part 1 ‘Resource Server’–An OAuth 2.0 Resource Server with Webflux and R2DBC</strong></a> will introduce a simple OAuth 2.0 resource server with Spring Boot Webflux, and Spring Data R2DBC that stores notes.</p>
</li>
<li>
<p><a href="/blog/2019/12/17/merry-microservices-part2-ui-gateway"><strong>Part 2 ‘UI Gateway’–A React UI served by a Spring Cloud Gateway OAuth 2.0 Client</strong></a> will build a CRUD front-end gateway application with Create React App, TypeScript, and Hooks. The JavaScript is served up by Spring Cloud Gateway–acting as a light server-side proxy for the React UI that safely manages OpenID Connect and OAuth 2.0 flows and that relays request back to our note resources server.</p>
</li>
<li>
<p><a href="/blog/2020/01/12/merry-microservices-part3-policy-service"><strong>Part 3 ‘Policy Service’–Managing application-specific authorization based on identity and permissions</strong></a> will demonstrate the place for a “policy service” in this stack to manage the identity permissions (or policies) specific to each application in the architecture rather than overloading the JWT, at the Identity Provider level, with irrelevant permissions.</p>
</li>
</ul>
<!-- * [**Part 4 'Jenkins X'--Bringing it all to Kubernetes**](/blog/2019/12/17/merry-microservices-part4-jenkins-x) -->
<ul>
<li><strong>Part 4 ‘Jenkins X’–Bringing it all to Kubernetes</strong> will introduce Jenkins X and how to build and deploy all of this on Google Cloud Platform’s managed Kubernetes service, GKE.</li>
</ul>
<p><img src="/assets/images/spring-framework.svg" alt="Spring Framework" width="24%" />
<img src="/assets/images/react.svg" alt="React" width="24%" />
<img src="/assets/images/openid.svg" alt="OpenID" width="24%" />
<img src="/assets/images/jenkins-x.svg" alt="Jenkins X" width="24%" /></p>
<p class="notice">Please <strong>follow me</strong> on <a href="https://twitter.com/doxsees">twitter</a> or <a href="/atom.xml">subscribe</a> to be updated as each part of this series comes out.</p>
<p>If you’d like help with any of these things, find out how what I do and how you can <strong>hire me</strong> at <a href="https://simplestep.ca">Simple Step Solutions</a></p>stephenMerry Microservices: Part 1 ‘Resource Server’–An OAuth 2.0 Resource Server with Webflux and R2DBC2019-12-17T00:00:00+00:002019-12-17T00:00:00+00:00https://sdoxsee.github.io/blog/2019/12/17/merry-microservices-part1-resource-server<p>This is Part 1 of the series “<a href="/blog/2019/12/17/merry-microservices-an-introduction">Merry Microservices</a>”</p>
<p>We’ll be introducing a simple confidential note OAuth 2.0 resource server with Spring Boot Webflux, and Spring Data R2DBC.</p>
<p>The source can be found on github at <a href="https://github.com/sdoxsee/merry-microservices/tree/part1">https://github.com/sdoxsee/merry-microservices/tree/part1</a>.</p>
<p><img border="0" src="/assets/images/merry-microservices/gift.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift2.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift3.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift2.svg" width="19%" /></p>
<div id="entry-table-of-contents" class="toc-wrapper">
<h2 id="toc-toggle" class="no_toc">
Table of Contents <i class="toc-toggle-icon fas fa-chevron-down"></i>
</h2>
<ol id="markdown-toc">
<li><a href="#preamble" id="markdown-toc-preamble">Preamble</a></li>
<li><a href="#generate-the-project" id="markdown-toc-generate-the-project">Generate the project</a></li>
<li><a href="#add-the-domain" id="markdown-toc-add-the-domain">Add the domain</a></li>
<li><a href="#add-the-controller" id="markdown-toc-add-the-controller">Add the controller</a> <ol>
<li><a href="#functional-routes" id="markdown-toc-functional-routes">Functional Routes</a></li>
<li><a href="#handler-methods" id="markdown-toc-handler-methods">Handler methods</a></li>
</ol>
</li>
<li><a href="#add-the-repository" id="markdown-toc-add-the-repository">Add the repository</a></li>
<li><a href="#add-the-schema" id="markdown-toc-add-the-schema">Add the schema</a></li>
<li><a href="#configure-applicationyml" id="markdown-toc-configure-applicationyml">Configure application.yml</a></li>
<li><a href="#use-docker-compose-to-start-keycloak" id="markdown-toc-use-docker-compose-to-start-keycloak">Use docker-compose to start Keycloak</a></li>
<li><a href="#playing-around-with-our-note-service" id="markdown-toc-playing-around-with-our-note-service">Playing around with our note service</a> <ol>
<li><a href="#start-the-spring-boot-application" id="markdown-toc-start-the-spring-boot-application">Start the Spring Boot application</a></li>
<li><a href="#create-a-note-using-postman" id="markdown-toc-create-a-note-using-postman">Create a note using Postman</a></li>
<li><a href="#get-the-note-back-using-postman" id="markdown-toc-get-the-note-back-using-postman">Get the note back using Postman</a></li>
</ol>
</li>
<li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ol>
</div>
<h1 id="preamble">Preamble</h1>
<p>I’ve spent most of my career doing Java development–especially with the Spring Framework. Java gets a fair bit of hate–some of it is merited while much of it is <a href="https://developer.okta.com/blog/2019/07/15/java-myths-2019">myth</a>. Personally, I love it. One downside, however, is that it tends to use a lot of memory; traditional blocking architectures and thread pools at scale really bring this to light.</p>
<p>With Spring Webflux, the Spring Framework has been re-architected on <a href="https://projectreactor.io/">Project Reactor</a> and <a href="https://netty.io/">Netty</a> to bring about more asynchronous, event-driven, non-blocking applications (i.e. “Reactive”). Spring Webflux has some advantages over Spring MVC including more efficient resource utilization (i.e. less threads => less memory). Those lead to cheaper cloud costs and more robust services. I like that. I find it hard to justify the resource requirements for the excessive threads in Spring MVC but, since I still love Java, Spring Webflux makes “Java in the cloud” much more palatable. It also can leverage the awesome autoconfiguration features of Spring Boot that can make it super-easy to use. Webflux has some limitations (e.g. lack of non-blocking libraries, etc.) and differences from the very support-rich servlet-based stack but we’ll see that it’s pretty cool!</p>
<h1 id="generate-the-project">Generate the project</h1>
<p>Let’s go to <a href="https://start.spring.io/#!type=maven-project&language=java&platformVersion=2.2.2.RELEASE&packaging=jar&jvmVersion=1.8&groupId=ca.simplstep&artifactId=note&name=note&description=Demo%20project%20for%20Spring%20Boot&packageName=ca.simplstep.note&dependencies=webflux,data-r2dbc,h2,oauth2-resource-server">start.spring.io</a> and generate our note.zip</p>
<p>Dependencies? <code class="language-plaintext highlighter-rouge">webflux,data-r2dbc,h2,oauth2-resource-server</code></p>
<p><img src="/assets/images/merry-microservices/part1/start.spring.io.png" alt="start.spring.io" /></p>
<p>We won’t be changing the <code class="language-plaintext highlighter-rouge">pom.xml</code> from <code class="language-plaintext highlighter-rouge">start.spring.io</code> but I do want to point out that one of our dependencies is currently experimental:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">spring-boot-starter-data-r2dbc</code> – allows for the autoconfiguration of our reactive datasource</li>
</ul>
<h1 id="add-the-domain">Add the domain</h1>
<p>Below is our very simple, yet verbose, <code class="language-plaintext highlighter-rouge">Note</code> domain class, although, with the power of an IDE, it’s probably really easy to generate the getters/setters! The key thing to recongize here is that it’s not a JPA <code class="language-plaintext highlighter-rouge">@Entity</code>. It’s a plain class with an <code class="language-plaintext highlighter-rouge">org.springframework.data.annotation.@Id</code> on the <code class="language-plaintext highlighter-rouge">id</code> field.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">class</span> <span class="nc">Note</span> <span class="o">{</span>
<span class="nd">@Id</span>
<span class="kd">private</span> <span class="nc">Long</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">text</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">confidential</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">Long</span> <span class="nf">getId</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setId</span><span class="o">(</span><span class="nc">Long</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="n">id</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getText</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">text</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setText</span><span class="o">(</span><span class="nc">String</span> <span class="n">text</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">text</span> <span class="o">=</span> <span class="n">text</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">isConfidential</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">confidential</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setConfidential</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">confidential</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">confidential</span> <span class="o">=</span> <span class="n">confidential</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">static</span> <span class="nc">Note</span> <span class="nf">create</span><span class="o">(</span><span class="nc">String</span> <span class="n">text</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nf">create</span><span class="o">(</span><span class="n">text</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">static</span> <span class="nc">Note</span> <span class="nf">create</span><span class="o">(</span><span class="nc">String</span> <span class="n">text</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">confidential</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Note</span> <span class="n">note</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Note</span><span class="o">();</span>
<span class="n">note</span><span class="o">.</span><span class="na">text</span> <span class="o">=</span> <span class="n">text</span><span class="o">;</span>
<span class="n">note</span><span class="o">.</span><span class="na">confidential</span> <span class="o">=</span> <span class="n">confidential</span><span class="o">;</span>
<span class="k">return</span> <span class="n">note</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<h1 id="add-the-controller">Add the controller</h1>
<h2 id="functional-routes">Functional Routes</h2>
<p>Now that we know what kind of domain class we’ll be working with, let’s switch over to our controller level and add our routes using the new functional routing available in Webflux</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Configuration</span>
<span class="kd">class</span> <span class="nc">WebConfig</span> <span class="kd">implements</span> <span class="nc">WebFluxConfigurer</span> <span class="o">{</span>
<span class="nd">@Bean</span>
<span class="nc">RouterFunction</span><span class="o"><</span><span class="nc">ServerResponse</span><span class="o">></span> <span class="nf">routerFunction</span><span class="o">(</span><span class="nc">NoteHandler</span> <span class="n">noteHandler</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nf">route</span><span class="o">(</span><span class="no">GET</span><span class="o">(</span><span class="s">"/api/notes"</span><span class="o">),</span> <span class="nl">noteHandler:</span><span class="o">:</span><span class="n">all</span><span class="o">)</span>
<span class="o">.</span><span class="na">andRoute</span><span class="o">(</span><span class="no">GET</span><span class="o">(</span><span class="s">"/api/notes/{id}"</span><span class="o">),</span> <span class="nl">noteHandler:</span><span class="o">:</span><span class="n">getById</span><span class="o">)</span>
<span class="o">.</span><span class="na">andRoute</span><span class="o">(</span><span class="no">PUT</span><span class="o">(</span><span class="s">"/api/notes/{id}"</span><span class="o">),</span> <span class="nl">noteHandler:</span><span class="o">:</span><span class="n">updateById</span><span class="o">)</span>
<span class="o">.</span><span class="na">andRoute</span><span class="o">(</span><span class="no">DELETE</span><span class="o">(</span><span class="s">"/api/notes/{id}"</span><span class="o">),</span> <span class="nl">noteHandler:</span><span class="o">:</span><span class="n">deleteById</span><span class="o">)</span>
<span class="o">.</span><span class="na">andRoute</span><span class="o">(</span><span class="no">POST</span><span class="o">(</span><span class="s">"/api/notes"</span><span class="o">),</span> <span class="nl">noteHandler:</span><span class="o">:</span><span class="n">create</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<h2 id="handler-methods">Handler methods</h2>
<p>These are pretty standard CRUD endpoints. It’s convenient to see all our routes defined together at a high level! Our endpoint implementations are implemented in our <code class="language-plaintext highlighter-rouge">NoteHandler</code> below:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Component</span>
<span class="kd">class</span> <span class="nc">NoteHandler</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">NoteRepository</span> <span class="n">noteRepository</span><span class="o">;</span>
<span class="nc">NoteHandler</span><span class="o">(</span><span class="nc">NoteRepository</span> <span class="n">noteRepository</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">noteRepository</span> <span class="o">=</span> <span class="n">noteRepository</span><span class="o">;</span>
<span class="o">}</span>
<span class="nc">Mono</span><span class="o"><</span><span class="nc">ServerResponse</span><span class="o">></span> <span class="nf">getById</span><span class="o">(</span><span class="nc">ServerRequest</span> <span class="n">r</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nf">defaultReadResponse</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">noteRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">id</span><span class="o">(</span><span class="n">r</span><span class="o">)));</span>
<span class="o">}</span>
<span class="nc">Mono</span><span class="o"><</span><span class="nc">ServerResponse</span><span class="o">></span> <span class="nf">all</span><span class="o">(</span><span class="nc">ServerRequest</span> <span class="n">r</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nf">defaultReadResponse</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">noteRepository</span><span class="o">.</span><span class="na">findAll</span><span class="o">());</span>
<span class="o">}</span>
<span class="nc">Mono</span><span class="o"><</span><span class="nc">ServerResponse</span><span class="o">></span> <span class="nf">deleteById</span><span class="o">(</span><span class="nc">ServerRequest</span> <span class="n">r</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Mono</span><span class="o"><</span><span class="nc">Note</span><span class="o">></span> <span class="n">noteMono</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">noteRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">id</span><span class="o">(</span><span class="n">r</span><span class="o">))</span>
<span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">n</span> <span class="o">-></span> <span class="k">this</span><span class="o">.</span><span class="na">noteRepository</span><span class="o">.</span><span class="na">deleteById</span><span class="o">(</span><span class="n">id</span><span class="o">(</span><span class="n">r</span><span class="o">)).</span><span class="na">thenReturn</span><span class="o">(</span><span class="n">n</span><span class="o">));</span>
<span class="k">return</span> <span class="nf">defaultReadResponse</span><span class="o">(</span><span class="n">noteMono</span><span class="o">);</span>
<span class="o">}</span>
<span class="nc">Mono</span><span class="o"><</span><span class="nc">ServerResponse</span><span class="o">></span> <span class="nf">updateById</span><span class="o">(</span><span class="nc">ServerRequest</span> <span class="n">r</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Flux</span><span class="o"><</span><span class="nc">Note</span><span class="o">></span> <span class="n">noteFlux</span> <span class="o">=</span> <span class="n">r</span>
<span class="o">.</span><span class="na">bodyToFlux</span><span class="o">(</span><span class="nc">Note</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">toWrite</span> <span class="o">-></span> <span class="k">this</span><span class="o">.</span><span class="na">noteRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">toWrite</span><span class="o">));</span>
<span class="k">return</span> <span class="nf">defaultWriteResponse</span><span class="o">(</span><span class="n">noteFlux</span><span class="o">);</span>
<span class="o">}</span>
<span class="nc">Mono</span><span class="o"><</span><span class="nc">ServerResponse</span><span class="o">></span> <span class="nf">create</span><span class="o">(</span><span class="nc">ServerRequest</span> <span class="n">r</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Flux</span><span class="o"><</span><span class="nc">Note</span><span class="o">></span> <span class="n">flux</span> <span class="o">=</span> <span class="n">r</span>
<span class="o">.</span><span class="na">bodyToFlux</span><span class="o">(</span><span class="nc">Note</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">toWrite</span> <span class="o">-></span> <span class="k">this</span><span class="o">.</span><span class="na">noteRepository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">toWrite</span><span class="o">));</span>
<span class="k">return</span> <span class="nf">defaultWriteResponse</span><span class="o">(</span><span class="n">flux</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">Mono</span><span class="o"><</span><span class="nc">ServerResponse</span><span class="o">></span> <span class="nf">defaultWriteResponse</span><span class="o">(</span><span class="nc">Publisher</span><span class="o"><</span><span class="nc">Note</span><span class="o">></span> <span class="n">notes</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Mono</span>
<span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="n">notes</span><span class="o">)</span>
<span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">n</span> <span class="o">-></span> <span class="nc">ServerResponse</span>
<span class="o">.</span><span class="na">created</span><span class="o">(</span><span class="no">URI</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="s">"/notes/"</span> <span class="o">+</span> <span class="n">n</span><span class="o">.</span><span class="na">getId</span><span class="o">()))</span>
<span class="o">.</span><span class="na">contentType</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_JSON</span><span class="o">)</span>
<span class="o">.</span><span class="na">build</span><span class="o">()</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">Mono</span><span class="o"><</span><span class="nc">ServerResponse</span><span class="o">></span> <span class="nf">defaultReadResponse</span><span class="o">(</span><span class="nc">Publisher</span><span class="o"><</span><span class="nc">Note</span><span class="o">></span> <span class="n">notes</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">ServerResponse</span>
<span class="o">.</span><span class="na">ok</span><span class="o">()</span>
<span class="o">.</span><span class="na">contentType</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_JSON</span><span class="o">)</span>
<span class="o">.</span><span class="na">body</span><span class="o">(</span><span class="n">notes</span><span class="o">,</span> <span class="nc">Note</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">Long</span> <span class="nf">id</span><span class="o">(</span><span class="nc">ServerRequest</span> <span class="n">r</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Long</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">pathVariable</span><span class="o">(</span><span class="s">"id"</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>Although I don’t recommend delegating directly to your repository in your handler methods (use a service layer!), you can see it’s pretty easy to do so. Since I’ve borrowed the shell of this handler from Matt Raible’s <a href="https://developer.okta.com/blog/2018/09/25/spring-webflux-websockets-react">post</a> (with a few modifications), I defer to his explanations for the purpose of brevity.</p>
<h1 id="add-the-repository">Add the repository</h1>
<p>Next, let’s look at our repository layer. Spring Data R2DBC is not an ORM–as explained on Spring Data R2DBC’s <a href="https://github.com/spring-projects/spring-data-r2dbc#this-is-not-an-orm">github</a> page…</p>
<blockquote>
<p>Spring Data R2DBC aims at being conceptually easy. In order to achieve this it does NOT offer caching, lazy loading, write behind or many other features of ORM frameworks. This makes Spring Data R2DBC a simple, limited, opinionated object mapper.</p>
</blockquote>
<p>Despite Spring Data R2DBC not being an ORM (which is arguably a good thing), there’s still a lot of power like you’re used to with Spring Data JPA repositories. We can still inherit basic CRUD methods without forfeiting the ability to add our own custom ones.</p>
<p class="notice"><strong>Notice</strong>: <a href="https://github.com/querydsl/querydsl/issues/2468">QueryDsl does not yet support R2DBC</a> (so add your votes!)</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">interface</span> <span class="nc">NoteRepository</span> <span class="kd">extends</span> <span class="nc">ReactiveCrudRepository</span><span class="o"><</span><span class="nc">Note</span><span class="o">,</span> <span class="nc">Long</span><span class="o">></span> <span class="o">{</span>
<span class="nd">@Query</span><span class="o">(</span><span class="s">"SELECT * FROM note WHERE text = :text"</span><span class="o">)</span>
<span class="nc">Flux</span><span class="o"><</span><span class="nc">Note</span><span class="o">></span> <span class="nf">findByText</span><span class="o">(</span><span class="nc">String</span> <span class="n">text</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<h1 id="add-the-schema">Add the schema</h1>
<p>Now for our schema. In my opinion, Flyway or liquibase are great choices for database revision management. However, for this demo, we’ll simply include <code class="language-plaintext highlighter-rouge">src/main/resources/schema.sql</code> and Spring Boot will automatically apply it on its embedded h2 server. We don’t have Hibernate’s DDL tricks at our disposal here but you might be able to leverage Liquibase (see Kenny Bastani’s delete <a href="https://webcache.googleusercontent.com/search?q=cache:Mq9jYArx9ycJ:https://twitter.com/statuses/1073311008234631168+&cd=1&hl=en&ct=clnk&gl=ca">tweet</a>). Here’s our uber-simple schema…</p>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">note</span> <span class="p">(</span><span class="n">id</span> <span class="nb">SERIAL</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span> <span class="nb">text</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">255</span><span class="p">),</span> <span class="n">confidential</span> <span class="nb">BIT</span><span class="p">);</span></code></pre></figure>
<h1 id="configure-applicationyml">Configure application.yml</h1>
<p>In our <code class="language-plaintext highlighter-rouge">src/main/resources/application.yml</code>, we’ve got minimal configuration that sets our port and the <code class="language-plaintext highlighter-rouge">issuer-uri</code> for our locally running identity provider, keycloak.</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">server</span><span class="pi">:</span>
<span class="na">port</span><span class="pi">:</span> <span class="m">8081</span>
<span class="na">spring</span><span class="pi">:</span>
<span class="na">security</span><span class="pi">:</span>
<span class="na">oauth2</span><span class="pi">:</span>
<span class="na">resourceserver</span><span class="pi">:</span>
<span class="na">jwt</span><span class="pi">:</span>
<span class="na">issuer-uri</span><span class="pi">:</span> <span class="s">http://localhost:9080/auth/realms/jhipster</span></code></pre></figure>
<h1 id="use-docker-compose-to-start-keycloak">Use docker-compose to start Keycloak</h1>
<p>Why the is our issuer using the realm <code class="language-plaintext highlighter-rouge">jhipster</code>? I was too lazy to configure keycloak for something else and took the default realm-config and users from what jhipster provides :) It includes</p>
<ul>
<li>two realms (<code class="language-plaintext highlighter-rouge">master</code> and <code class="language-plaintext highlighter-rouge">jhipster</code>)</li>
<li><code class="language-plaintext highlighter-rouge">master</code> realm includes 1 user: <code class="language-plaintext highlighter-rouge">admin/admin</code></li>
<li><code class="language-plaintext highlighter-rouge">jhipster</code> realm includes 2 users: <code class="language-plaintext highlighter-rouge">admin/admin</code> (different from <code class="language-plaintext highlighter-rouge">master</code>’s) and <code class="language-plaintext highlighter-rouge">user/user</code></li>
<li>default identity role for <code class="language-plaintext highlighter-rouge">jhipster</code> admin: <code class="language-plaintext highlighter-rouge">ROLE_ADMIN</code></li>
<li>default identity role for <code class="language-plaintext highlighter-rouge">jhipster</code> user: <code class="language-plaintext highlighter-rouge">ROLE_USER</code></li>
<li>default client id and secret of <code class="language-plaintext highlighter-rouge">web_app/web_app</code> configured for authorization_code flow</li>
</ul>
<p>and, it’s really easy to start up on port 9080 with docker-compose! We’ve included a copy of it in the root directory of our github repo</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">docker-compose up</code></pre></figure>
<p>Go to <a href="http://localhost:9080/auth/admin">http://localhost:9080/auth/admin</a> and log in with <code class="language-plaintext highlighter-rouge">admin/admin</code>.</p>
<h1 id="playing-around-with-our-note-service">Playing around with our note service</h1>
<h2 id="start-the-spring-boot-application">Start the Spring Boot application</h2>
<p>That’s it! Fire up the app from the <code class="language-plaintext highlighter-rouge">note</code> directory</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">./mvnw spring-boot:run</code></pre></figure>
<p>Once your app is up and running, head over to your favourite http tool to start interacting with our note service. We’ll use Postman.</p>
<h2 id="create-a-note-using-postman">Create a note using Postman</h2>
<p>Create a note using a <code class="language-plaintext highlighter-rouge">POST</code> to <code class="language-plaintext highlighter-rouge">http://localhost:8081/api/notes</code> with the a JSON body as follows:</p>
<p><img src="/assets/images/merry-microservices/part1/postman-json-body.png" alt="postman-json-body" /></p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
</span><span class="nl">"text"</span><span class="p">:</span><span class="s2">"Here's a confidential note"</span><span class="p">,</span><span class="w">
</span><span class="nl">"confidential"</span><span class="p">:</span><span class="w"> </span><span class="s2">"true"</span><span class="w">
</span><span class="p">}</span></code></pre></figure>
<p>Go to your Authorization section, click the “Get New Access Token” button, and fill in the OAuth 2.0 configuration for your identity provider. Our configuration is as follows:</p>
<p class="notice"><strong>Tip</strong>: If you you need to look up the endpoints, use the <a href="http://localhost:9080/auth/realms/jhipster/.well-known/openid-configuration">discovery endpoint</a> as your guide</p>
<p><img src="/assets/images/merry-microservices/part1/postman-oauth-config.png" alt="postman-oauth-config" /></p>
<p>Request the token, use the token, and then “Send” your request. You should see a 201 response for your newly created note.</p>
<h2 id="get-the-note-back-using-postman">Get the note back using Postman</h2>
<p>Similar to creating a note, we can change our HTTP Method to <code class="language-plaintext highlighter-rouge">GET</code> at the same endpoint, <code class="language-plaintext highlighter-rouge">http://localhost:8081/api/notes</code>, get an access token as before, and “Send” the request. We should see a 200 response containing the note we created earlier that looks something like this:</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Here's a confidential note"</span><span class="p">,</span><span class="w">
</span><span class="nl">"confidential"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span></code></pre></figure>
<h1 id="conclusion">Conclusion</h1>
<p>So now, we’ve got a fully reactive OAuth 2.0 Resource Server built with Spring Boot Webflux app and R2DBC.</p>
<p>Next time (in <a href="/blog/2019/12/17/merry-microservices-part2-ui-gateway">Part 2</a>), we’ll create a gateway application with React (<a href="https://github.com/facebook/create-react-app">Create React App</a>) and Spring Cloud Gateway (with Webflux) that will</p>
<ol>
<li>Serve up our React CRUD application with a great development experience</li>
<li>Manage all the OAuth 2.0 and OpenID Connect token flows automatically and safely with Spring Security 5.2.x and relay the access tokens to our note service</li>
</ol>
<p>Let me know what you think in the comments section.</p>
<p>Thanks for reading!</p>
<p class="notice">Please <strong>follow me</strong> on <a href="https://twitter.com/doxsees">twitter</a> or <a href="/atom.xml">subscribe</a> to be updated as each part of this series comes out.</p>
<p>If you’d like help with any of these things, find out how what I do and how you can <strong>hire me</strong> at <a href="https://simplestep.ca">Simple Step Solutions</a></p>stephenThis is Part 1 of the series “Merry Microservices”Merry Microservices: Part 2 ‘UI Gateway’–A React UI served by a Spring Cloud Gateway OAuth 2.0 Client2019-12-17T00:00:00+00:002019-12-17T00:00:00+00:00https://sdoxsee.github.io/blog/2019/12/17/merry-microservices-part2-ui-gateway<p>This is Part 2 of the series “<a href="/blog/2019/12/17/merry-microservices-an-introduction">Merry Microservices</a>”</p>
<p>The source can be found on github at <a href="https://github.com/sdoxsee/merry-microservices/tree/part2">https://github.com/sdoxsee/merry-microservices/tree/part2</a>.</p>
<p><img border="0" src="/assets/images/merry-microservices/gift.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift2.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift3.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift.svg" width="19%" />
<img border="0" src="/assets/images/merry-microservices/gift2.svg" width="19%" /></p>
<div id="entry-table-of-contents" class="toc-wrapper">
<h2 id="toc-toggle" class="no_toc">
Table of Contents <i class="toc-toggle-icon fas fa-chevron-down"></i>
</h2>
<ol id="markdown-toc">
<li><a href="#preamble" id="markdown-toc-preamble">Preamble</a></li>
<li><a href="#generate-the-project" id="markdown-toc-generate-the-project">Generate the project</a> <ol>
<li><a href="#server-side" id="markdown-toc-server-side">Server side</a></li>
<li><a href="#front-end" id="markdown-toc-front-end">Front end</a></li>
</ol>
</li>
<li><a href="#add-reactrstrap-dependencies" id="markdown-toc-add-reactrstrap-dependencies">Add Reactrstrap dependencies</a></li>
<li><a href="#convert-crud-app-to-typescript" id="markdown-toc-convert-crud-app-to-typescript">Convert CRUD app to TypeScript</a></li>
<li><a href="#convert-look-and-feel-with-reactstrap" id="markdown-toc-convert-look-and-feel-with-reactstrap">Convert look and feel with Reactstrap</a></li>
<li><a href="#authentication-and-security" id="markdown-toc-authentication-and-security">Authentication and Security</a> <ol>
<li><a href="#front-end-security" id="markdown-toc-front-end-security">Front end security</a></li>
<li><a href="#back-end-security" id="markdown-toc-back-end-security">Back end security</a> <ol>
<li><a href="#yaml-oauth-20-configuration" id="markdown-toc-yaml-oauth-20-configuration">YAML OAuth 2.0 configuration</a></li>
<li><a href="#spring-security-configuration" id="markdown-toc-spring-security-configuration">Spring Security configuration</a></li>
</ol>
</li>
</ol>
</li>
<li><a href="#proxy-configuration" id="markdown-toc-proxy-configuration">Proxy configuration</a> <ol>
<li><a href="#honour-proxys-x-forward--headers-in-webflux" id="markdown-toc-honour-proxys-x-forward--headers-in-webflux">Honour proxy’s <code class="language-plaintext highlighter-rouge">x-forward-*</code> headers in webflux</a></li>
<li><a href="#add-express-proxy-configuration-to-proxy-to-our-gateway-server" id="markdown-toc-add-express-proxy-configuration-to-proxy-to-our-gateway-server">Add express proxy configuration to proxy to our gateway server</a></li>
</ol>
</li>
<li><a href="#start-keycloak" id="markdown-toc-start-keycloak">Start keycloak</a></li>
<li><a href="#set-up-our-server-side-routes" id="markdown-toc-set-up-our-server-side-routes">Set up our server-side routes</a></li>
<li><a href="#ajaxify-front-end-crud" id="markdown-toc-ajaxify-front-end-crud">Ajaxify front-end CRUD</a></li>
<li><a href="#relay-api-calls-to-resource-servers" id="markdown-toc-relay-api-calls-to-resource-servers">Relay API calls to resource servers</a></li>
<li><a href="#start-the-gateway-in-development-mode" id="markdown-toc-start-the-gateway-in-development-mode">Start the gateway in development mode</a></li>
<li><a href="#get-ready-for-production" id="markdown-toc-get-ready-for-production">Get ready for production</a></li>
<li><a href="#configure-maven-plugins" id="markdown-toc-configure-maven-plugins">Configure maven plugins</a> <ol>
<li><a href="#frontend-maven-plugin" id="markdown-toc-frontend-maven-plugin">frontend-maven-plugin</a></li>
<li><a href="#maven-resources-plugin" id="markdown-toc-maven-resources-plugin">maven-resources-plugin</a></li>
<li><a href="#maven-clean-plugin" id="markdown-toc-maven-clean-plugin">maven-clean-plugin</a></li>
</ol>
</li>
<li><a href="#add-indexhtml-to--mapping" id="markdown-toc-add-indexhtml-to--mapping">Add index.html to “/” mapping</a></li>
<li><a href="#done" id="markdown-toc-done">Done!</a></li>
<li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ol>
</div>
<h1 id="preamble">Preamble</h1>
<p>There are different opinions on whether or not to keep the UI separate from the backend API.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I write a lot of Java API + JavaScript App tutorials. If you’ve read them, thanks! <br /><br />I’m curious to know which deployment model you prefer. Results may influence future tutorials/talks. 😉<br /><br />Do you prefer:</p>— Matt Raible (@mraible) <a href="https://twitter.com/mraible/status/1032353786713391104?ref_src=twsrc%5Etfw">August 22, 2018</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>At face value, it looks like people like them to be separated. However, I think the question needs more unpacking (Dave Syer does a great job of <a href="https://spring.io/guides/tutorials/spring-security-and-angular-js/#_help_how_is_my_application_going_to_scale">discussing</a> this).</p>
<p>My take on that poll is that people don’t want to restart their backend to see changes in their UI code. I 100% agree with that. However, you can still have that developer experience and serve up the UI along with a backend of some sort. In the case of a monolith, your full backend API would be part of your bundle. In the case of a microservice gateway, you can still serve up your application with a thin server-side gateway that manages your authentication, session, OAuth 2.0 access tokens, and request routing. That’s what we’re going to do in this tutorial. Devs may also be worried about fighting with UI and server configuration when serving up the UI along with a backend but I’ll show you how easy it is!</p>
<p>This tutorial builds on the great work of others. <a href="https://twitter.com/taniarascia">Tania Rascia</a> has a simple standalone CRUD front-end built with Create React App and Hooks <a href="https://github.com/taniarascia/react-hooks">https://github.com/taniarascia/react-hooks</a>. I love it because it’s simple! However, as a good Java developer, we have to add TypeScript and, until I learn <a href="https://www.styled-components.com/">Styled Components</a>, my go-to UI style is Bootstrap with Reactstrap. We also add some OpenID Connect Authentication by using some techniques by <a href="https://twitter.com/mraible">Matt Raible</a> in <a href="https://developer.okta.com/blog/2018/07/19/simple-crud-react-and-spring-boot">Use React and Spring Boot to Build a Simple CRUD App</a> and make the backend a Spring Cloud Gateway (Webflux) and OAuth 2.0 Client.</p>
<h1 id="generate-the-project">Generate the project</h1>
<h2 id="server-side">Server side</h2>
<p>Let’s go to <a href="https://start.spring.io/#!type=maven-project&language=java&platformVersion=2.2.2.RELEASE&packaging=jar&jvmVersion=1.8&groupId=ca.simplestep&artifactId=gateway&name=gateway&description=Demo%20project%20for%20Spring%20Boot&packageName=ca.simplestep.gateway&dependencies=cloud-gateway,oauth2-client,security">start.spring.io</a> and generate our gateway.zip</p>
<p>Dependencies? <code class="language-plaintext highlighter-rouge">cloud-gateway,oauth2-client,security</code></p>
<p><img src="/assets/images/merry-microservices/part2/gateway.start.spring.io.png" alt="start.spring.io" /></p>
<h2 id="front-end">Front end</h2>
<p>Make sure you have <a href="https://nodejs.org/">node.js</a> installed. I’ve got <code class="language-plaintext highlighter-rouge">v10.16.3</code> but the current LTS (<code class="language-plaintext highlighter-rouge">v12.x</code>) should work fine. My npm is <code class="language-plaintext highlighter-rouge">6.0.0</code>.</p>
<p>Put your gateway.zip file where you want your combined UI and Gateway code to live. Then unzip it and create your react app</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">unzip gateway.zip <span class="o">&&</span> <span class="nb">mkdir </span>gateway/src/main <span class="o">&&</span> <span class="nb">cd </span>gateway/src/main <span class="o">&&</span> npx create-react-app app <span class="nt">--template</span> typescript <span class="o">&&</span> <span class="nb">cd </span>app <span class="o">&&</span> npm start</code></pre></figure>
<p>We should see a simple react app on http://localhost:3000</p>
<h1 id="add-reactrstrap-dependencies">Add Reactrstrap dependencies</h1>
<p>Next, add <a href="https://reactstrap.github.io/">reactstrap</a> dependencies</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">npm <span class="nb">install</span> <span class="nt">--save</span> bootstrap
npm <span class="nb">install</span> <span class="nt">--save</span> reactstrap react react-dom
npm <span class="nb">install</span> <span class="nt">--save</span> @types/reactstrap</code></pre></figure>
<p class="notice">The <code class="language-plaintext highlighter-rouge">@types/reactstrap</code> is so that TypeScript knows the types needed for Reactstrap components.</p>
<p>Now we can import it to our <code class="language-plaintext highlighter-rouge">src/index.tsx</code></p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="dl">'</span><span class="s1">bootstrap/dist/css/bootstrap.min.css</span><span class="dl">'</span><span class="p">;</span></code></pre></figure>
<h1 id="convert-crud-app-to-typescript">Convert CRUD app to TypeScript</h1>
<p>I won’t go through all the details of how convert Tania Rascia’s CRUD example into TypeScript and Reactstrap, but that’s what I did.</p>
<p>For TypeScript, I basically, copied the <code class="language-plaintext highlighter-rouge">.js</code> files, converted them to <code class="language-plaintext highlighter-rouge">.tsx</code>, added types to make errors go away. I create a common <code class="language-plaintext highlighter-rouge">Note.tsx</code> that could be used to represent notes in the different files.</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="k">export</span> <span class="kr">interface</span> <span class="nx">Note</span> <span class="p">{</span>
<span class="nl">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nl">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">confidential</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<h1 id="convert-look-and-feel-with-reactstrap">Convert look and feel with Reactstrap</h1>
<p>For Reactstrap, I just changed things like <code class="language-plaintext highlighter-rouge"><input></code> to Reactstrap’s <code class="language-plaintext highlighter-rouge"><Input></code> and so on :)</p>
<p>In the end I had a working CRUD app running on <code class="language-plaintext highlighter-rouge">http://localhost:3000</code> but the state was all stored in the browser and, of course, there was no authentication or access tokens with which we could call a secured backend.</p>
<h1 id="authentication-and-security">Authentication and Security</h1>
<h2 id="front-end-security">Front end security</h2>
<p>At this point I leaned on Matt Raible’s React example to call a yet-to-be-implemented <code class="language-plaintext highlighter-rouge">/api/user</code> endpoint that returns either an empty string or the username (if authenticated) but with Hooks!</p>
<p>In <code class="language-plaintext highlighter-rouge">App.tsx</code>, I did the following:</p>
<figure class="highlight"><pre><code class="language-tsx" data-lang="tsx"><span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="c1">// Setting state</span>
<span class="kd">const</span> <span class="p">[</span> <span class="nx">isAuthenticated</span><span class="p">,</span> <span class="nx">setAuthenticated</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
<span class="kd">const</span> <span class="p">[</span> <span class="nx">authenticatedUser</span><span class="p">,</span> <span class="nx">setAuthenticatedUser</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">''</span><span class="p">)</span>
<span class="kd">const</span> <span class="p">[</span> <span class="nx">cookies</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">useCookies</span><span class="p">([</span><span class="dl">'</span><span class="s1">XSRF-TOKEN</span><span class="dl">'</span><span class="p">])</span>
<span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// Create a scoped async function in the hook</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">runAsync</span><span class="p">()</span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/api/user</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">body</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">text</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">body</span> <span class="o">===</span> <span class="dl">''</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">setAuthenticated</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
<span class="nx">setAuthenticatedUser</span><span class="p">(</span><span class="dl">''</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">setAuthenticated</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
<span class="nx">setAuthenticatedUser</span><span class="p">(</span><span class="nx">body</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
<span class="c1">// add better error handling here</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// Execute the created function directly</span>
<span class="nx">runAsync</span><span class="p">()</span>
<span class="p">},[])</span>
<span class="c1">// ...</span>
<span class="p">}</span></code></pre></figure>
<p>Since our <code class="language-plaintext highlighter-rouge">/api/user</code> call is async, we put it in an <code class="language-plaintext highlighter-rouge">async</code> function, <code class="language-plaintext highlighter-rouge">runAsync</code>, and invoke it with <code class="language-plaintext highlighter-rouge">runAsync()</code> after defining it. Once we get the body of our response we’ll know if we have a authenticated user or not by whether the body (i.e. the username) is an empty string or not. Depending on whether or not we have a username come back, we set the state accordingly. As for the <code class="language-plaintext highlighter-rouge">const [ cookies ] = useCookies(['XSRF-TOKEN'])</code>, we’ll get to that in a minute :)</p>
<p>Next we want to hide the UI if we are not authenticated and show a login or logout button depending if the value of <code class="language-plaintext highlighter-rouge">isAuthenticated</code> is <code class="language-plaintext highlighter-rouge">false</code> or <code class="language-plaintext highlighter-rouge">true</code> respectively</p>
<figure class="highlight"><pre><code class="language-tsx" data-lang="tsx"><span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="kd">const</span> <span class="nx">login</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">port</span> <span class="o">=</span> <span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">port</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">:</span><span class="dl">'</span> <span class="o">+</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">port</span> <span class="p">:</span> <span class="dl">''</span><span class="p">);</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">//</span><span class="dl">'</span> <span class="o">+</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">hostname</span> <span class="o">+</span> <span class="nx">port</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/private</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p"><</span><span class="nc">Container</span><span class="p">></span>
<span class="p"><</span><span class="nc">Jumbotron</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>CRUD App with Hooks<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="si">{</span><span class="nx">isAuthenticated</span> <span class="p">?</span>
<span class="p"><</span><span class="nc">Form</span> <span class="na">action</span><span class="p">=</span><span class="s">"/logout"</span> <span class="na">method</span><span class="p">=</span><span class="s">"POST"</span><span class="p">></span>
<span class="p"><</span><span class="nc">Input</span> <span class="na">type</span><span class="p">=</span><span class="s">"hidden"</span> <span class="na">name</span><span class="p">=</span><span class="s">"_csrf"</span> <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">cookies</span><span class="p">[</span><span class="dl">'</span><span class="s1">XSRF-TOKEN</span><span class="dl">'</span><span class="p">]</span><span class="si">}</span><span class="p">/></span>
<span class="p"><</span><span class="nt">h3</span><span class="p">></span>Welcome <span class="si">{</span><span class="nx">authenticatedUser</span><span class="si">}</span>!<span class="p"></</span><span class="nt">h3</span><span class="p">><</span><span class="nc">Button</span> <span class="na">color</span><span class="p">=</span><span class="s">"secondary"</span><span class="p">></span>Logout<span class="p"></</span><span class="nc">Button</span><span class="p">></span>
<span class="p"></</span><span class="nc">Form</span><span class="p">></span> <span class="p">:</span>
<span class="p"><</span><span class="nc">Button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">login</span><span class="si">}</span><span class="p">></span>Login<span class="p"></</span><span class="nc">Button</span><span class="p">></span>
<span class="si">}</span>
<span class="p"></</span><span class="nc">Jumbotron</span><span class="p">></span>
<span class="si">{</span><span class="nx">isAuthenticated</span> <span class="o">&&</span>
<span class="p"><</span><span class="nc">Row</span><span class="p">></span>
<span class="p"><</span><span class="err">!--</span> <span class="err">...</span> <span class="err">--</span><span class="p">></span>
<span class="p"></</span><span class="nc">Row</span><span class="p">></span>
}
<span class="p"></</span><span class="nc">Container</span><span class="p">></span>
<span class="p">)</span>
<span class="si">}</span></code></pre></figure>
<p>In the above tsx code, we define a <code class="language-plaintext highlighter-rouge">login</code> function that, when we click the login button, we get redirected to <code class="language-plaintext highlighter-rouge">/private</code>. The purpose of this is to hit a secured server-side endpoint that will force authentication with the configured Identity Provider, Keycloak in our case. For our logout button, we put it in a form that submits a <code class="language-plaintext highlighter-rouge">POST</code> to <code class="language-plaintext highlighter-rouge">/logout</code> and include a hidden input named <code class="language-plaintext highlighter-rouge">_csrf</code> with the value from a cookie named <code class="language-plaintext highlighter-rouge">XSRF_TOKEN</code>.</p>
<h2 id="back-end-security">Back end security</h2>
<p>We’re now getting to the point where we need to jump to the server-side to understand what’s going on.</p>
<h3 id="yaml-oauth-20-configuration">YAML OAuth 2.0 configuration</h3>
<p>How does Spring Security know to redirect us to Keycloak? Well, we tell set it up with an OAuth 2.0 client registration. Here’s a part of our <code class="language-plaintext highlighter-rouge">src/main/resources/application.yml</code>:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="c1"># ...</span>
<span class="na">spring</span><span class="pi">:</span>
<span class="na">security</span><span class="pi">:</span>
<span class="na">oauth2</span><span class="pi">:</span>
<span class="na">client</span><span class="pi">:</span>
<span class="na">registration</span><span class="pi">:</span>
<span class="na">login-client</span><span class="pi">:</span>
<span class="na">provider</span><span class="pi">:</span> <span class="s">keycloak</span>
<span class="na">client-id</span><span class="pi">:</span> <span class="s">web_app</span>
<span class="na">client-secret</span><span class="pi">:</span> <span class="s">web_app</span>
<span class="na">scope</span><span class="pi">:</span> <span class="s">openid,profile,email</span>
<span class="na">provider</span><span class="pi">:</span>
<span class="na">keycloak</span><span class="pi">:</span>
<span class="na">issuer-uri</span><span class="pi">:</span> <span class="s">http://localhost:9080/auth/realms/jhipster</span>
<span class="c1"># ...</span></code></pre></figure>
<p>Above we define a <code class="language-plaintext highlighter-rouge">provider</code> that we call <code class="language-plaintext highlighter-rouge">keycloak</code> whose meta information (i.e. endpoints, supported features, etc.) can be found at the <code class="language-plaintext highlighter-rouge">issuer-uri</code>. It’s called the <code class="language-plaintext highlighter-rouge">discovery endpoint</code> and, once Keycloak has started up, you can check it out yourself at <a href="http://localhost:9080/auth/realms/jhipster/.well-known/openid-configuration">http://localhost:9080/auth/realms/jhipster/.well-known/openid-configuration</a>.</p>
<p>In the <code class="language-plaintext highlighter-rouge">registration</code> section, we name our client <code class="language-plaintext highlighter-rouge">login-client</code> and link it to our <code class="language-plaintext highlighter-rouge">keycloak</code> provider. Out Keycloak realm has been pre-setup with a client that has the client id <code class="language-plaintext highlighter-rouge">web_app</code> and client secret <code class="language-plaintext highlighter-rouge">web_app</code>. Of course you’ll change the client secret in your hosted environments! Finally, we request four scopes <code class="language-plaintext highlighter-rouge">openid,profile,email</code> so that Keycloak will allow us to get user information and, most importantly, an <code class="language-plaintext highlighter-rouge">id_token</code> AND an <code class="language-plaintext highlighter-rouge">access_token</code> for authentication and authorization respectively.</p>
<h3 id="spring-security-configuration">Spring Security configuration</h3>
<p>Once we’ve added the YAML configuration, we need to customize our <code class="language-plaintext highlighter-rouge">SecurityWebFilterChain</code> to cause Spring Boot’s otherwise-autoconfigured one to back off.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@EnableWebFluxSecurity</span>
<span class="kd">class</span> <span class="nc">SecurityConfiguration</span> <span class="o">{</span>
<span class="c1">// ...</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">SecurityWebFilterChain</span> <span class="nf">securityWebFilterChain</span><span class="o">(</span><span class="nc">ServerHttpSecurity</span> <span class="n">http</span><span class="o">)</span> <span class="o">{</span>
<span class="n">http</span>
<span class="o">.</span><span class="na">oauth2Login</span><span class="o">(</span><span class="n">withDefaults</span><span class="o">())</span>
<span class="o">.</span><span class="na">csrf</span><span class="o">(</span><span class="n">csrf</span> <span class="o">-></span> <span class="n">csrf</span><span class="o">.</span><span class="na">csrfTokenRepository</span><span class="o">(</span><span class="nc">CookieServerCsrfTokenRepository</span><span class="o">.</span><span class="na">withHttpOnlyFalse</span><span class="o">()))</span>
<span class="o">.</span><span class="na">authorizeExchange</span><span class="o">(</span><span class="n">exchanges</span> <span class="o">-></span>
<span class="n">exchanges</span>
<span class="o">.</span><span class="na">pathMatchers</span><span class="o">(</span><span class="s">"/manifest.json"</span><span class="o">,</span> <span class="s">"/*.png"</span><span class="o">,</span> <span class="s">"/static/**"</span><span class="o">,</span> <span class="s">"/api/user"</span><span class="o">,</span> <span class="s">"/"</span><span class="o">).</span><span class="na">permitAll</span><span class="o">()</span>
<span class="o">.</span><span class="na">anyExchange</span><span class="o">().</span><span class="na">authenticated</span><span class="o">()</span>
<span class="o">)</span>
<span class="o">.</span><span class="na">logout</span><span class="o">(</span><span class="n">logout</span> <span class="o">-></span>
<span class="n">logout</span>
<span class="o">.</span><span class="na">logoutSuccessHandler</span><span class="o">(</span><span class="n">oidcLogoutSuccessHandler</span><span class="o">()));</span>
<span class="k">return</span> <span class="n">http</span><span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="c1">// see https://github.com/spring-projects/spring-security/issues/5766#issuecomment-564636167</span>
<span class="nd">@Bean</span>
<span class="nc">WebFilter</span> <span class="nf">addCsrfToken</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="o">(</span><span class="n">exchange</span><span class="o">,</span> <span class="n">next</span><span class="o">)</span> <span class="o">-></span> <span class="n">exchange</span>
<span class="o">.<</span><span class="nc">Mono</span><span class="o"><</span><span class="nc">CsrfToken</span><span class="o">>></span><span class="n">getAttribute</span><span class="o">(</span><span class="nc">CsrfToken</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">())</span>
<span class="o">.</span><span class="na">doOnSuccess</span><span class="o">(</span><span class="n">token</span> <span class="o">-></span> <span class="o">{})</span>
<span class="o">.</span><span class="na">then</span><span class="o">(</span><span class="n">next</span><span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">exchange</span><span class="o">));</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="nc">ServerLogoutSuccessHandler</span> <span class="nf">oidcLogoutSuccessHandler</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">OidcClientInitiatedServerLogoutSuccessHandler</span> <span class="n">oidcLogoutSuccessHandler</span> <span class="o">=</span>
<span class="k">new</span> <span class="nf">OidcClientInitiatedServerLogoutSuccessHandler</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">clientRegistrationRepository</span><span class="o">)</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Mono</span><span class="o"><</span><span class="nc">Void</span><span class="o">></span> <span class="nf">onLogoutSuccess</span><span class="o">(</span><span class="nc">WebFilterExchange</span> <span class="n">exchange</span><span class="o">,</span> <span class="nc">Authentication</span> <span class="n">authentication</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// https://stackoverflow.com/q/15988323/1098564</span>
<span class="c1">// logout was called and proxied, let's default redirection to "origin"</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="n">origin</span> <span class="o">=</span> <span class="n">exchange</span><span class="o">.</span><span class="na">getExchange</span><span class="o">().</span><span class="na">getRequest</span><span class="o">().</span><span class="na">getHeaders</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="nc">HttpHeaders</span><span class="o">.</span><span class="na">ORIGIN</span><span class="o">);</span>
<span class="c1">// https://stackoverflow.com/q/22397072/1098564</span>
<span class="n">setPostLogoutRedirectUri</span><span class="o">(</span><span class="no">URI</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">origin</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">()</span> <span class="o">||</span> <span class="s">"null"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">origin</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">))</span> <span class="o">?</span>
<span class="s">"http://localhost:"</span> <span class="o">+</span> <span class="n">serverPort</span> <span class="o">:</span>
<span class="n">origin</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">)));</span>
<span class="k">return</span> <span class="kd">super</span><span class="o">.</span><span class="na">onLogoutSuccess</span><span class="o">(</span><span class="n">exchange</span><span class="o">,</span> <span class="n">authentication</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="k">return</span> <span class="n">oidcLogoutSuccessHandler</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>There’s a fair bit going on above.</p>
<p>We configure our <code class="language-plaintext highlighter-rouge">SecurityWebFilterChain</code> with the following:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">oauth2Login</code> tells us to redirect the browser to the Keycloak’s authorization endpoint for the user to authenticate there. Before it redirects, it will save the request (e.g. <code class="language-plaintext highlighter-rouge">/private</code>) and redirect the browser back there once the user is authenticated.</li>
<li>We configure <code class="language-plaintext highlighter-rouge">csrf</code> with <code class="language-plaintext highlighter-rouge">CookieServerCsrfTokenRepository.withHttpOnlyFalse()</code> so that the React app will be able to obtain the <code class="language-plaintext highlighter-rouge">XSRF-TOKEN</code> that Spring Security will return in responses so that it can send it with <code class="language-plaintext highlighter-rouge">POST</code> requests. Note that, at least <a href="https://github.com/spring-projects/spring-security/issues/5766#issuecomment-564636167">currently</a>, we also need to create a <code class="language-plaintext highlighter-rouge">WebFilter</code> that will subscribe to our <code class="language-plaintext highlighter-rouge">CsrfToken</code> so that it will be included in responses!</li>
<li>We configure the <code class="language-plaintext highlighter-rouge">authorizeExchange</code> to ensure all requests are made by an authenticated user except for requests to known public paths:
<ul>
<li><code class="language-plaintext highlighter-rouge">/manifest.json</code> created by Create React App</li>
<li><code class="language-plaintext highlighter-rouge">/*.png</code> created by Create React App</li>
<li><code class="language-plaintext highlighter-rouge">/static/**</code> where we copy the build directory following an <code class="language-plaintext highlighter-rouge">npm build</code> from Create React App</li>
<li><code class="language-plaintext highlighter-rouge">/api/user</code> where we return a username if the user is authenticated, and</li>
<li><code class="language-plaintext highlighter-rouge">/</code> to let the <code class="language-plaintext highlighter-rouge">index.html</code> with our login button display without triggering a Keycloak redirection</li>
</ul>
</li>
<li>Finally, we configure <code class="language-plaintext highlighter-rouge">logout</code>. We’ll take the following paragraph to explain that one.</li>
</ul>
<p>We configure <code class="language-plaintext highlighter-rouge">logout</code> with an <code class="language-plaintext highlighter-rouge">OidcClientInitiatedServerLogoutSuccessHandler</code> that knows about the Identity Provider’s <code class="language-plaintext highlighter-rouge">end_session_endpoint</code> and will log us out of both our gateway AND Keycloak–redirecting us back, unauthenticated, to our application’s <code class="language-plaintext highlighter-rouge">/</code>. Since we won’t always be on <code class="language-plaintext highlighter-rouge">localhost</code>, our Identity Provider usually needs to redirect us back to a different DNS name or port. When we’re running our UI with <code class="language-plaintext highlighter-rouge">npm start</code>, we want to be redirected back to <code class="language-plaintext highlighter-rouge">http://localhost:3000</code>. When we’re running in production, our application may be running on port 8080 but we’re usually behind a reverse proxy with a DNS like <code class="language-plaintext highlighter-rouge">https://gateway.simplestep.ca</code> that we want to be redirected back to. In both cases, there’s a proxy involved so that, by the time we get to our server-side, the request URI has changed. Fortunately, the <code class="language-plaintext highlighter-rouge">origin</code> http header is set with the original host where the <code class="language-plaintext highlighter-rouge">POST</code> to <code class="language-plaintext highlighter-rouge">/logout</code> was requested, letting us set that as our <code class="language-plaintext highlighter-rouge">post_logout_redirect_uri</code> when we’re logged out (e.g. <code class="language-plaintext highlighter-rouge">http://localhost:3000</code> or <code class="language-plaintext highlighter-rouge">https://gateway.simplestep.ca</code>)</p>
<h1 id="proxy-configuration">Proxy configuration</h1>
<h2 id="honour-proxys-x-forward--headers-in-webflux">Honour proxy’s <code class="language-plaintext highlighter-rouge">x-forward-*</code> headers in webflux</h2>
<p>Next, lets jump back to our <code class="language-plaintext highlighter-rouge">application.yml</code> for a second…</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">server</span><span class="pi">:</span>
<span class="na">forward-headers-strategy</span><span class="pi">:</span> <span class="s">framework</span> <span class="c1"># https://stackoverflow.com/a/59126519/1098564 (but ours is non-servlet)</span></code></pre></figure>
<p>This tells Spring to look for <code class="language-plaintext highlighter-rouge">x-forward-*</code> headers set by the proxy to let the requests be understood on the application server as if they were made to the proxy server itself. This must be done in on your nginx or whatever proxy server you use. For local development, we also have a proxy server–a node.js express app running on <code class="language-plaintext highlighter-rouge">http://localhost:3000</code>. We can add in some middleware to configure it beyond the Create React App proxy defaults.</p>
<h2 id="add-express-proxy-configuration-to-proxy-to-our-gateway-server">Add express proxy configuration to proxy to our gateway server</h2>
<p>So, we install <code class="language-plaintext highlighter-rouge">http-proxy-middleware</code></p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">npm <span class="nb">install</span> <span class="nt">--save</span> http-proxy-middleware</code></pre></figure>
<p>and then we add <code class="language-plaintext highlighter-rouge">setupProxy.js</code> with the following content:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// https://create-react-app.dev/docs/proxying-api-requests-in-development#configuring-the-proxy-manually</span>
<span class="c1">// https://github.com/chimurai/http-proxy-middleware</span>
<span class="kd">const</span> <span class="nx">proxy</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">http-proxy-middleware</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">app</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span>
<span class="p">[</span>
<span class="dl">'</span><span class="s1">/api</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">/logout</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">/private</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">/oauth2/authorization/login-client</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">/login/oauth2/code/login-client</span><span class="dl">'</span>
<span class="p">],</span>
<span class="nx">proxy</span><span class="p">({</span>
<span class="na">target</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http://localhost:8080</span><span class="dl">'</span><span class="p">,</span>
<span class="na">changeOrigin</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">xfwd</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">})</span>
<span class="p">);</span>
<span class="p">};</span></code></pre></figure>
<p>Here we add the <code class="language-plaintext highlighter-rouge">xfwd: true</code> to our proxy options so that <code class="language-plaintext highlighter-rouge">x-forward-*</code> headers are added.</p>
<p>Of course, we’ll be proxying to <code class="language-plaintext highlighter-rouge">http://localhost:8080</code> where our gateway server is running.</p>
<p>We also say that we want to proxy any requests made to the following paths to our gateway backend:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">/api</code> so we receive requests like <code class="language-plaintext highlighter-rouge">/api/notes</code></li>
<li><code class="language-plaintext highlighter-rouge">/logout</code> so that our form post will be received</li>
<li><code class="language-plaintext highlighter-rouge">/private</code> so that when we redirect to <code class="language-plaintext highlighter-rouge">/private</code> that Spring Security can redirect it again for authentication at the Identity Provider</li>
<li><code class="language-plaintext highlighter-rouge">/oauth2/authorization/login-client</code> because Spring Security’s redirection to the Identity Provider first gets redirected here to make call the authorize endpoint for this particular client. In this case, <code class="language-plaintext highlighter-rouge">login-client</code>.</li>
<li>and finally, <code class="language-plaintext highlighter-rouge">/login/oauth2/code/login-client</code> because that is where the Identity Provider sends back the <code class="language-plaintext highlighter-rouge">code</code> during the OAuth 2.0 authorization_code dance.</li>
</ul>
<h1 id="start-keycloak">Start keycloak</h1>
<p>Well, we’ve done a lot of configuration to talk to our Identity Provider so let’s start it, Keycloak, up!</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">docker-compose up</code></pre></figure>
<p>Verify it’s running at <code class="language-plaintext highlighter-rouge">http://localhost:9080</code></p>
<h1 id="set-up-our-server-side-routes">Set up our server-side routes</h1>
<p>From our React UI, we redirect to <code class="language-plaintext highlighter-rouge">/private</code> when the login button is clicked. Of course we don’t want to end up there–we’re doing it to trigger authentication. Let’s define our routes and some handlers for those routes</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Configuration</span>
<span class="kd">class</span> <span class="nc">WebConfig</span> <span class="kd">implements</span> <span class="nc">WebFluxConfigurer</span> <span class="o">{</span>
<span class="nd">@Bean</span>
<span class="nc">RouterFunction</span><span class="o"><</span><span class="nc">ServerResponse</span><span class="o">></span> <span class="nf">routerFunction</span><span class="o">(</span><span class="nc">GatewayHandler</span> <span class="n">gatewayHandler</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nf">route</span><span class="o">(</span><span class="no">GET</span><span class="o">(</span><span class="s">"/api/user"</span><span class="o">),</span> <span class="nl">gatewayHandler:</span><span class="o">:</span><span class="n">getCurrentUser</span><span class="o">)</span>
<span class="o">.</span><span class="na">andRoute</span><span class="o">(</span><span class="no">GET</span><span class="o">(</span><span class="s">"/private"</span><span class="o">),</span> <span class="nl">gatewayHandler:</span><span class="o">:</span><span class="n">getPrivate</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Component</span>
<span class="kd">class</span> <span class="nc">GatewayHandler</span> <span class="o">{</span>
<span class="kd">public</span> <span class="nc">Mono</span><span class="o"><</span><span class="nc">ServerResponse</span><span class="o">></span> <span class="nf">getCurrentUser</span><span class="o">(</span><span class="nc">ServerRequest</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="na">principal</span><span class="o">()</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">p</span> <span class="o">-></span> <span class="o">((</span><span class="nc">OAuth2AuthenticationToken</span><span class="o">)</span><span class="n">p</span><span class="o">).</span><span class="na">getPrincipal</span><span class="o">())</span>
<span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">n</span> <span class="o">-></span> <span class="n">ok</span><span class="o">().</span><span class="na">bodyValue</span><span class="o">(</span><span class="n">n</span><span class="o">.</span><span class="na">getAttribute</span><span class="o">(</span><span class="s">"preferred_username"</span><span class="o">)));</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Mono</span><span class="o"><</span><span class="nc">ServerResponse</span><span class="o">></span> <span class="nf">getPrivate</span><span class="o">(</span><span class="nc">ServerRequest</span> <span class="n">serverRequest</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">ServerResponse</span><span class="o">.</span><span class="na">temporaryRedirect</span><span class="o">(</span><span class="no">URI</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="s">"/"</span><span class="o">)).</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>We’ve got two routes and their respective handler methods:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">/api/user</code> where we get the current username from the ID Token’s <code class="language-plaintext highlighter-rouge">preferred_username</code> claim that is set in our <code class="language-plaintext highlighter-rouge">OAuth2AuthenticationToken</code>, and</li>
<li><code class="language-plaintext highlighter-rouge">/private</code> where we return a redirect back to our root, <code class="language-plaintext highlighter-rouge">/</code> because, like we said, we only did this to trigger authentication.</li>
</ol>
<h1 id="ajaxify-front-end-crud">Ajaxify front-end CRUD</h1>
<p>Now we’ve got a lot of pieces set up! Let’s hop back to the front-end and add some more calls to finish our CRUD functionality. Tania Rascia’s UI already defined these functions but manipulated the state in the browser. We tweak those functions to actually call backend API. Here’s a portion of <code class="language-plaintext highlighter-rouge">App.tsx</code> again.</p>
<figure class="highlight"><pre><code class="language-tsx" data-lang="tsx"><span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="c1">// Data</span>
<span class="kd">const</span> <span class="na">notesData</span> <span class="p">:</span> <span class="nx">Note</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[]</span>
<span class="c1">// Setting state</span>
<span class="kd">const</span> <span class="p">[</span> <span class="nx">notes</span><span class="p">,</span> <span class="nx">setNotes</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="nx">notesData</span><span class="p">)</span>
<span class="c1">// ...</span>
<span class="kd">const</span> <span class="nx">getNotes</span> <span class="o">=</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/api/notes</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">notes</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
<span class="nx">setNotes</span><span class="p">(</span><span class="nx">notes</span><span class="p">)</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
<span class="c1">// add better error handling here (e.g. 401?)</span>
<span class="nx">login</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// CRUD operations</span>
<span class="kd">const</span> <span class="nx">addNote</span> <span class="o">=</span> <span class="p">(</span><span class="na">note</span><span class="p">:</span> <span class="p">{</span><span class="na">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="na">confidential</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">})</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/api/notes</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">Accept</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">X-XSRF-TOKEN</span><span class="dl">'</span><span class="p">:</span> <span class="nx">cookies</span><span class="p">[</span><span class="dl">'</span><span class="s1">XSRF-TOKEN</span><span class="dl">'</span><span class="p">]</span>
<span class="p">},</span>
<span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">note</span><span class="p">),</span>
<span class="p">}).</span><span class="nx">then</span><span class="p">((</span><span class="nx">result</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">getNotes</span><span class="p">()</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">deleteNote</span> <span class="o">=</span> <span class="p">(</span><span class="na">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="nx">fetch</span><span class="p">(</span><span class="s2">`/api/notes/</span><span class="p">${</span><span class="nx">id</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="p">{</span>
<span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">DELETE</span><span class="dl">'</span><span class="p">,</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">Accept</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">X-XSRF-TOKEN</span><span class="dl">'</span><span class="p">:</span> <span class="nx">cookies</span><span class="p">[</span><span class="dl">'</span><span class="s1">XSRF-TOKEN</span><span class="dl">'</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">}).</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">getNotes</span><span class="p">()</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">updateNote</span> <span class="o">=</span> <span class="p">(</span><span class="na">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="na">updatedNote</span><span class="p">:</span> <span class="nx">Note</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="nx">fetch</span><span class="p">(</span><span class="s2">`/api/notes/</span><span class="p">${</span><span class="nx">id</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="p">{</span>
<span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">PUT</span><span class="dl">'</span><span class="p">,</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">Accept</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">X-XSRF-TOKEN</span><span class="dl">'</span><span class="p">:</span> <span class="nx">cookies</span><span class="p">[</span><span class="dl">'</span><span class="s1">XSRF-TOKEN</span><span class="dl">'</span><span class="p">]</span>
<span class="p">},</span>
<span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">updatedNote</span><span class="p">),</span>
<span class="p">}).</span><span class="nx">then</span><span class="p">((</span><span class="nx">result</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">getNotes</span><span class="p">()</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="c1">// ...</span>
<span class="p">}</span></code></pre></figure>
<p>We’re going to skip explaining the particular CRUD UI definitions but they can be looked up in the source on github. Rather, we show the CRUD function definitions that make the calls to our backend and the state that it modifies:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">const [ notes, setNotes ] = useState(notesData)</code> initilizes our notes with <code class="language-plaintext highlighter-rouge">notesData</code>–an empty array of Note: <code class="language-plaintext highlighter-rouge">[]</code></li>
<li><code class="language-plaintext highlighter-rouge">getNotes</code> is what we call to fetch all the notes and set the resulting json as the new state for <code class="language-plaintext highlighter-rouge">notes</code> using <code class="language-plaintext highlighter-rouge">setNotes</code>. For now, if there’s an error, e.g. 401 unauthorized because our gateway session expired, we call <code class="language-plaintext highlighter-rouge">login()</code> to kickstart the session again by doing the OAuth 2.0 dance.</li>
<li><code class="language-plaintext highlighter-rouge">addNote</code>, <code class="language-plaintext highlighter-rouge">updateNote</code>, and <code class="language-plaintext highlighter-rouge">deleteNote</code> look pretty similar and are probably nothing new if you’re familiar with REST. However, let’s unpack a couple of things:
<ol>
<li>First, you’ll notice we’re including the header <code class="language-plaintext highlighter-rouge">X-XSRF-TOKEN</code> in these “write” operations. That’s what Spring Security <a href="https://docs.spring.io/spring-security/site/docs/current/reference/html5/#webflux-csrf-configure-custom-repository">expects</a></li>
<li>Second, we always call <code class="language-plaintext highlighter-rouge">getNotes()</code> after the request is complete. It’s simple and not the most efficient but it makes sure that we get the latest array of notes in our state.</li>
</ol>
</li>
</ul>
<h1 id="relay-api-calls-to-resource-servers">Relay API calls to resource servers</h1>
<p>The main responsibility of a gateway is to route requests to downstream services. Here’s how we do that.</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">spring</span><span class="pi">:</span>
<span class="c1"># ...</span>
<span class="na">cloud</span><span class="pi">:</span>
<span class="na">gateway</span><span class="pi">:</span>
<span class="na">routes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">note</span>
<span class="na">uri</span><span class="pi">:</span> <span class="s">http://localhost:8081</span>
<span class="na">predicates</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">Path=/api/notes/**</span>
<span class="na">filters</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">TokenRelay=</span>
<span class="pi">-</span> <span class="s">RemoveRequestHeader=Cookie</span></code></pre></figure>
<p>We add predicates for the path <code class="language-plaintext highlighter-rouge">/api/notes/**</code> so that requests that match that path are sent to <code class="language-plaintext highlighter-rouge">http:localhost:8081</code> using filters to</p>
<ol>
<li>relay the access token along with the request to the resources server (i.e. <code class="language-plaintext highlighter-rouge">TokenRelay</code>), and</li>
<li>strip cookie headers as they are of no use to the resource server (i.e. <code class="language-plaintext highlighter-rouge">RemoveRequestHeader=Cookie</code>)</li>
</ol>
<h1 id="start-the-gateway-in-development-mode">Start the gateway in development mode</h1>
<p>Assuming that your note resource server is up and running from <a href="/blog/2019/12/17/merry-microservices-part1-resource-server">Part 1</a> on port 8081 with keycloak on port 9080, from the <code class="language-plaintext highlighter-rouge">gateway</code> directory, you can pretty much start up the front end <code class="language-plaintext highlighter-rouge">npm start</code> and the gateway server <code class="language-plaintext highlighter-rouge">/.mvnw spring-boot:run</code> and it all should work (also assuming you’ve filled in the missing pieces from the github repo).</p>
<h1 id="get-ready-for-production">Get ready for production</h1>
<p>In order to prepare this for production, we need add the frontend-maven-plugin to install node/npm and build our react app and place it into the executable Spring Boot <code class="language-plaintext highlighter-rouge">.jar</code></p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"> <span class="c"><!-- ... --></span>
<span class="nt"><properties></span>
<span class="c"><!-- ... --></span>
<span class="c"><!-- how versions were picked: https://nodejs.org/en/download/releases/ --></span>
<span class="nt"><nodeVersion></span>v10.17.0<span class="nt"></nodeVersion></span>
<span class="nt"><npmVersion></span>6.11.3<span class="nt"></npmVersion></span>
<span class="nt"><frontend-maven-plugin.version></span>1.8.0<span class="nt"></frontend-maven-plugin.version></span>
<span class="nt"></properties></span>
<span class="c"><!-- ... --></span>
<span class="nt"><build></span>
<span class="nt"><plugins></span>
<span class="c"><!-- ... --></span>
<span class="nt"><plugin></span>
<span class="nt"><groupId></span>com.github.eirslett<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>frontend-maven-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>${frontend-maven-plugin.version}<span class="nt"></version></span>
<span class="nt"><executions></span>
<span class="nt"><execution></span>
<span class="nt"><id></span>install node and npm<span class="nt"></id></span>
<span class="nt"><goals></span>
<span class="nt"><goal></span>install-node-and-npm<span class="nt"></goal></span>
<span class="nt"></goals></span>
<span class="nt"></execution></span>
<span class="nt"><execution></span>
<span class="nt"><id></span>npm install<span class="nt"></id></span>
<span class="nt"><goals></span>
<span class="nt"><goal></span>npm<span class="nt"></goal></span>
<span class="nt"></goals></span>
<span class="nt"><configuration></span>
<span class="nt"><arguments></span>install<span class="nt"></arguments></span>
<span class="nt"></configuration></span>
<span class="nt"></execution></span>
<span class="nt"><execution></span>
<span class="nt"><id></span>npm test<span class="nt"></id></span>
<span class="nt"><phase></span>test<span class="nt"></phase></span>
<span class="nt"><goals></span>
<span class="nt"><goal></span>npm<span class="nt"></goal></span>
<span class="nt"></goals></span>
<span class="nt"><configuration></span>
<span class="nt"><arguments></span>test<span class="nt"></arguments></span>
<span class="nt"></configuration></span>
<span class="nt"></execution></span>
<span class="nt"><execution></span>
<span class="nt"><id></span>Frontend production build<span class="nt"></id></span>
<span class="nt"><phase></span>prepare-package<span class="nt"></phase></span>
<span class="nt"><goals></span>
<span class="nt"><goal></span>npm<span class="nt"></goal></span>
<span class="nt"></goals></span>
<span class="nt"><configuration></span>
<span class="nt"><arguments></span>run build<span class="nt"></arguments></span>
<span class="nt"></configuration></span>
<span class="nt"></execution></span>
<span class="nt"></executions></span>
<span class="nt"><configuration></span>
<span class="nt"><nodeVersion></span>${nodeVersion}<span class="nt"></nodeVersion></span>
<span class="nt"><npmVersion></span>${npmVersion}<span class="nt"></npmVersion></span>
<span class="nt"><installDirectory></span>.mvn<span class="nt"></installDirectory></span>
<span class="nt"><workingDirectory></span>src/main/app<span class="nt"></workingDirectory></span>
<span class="nt"><environmentVariables></span>
<span class="nt"><CI></span>true<span class="nt"></CI></span>
<span class="nt"></environmentVariables></span>
<span class="nt"></configuration></span>
<span class="nt"></plugin></span>
<span class="nt"><plugin></span>
<span class="nt"><artifactId></span>maven-resources-plugin<span class="nt"></artifactId></span>
<span class="nt"><executions></span>
<span class="nt"><execution></span>
<span class="nt"><id></span>Copy frontend production build to resources<span class="nt"></id></span>
<span class="nt"><phase></span>prepare-package<span class="nt"></phase></span>
<span class="nt"><goals></span>
<span class="nt"><goal></span>copy-resources<span class="nt"></goal></span>
<span class="nt"></goals></span>
<span class="nt"><configuration></span>
<span class="nt"><outputDirectory></span>${basedir}/target/classes/static<span class="nt"></outputDirectory></span>
<span class="nt"><resources></span>
<span class="nt"><resource></span>
<span class="nt"><directory></span>src/main/app/build/<span class="nt"></directory></span>
<span class="nt"><filtering></span>false<span class="nt"></filtering></span>
<span class="nt"></resource></span>
<span class="nt"></resources></span>
<span class="nt"></configuration></span>
<span class="nt"></execution></span>
<span class="nt"></executions></span>
<span class="nt"></plugin></span>
<span class="nt"><plugin></span>
<span class="nt"><artifactId></span>maven-clean-plugin<span class="nt"></artifactId></span>
<span class="nt"><configuration></span>
<span class="nt"><filesets></span>
<span class="nt"><fileset></span>
<span class="nt"><directory></span>src/main/app/build<span class="nt"></directory></span>
<span class="nt"></fileset></span>
<span class="nt"></filesets></span>
<span class="nt"></configuration></span>
<span class="nt"></plugin></span>
<span class="nt"></plugins></span>
<span class="nt"></build></span>
<span class="nt"></project></span></code></pre></figure>
<h1 id="configure-maven-plugins">Configure maven plugins</h1>
<h2 id="frontend-maven-plugin">frontend-maven-plugin</h2>
<p>First, we set our node, npm and frontend-maven-plugin versions in the properties. Next we define our frontend-maven-plugin <code class="language-plaintext highlighter-rouge">executions</code> to</p>
<ol>
<li>install node and npm</li>
<li>run <code class="language-plaintext highlighter-rouge">npm install</code> on the project</li>
<li>bind <code class="language-plaintext highlighter-rouge">npm test</code> to the <code class="language-plaintext highlighter-rouge">test</code> maven phase</li>
<li>bind <code class="language-plaintext highlighter-rouge">npm run build</code> to the <code class="language-plaintext highlighter-rouge">prepare-package</code> maven phase
Finally, we configure frontend-maven-plugin to set the working directory to <code class="language-plaintext highlighter-rouge">src/main/app</code> (i.e. where our TypeScript lives) and set an environment variable <code class="language-plaintext highlighter-rouge">CI</code> to <code class="language-plaintext highlighter-rouge">true</code> so that when <code class="language-plaintext highlighter-rouge">npm test</code> is run, it completes and doesn’t sit in “watch” mode :)</li>
</ol>
<h2 id="maven-resources-plugin">maven-resources-plugin</h2>
<p>Here we bind the <code class="language-plaintext highlighter-rouge">copy-resources</code> goal to the <code class="language-plaintext highlighter-rouge">prepare-package</code> maven phase to copy the results of the React build in <code class="language-plaintext highlighter-rouge">src/main/app/build/</code> to <code class="language-plaintext highlighter-rouge">${basedir}/target/classes/static</code> so it will be added to our <code class="language-plaintext highlighter-rouge">.jar</code> in the later <code class="language-plaintext highlighter-rouge">package</code> maven phase. Note that we’re also setting <code class="language-plaintext highlighter-rouge">filtered</code> to <code class="language-plaintext highlighter-rouge">false</code> because we don’t need to filter anything and I’ve been burnt too much by maven trying to “filter” files by processing things it shouldn’t!</p>
<h2 id="maven-clean-plugin">maven-clean-plugin</h2>
<p>The final plugin simply makes sure that the <code class="language-plaintext highlighter-rouge">src/main/app/build</code> directory where react built the production-ready JavaScript, etc. is deleted along with the usual “clean” location: <code class="language-plaintext highlighter-rouge">target</code>.</p>
<h1 id="add-indexhtml-to--mapping">Add index.html to “/” mapping</h1>
<p>Last, but not least, we need to make one last gateway server change. Currently, if you start up the production-built application (i.e. <code class="language-plaintext highlighter-rouge">./mvnw package</code>), and start it up with</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">java <span class="nt">-jar</span> target/gateway-0.0.1-SNAPSHOT.jar</code></pre></figure>
<p><code class="language-plaintext highlighter-rouge">http://localhost:8080</code> will be a blank page! That’s because Webflux (details <a href="https://github.com/spring-projects/spring-boot/issues/9785">here</a>) currently doesn’t serve up <code class="language-plaintext highlighter-rouge">index.html</code> to <code class="language-plaintext highlighter-rouge">/</code>. So we take a suggested solution from stackoverflow in the link to mutate the request path accordingly.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Component</span>
<span class="kd">class</span> <span class="nc">CustomWebFilter</span> <span class="kd">implements</span> <span class="nc">WebFilter</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Mono</span><span class="o"><</span><span class="nc">Void</span><span class="o">></span> <span class="nf">filter</span><span class="o">(</span><span class="nc">ServerWebExchange</span> <span class="n">exchange</span><span class="o">,</span> <span class="nc">WebFilterChain</span> <span class="n">chain</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">exchange</span><span class="o">.</span><span class="na">getRequest</span><span class="o">().</span><span class="na">getURI</span><span class="o">().</span><span class="na">getPath</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="s">"/"</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">chain</span><span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">exchange</span><span class="o">.</span><span class="na">mutate</span><span class="o">().</span><span class="na">request</span><span class="o">(</span><span class="n">exchange</span><span class="o">.</span><span class="na">getRequest</span><span class="o">().</span><span class="na">mutate</span><span class="o">().</span><span class="na">path</span><span class="o">(</span><span class="s">"/index.html"</span><span class="o">).</span><span class="na">build</span><span class="o">()).</span><span class="na">build</span><span class="o">());</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">chain</span><span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">exchange</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<h1 id="done">Done!</h1>
<p>Now if you build the app with <code class="language-plaintext highlighter-rouge">./mvnw clean package</code> and run <code class="language-plaintext highlighter-rouge">java -jar target/gateway-0.0.1-SNAPSHOT.jar</code>, you’ll get the gateway running as a single artifact on <code class="language-plaintext highlighter-rouge">http://localhost:8080</code>, relaying requests to a note resource server at <code class="language-plaintext highlighter-rouge">http://localhost:8081</code> with authenticating with and using access tokens from Keycloak running at <code class="language-plaintext highlighter-rouge">http://localhost:9080</code>!</p>
<p><img src="/assets/images/merry-microservices/part2/crud-ui.png" alt="Our running gateway!" /></p>
<p>I think that’s REALLY cool :)</p>
<p class="notice--primary"><strong>Tip</strong>: If you go back to development mode (i.e. <code class="language-plaintext highlighter-rouge">npm start</code> and <code class="language-plaintext highlighter-rouge">./mvnw spring-boot:run</code>) you’ll want to delete the <code class="language-plaintext highlighter-rouge">target</code> directory manually or by a <code class="language-plaintext highlighter-rouge">./mvnw clean spring-boot:run</code> instead so that files from the production build don’t mess with your development files.</p>
<h1 id="conclusion">Conclusion</h1>
<p>It’s been my pleasure to share how I added a Create React App with TypeScript and Hooks to a Spring Cloud Gateway OAuth 2.0 Client to relay secure requests to downstream resource servers. I’d love to hear what you think!</p>
<p>In the next post (<a href="/blog/2020/01/12/merry-microservices-part3-policy-service">Part 3</a>), we’ll look into the place of a “policy service” for controlling authorization in applications based on the user’s identity and the permissions set up on the “policy service”.</p>
<p class="notice">Please <strong>follow me</strong> on <a href="https://twitter.com/doxsees">twitter</a> or <a href="/atom.xml">subscribe</a> to be updated as each part of this series comes out.</p>
<p>If you’d like help with any of these things, find out how what I do and how you can <strong>hire me</strong> at <a href="https://simplestep.ca">Simple Step Solutions</a></p>stephenThis is Part 2 of the series “Merry Microservices”Implementing Microservices Security Protocols and Patterns with Spring Security 5.22019-11-18T00:00:00+00:002019-11-18T00:00:00+00:00https://sdoxsee.github.io/blog/2019/11/18/implementing-microservices-security-protocols-and-patterns<p>Last month, I had the opportunity to speak with Joe Grandja at SpringOne Platform 2019 in Austin, Texas. In the <a href="https://springoneplatform.io/2019/sessions/implementing-microservices-security-patterns-protocols-with-spring-security">talk</a>, we break apart the pieces of an intentionally non-trivial demo to show how you can secure real-life microservices architectures with OAuth 2.0 and OpenID Connect.</p>
<div class="responsive-embed responsive-embed-16by9">
<iframe src="https://www.youtube.com/embed/JnYIsvJY7gM" frameborder="0" allowfullscreen=""></iframe>
</div>Stephen DoxseeLast month, I had the opportunity to speak with Joe Grandja at SpringOne Platform 2019 in Austin, Texas. In the talk, we break apart the pieces of an intentionally non-trivial demo to show how you can secure real-life microservices architectures with OAuth 2.0 and OpenID Connect.HttpInvoker over OAuth 2.0 with Spring Boot 2.22019-03-27T00:00:00+00:002019-03-27T00:00:00+00:00https://sdoxsee.github.io/blog/2019/03/27/http-invoker-over-oauth2<p>Ok, so <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#remoting-httpinvoker"><code class="language-plaintext highlighter-rouge">HttpInvoker</code></a> may not be the what the hipsters are using (it’s been around since 2003 or so) but there are still plenty of Java desktop applications out there communicating over RMI or EJB that could use a security boost by using OAuth 2.0.</p>
<p>If you’ve got a desktop app communicating to a server over RMI, how are you authorizing the calls? If the app is on the end-user’s machine, how can you keep any secret auth information?</p>
<ul>
<li>username/password? - exposed</li>
<li>static token? - exposed</li>
</ul>
<p>Although obfuscation mechanisms can be used, any text can be extracted from the jar on the persons machine.</p>
<p>So how can you keep a secret on a public application? You can’t. But OAuth 2.0 authorization grant type can be used in combination with PKCE to obtain short-lived access tokens for a specific user to securely communicate with server endpoints.</p>
<p>How can we do this?</p>
<p>Let’s build on baeldungs’ <a href="https://github.com/eugenp/tutorials/tree/master/spring-remoting/remoting-http">http-invoker tutorial</a>. It doesn’t use a typical desktop client but you can imagine this Spring Boot application running on the end users machine, and communicating securely with a server somewhere else, without needing to have any secrets! Essentially, the server allows remote invocations of a CabBookingService via the <code class="language-plaintext highlighter-rouge">http://localhost:8080/booking</code> endpoint. The client invokes it by hitting <code class="language-plaintext highlighter-rouge">http://localhost:8081</code> on the client after an authorization code flow to obtain access tokens for the server. Here we go!</p>
<p>First create an OAuth 2.0-aware WebClient:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">WebClientConfig</span> <span class="o">{</span>
<span class="nd">@Bean</span>
<span class="nc">WebClient</span> <span class="nf">webClient</span><span class="o">(</span><span class="nc">ClientRegistrationRepository</span> <span class="n">clientRegistrationRepository</span><span class="o">,</span> <span class="nc">OAuth2AuthorizedClientRepository</span> <span class="n">authorizedClientRepository</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">ServletOAuth2AuthorizedClientExchangeFilterFunction</span> <span class="n">oauth2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ServletOAuth2AuthorizedClientExchangeFilterFunction</span><span class="o">(</span><span class="n">clientRegistrationRepository</span><span class="o">,</span> <span class="n">authorizedClientRepository</span><span class="o">);</span>
<span class="n">oauth2</span><span class="o">.</span><span class="na">setDefaultOAuth2AuthorizedClient</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="k">return</span> <span class="nc">WebClient</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
<span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">oauth2</span><span class="o">.</span><span class="na">oauth2Configuration</span><span class="o">())</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>Create a custom <code class="language-plaintext highlighter-rouge">WebClientHttpInvokerRequestExecutor</code> that will send your requests using the OAuth 2.0-aware <code class="language-plaintext highlighter-rouge">WebClient</code>:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">WebClientHttpInvokerRequestExecutor</span> <span class="kd">extends</span> <span class="nc">AbstractHttpInvokerRequestExecutor</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">WebClient</span> <span class="n">webClient</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">WebClientHttpInvokerRequestExecutor</span><span class="o">(</span><span class="nc">WebClient</span> <span class="n">webClient</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">webClient</span> <span class="o">=</span> <span class="n">webClient</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="nc">RemoteInvocationResult</span> <span class="nf">doExecuteRequest</span><span class="o">(</span><span class="nc">HttpInvokerClientConfiguration</span> <span class="n">config</span><span class="o">,</span> <span class="nc">ByteArrayOutputStream</span> <span class="n">baos</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span><span class="o">,</span> <span class="nc">ClassNotFoundException</span> <span class="o">{</span>
<span class="nc">InputStreamResource</span> <span class="n">inputStreamResource</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">webClient</span>
<span class="o">.</span><span class="na">post</span><span class="o">()</span>
<span class="o">.</span><span class="na">uri</span><span class="o">(</span><span class="n">config</span><span class="o">.</span><span class="na">getServiceUrl</span><span class="o">())</span>
<span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="no">HTTP_HEADER_CONTENT_TYPE</span><span class="o">,</span> <span class="no">CONTENT_TYPE_SERIALIZED_OBJECT</span><span class="o">)</span>
<span class="o">.</span><span class="na">syncBody</span><span class="o">(</span><span class="k">new</span> <span class="nc">ByteArrayResource</span><span class="o">(</span><span class="n">baos</span><span class="o">.</span><span class="na">toByteArray</span><span class="o">()))</span>
<span class="o">.</span><span class="na">exchange</span><span class="o">()</span>
<span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="n">response</span> <span class="o">-></span> <span class="n">response</span><span class="o">.</span><span class="na">bodyToMono</span><span class="o">(</span><span class="nc">InputStreamResource</span><span class="o">.</span><span class="na">class</span><span class="o">))</span>
<span class="o">.</span><span class="na">block</span><span class="o">();</span>
<span class="k">return</span> <span class="nf">readRemoteInvocationResult</span><span class="o">(</span><span class="n">inputStreamResource</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">(),</span> <span class="n">config</span><span class="o">.</span><span class="na">getCodebaseUrl</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>Ensure your security configuration uses Spring Security’s OAuth 2.0 Client and uses OAuth 2.0 Login to protect every endpoint. When you hit a protected endpoint via a web browser, Spring Security will request authentication and redirect you to its login page.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@EnableWebSecurity</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SecurityConfig</span> <span class="kd">extends</span> <span class="nc">WebSecurityConfigurerAdapter</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">configure</span><span class="o">(</span><span class="nc">HttpSecurity</span> <span class="n">http</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">http</span>
<span class="o">.</span><span class="na">authorizeRequests</span><span class="o">()</span>
<span class="o">.</span><span class="na">anyRequest</span><span class="o">().</span><span class="na">authenticated</span><span class="o">()</span>
<span class="o">.</span><span class="na">and</span><span class="o">()</span>
<span class="o">.</span><span class="na">formLogin</span><span class="o">()</span>
<span class="o">.</span><span class="na">and</span><span class="o">()</span>
<span class="o">.</span><span class="na">oauth2Login</span><span class="o">()</span>
<span class="o">.</span><span class="na">and</span><span class="o">()</span>
<span class="o">.</span><span class="na">oauth2Client</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>To use our WebClientHttpInvokerRequestExecutor, hook it up to HttpInvokerProxyFactoryBean something like so</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">HttpInvokerProxyFactoryBean</span> <span class="nf">invoker</span><span class="o">(</span><span class="nc">HttpInvokerRequestExecutor</span> <span class="n">httpInvokerRequestExecutor</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">HttpInvokerProxyFactoryBean</span> <span class="n">invoker</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HttpInvokerProxyFactoryBean</span><span class="o">();</span>
<span class="n">invoker</span><span class="o">.</span><span class="na">setServiceUrl</span><span class="o">(</span><span class="s">"http://localhost:8080/booking"</span><span class="o">);</span>
<span class="n">invoker</span><span class="o">.</span><span class="na">setServiceInterface</span><span class="o">(</span><span class="nc">CabBookingService</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">invoker</span><span class="o">.</span><span class="na">setHttpInvokerRequestExecutor</span><span class="o">(</span><span class="n">httpInvokerRequestExecutor</span><span class="o">);</span>
<span class="k">return</span> <span class="n">invoker</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">HttpInvokerRequestExecutor</span> <span class="nf">httpInvokerRequestExecutor</span><span class="o">(</span><span class="nc">WebClient</span> <span class="n">webClient</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">WebClientHttpInvokerRequestExecutor</span><span class="o">(</span><span class="n">webClient</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<p>The client registration and provider are below. Notice there is no client-secret and that the client-authentication-method is <code class="language-plaintext highlighter-rouge">none</code>. This tells Spring Security to use PKCE to secure the authorization code flow.</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">spring</span><span class="pi">:</span>
<span class="na">security</span><span class="pi">:</span>
<span class="na">oauth2</span><span class="pi">:</span>
<span class="na">client</span><span class="pi">:</span>
<span class="na">registration</span><span class="pi">:</span>
<span class="na">client-id</span><span class="pi">:</span>
<span class="na">client-id</span><span class="pi">:</span> <span class="s">0oaj42d3hpR3EVrtK0h7</span>
<span class="na">provider</span><span class="pi">:</span> <span class="s">okta</span>
<span class="na">scopes</span><span class="pi">:</span> <span class="s">email,openid,profile</span>
<span class="na">client-authentication-method</span><span class="pi">:</span> <span class="s">none</span>
<span class="na">provider</span><span class="pi">:</span>
<span class="na">okta</span><span class="pi">:</span>
<span class="na">issuer-uri</span><span class="pi">:</span> <span class="s">https://dev-334545.oktapreview.com/oauth2/default</span></code></pre></figure>
<p>The server config is super simple.</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="s">spring.security.oauth2.resourceserver.jwt.issuer-uri</span><span class="pi">:</span> <span class="s">https://dev-334545.oktapreview.com/oauth2/default</span></code></pre></figure>
<p>I’ve shown most but not all of the code but let’s try it all out!</p>
<p>Assuming you’ve cloned the project from <a href="https://github.com/sdoxsee/http-invoker-over-oauth2">GitHub</a>, start up the server and then the client. Trigger a login flow on your client by going to http://localhost:8081 in your browser and you’ll get a screen like this:</p>
<p><img src="/assets/images/2019-03-27/SpringLogin.png" alt="Client Login Page" /></p>
<p>Click on the Okta link and you should see the Okta IdP sign in page below (unless you’ve already signed in to the Okta IdP)</p>
<p><img src="/assets/images/2019-03-27/OktaLogin.png" alt="Okta IdP Login Page" /></p>
<p>Sign in with <code class="language-plaintext highlighter-rouge">user@example.com</code>/<code class="language-plaintext highlighter-rouge">Password1</code> (if someone hasn’t changed it!) or sign up for your own account with Okta on that IdP instance (using the sign up link on the page). Once you’ve signed in to Okta’s IdP, your client should have an access token with which it can talk to the server! In fact, you should already see something like this in your browser:</p>
<p><code class="language-plaintext highlighter-rouge">Ride confirmed: code '0d0b9f4a-0244-4c26-a8b2-1ce908e6756f'.</code></p>
<p>This means you hit the server using HttpInvoker and received a response! Woohoo :)</p>
<p>I hope you enjoyed doing fun learning how to do modern OAuth 2.0 PKCE stuff even with older tech like Http Invoker! Please let me know what you thought of this tutorial in the comments below or on Twitter. Feel free to clone, star, or browse the code on <a href="https://github.com/sdoxsee/http-invoker-over-oauth2">GitHub</a>.</p>Stephen DoxseeOk, so HttpInvoker may not be the what the hipsters are using (it’s been around since 2003 or so) but there are still plenty of Java desktop applications out there communicating over RMI or EJB that could use a security boost by using OAuth 2.0.