<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shyamala</title>
    <description>The latest articles on DEV Community by Shyamala (@shyamala_u).</description>
    <link>https://dev.to/shyamala_u</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F10403%2F5d0c7410-ad7c-4dbf-bf7d-48b8dc815b5a.jpg</url>
      <title>DEV Community: Shyamala</title>
      <link>https://dev.to/shyamala_u</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shyamala_u"/>
    <language>en</language>
    <item>
      <title>My commit message said "You've hit your session limit"</title>
      <dc:creator>Shyamala</dc:creator>
      <pubDate>Mon, 29 Jun 2026 15:15:49 +0000</pubDate>
      <link>https://dev.to/shyamala_u/my-commit-message-said-youve-hit-your-session-limit-2abn</link>
      <guid>https://dev.to/shyamala_u/my-commit-message-said-youve-hit-your-session-limit-2abn</guid>
      <description>&lt;h2&gt;
  
  
  🧐 Context 🧐
&lt;/h2&gt;

&lt;p&gt;I had this one-liner that I was using.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git diff &lt;span class="nt"&gt;--staged&lt;/span&gt; | claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Provide a simple, one-line git commit message based on this diff following best practices. Output absolutely nothing else."&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pipe the staged diff to Claude, get a commit message back. Worked well until I hit my Claude usage limit mid commit. The shell captured the error instead of a commit message.&lt;/p&gt;

&lt;p&gt;So I had a commit in my repo that said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You've hit your session limit&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's when it hit me! Voila, ✨My use case for a Local Model.✨&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚠️ Disclaimer ⚠️
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I am learning GenAI, this is my journey&lt;/li&gt;
&lt;li&gt;This is not a tutorial&lt;/li&gt;
&lt;li&gt;What is obvious to you might not be obvious to me&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Ollama running
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; lets you run open source models locally. After installing it, you have a server running at &lt;code&gt;http://localhost:11434&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull qwen2.5-coder:1.5b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I picked &lt;code&gt;qwen2.5-coder:1.5b&lt;/code&gt; because it's small and code-aware.&lt;/p&gt;

&lt;p&gt;Why 1.5b specifically? My laptop has 8GB RAM. That's not a lot when you're running a model locally.&lt;/p&gt;

&lt;p&gt;Here's the rough math (these are estimates from my machine, yours may vary):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total Mac RAM: 8.0 GB&lt;/li&gt;
&lt;li&gt;macOS + apps already running: ~4.0 to 5.0 GB&lt;/li&gt;
&lt;li&gt;Model loaded in memory: ~1.2 GB (based on the model file size of ~1 GB)&lt;/li&gt;
&lt;li&gt;Context window: ~0.03 GB&lt;/li&gt;
&lt;li&gt;Remaining: ~1.77 to 2.77 GB free&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Interestingly, despite being a 1.5 billion parameter model, qwen2.5-coder:1.5b only takes up about 1 GB of disk space. That's because it's a quantized model. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/docs/optimum/en/concept_guides/quantization" rel="noopener noreferrer"&gt;Quantization&lt;/a&gt; means the &lt;a href="https://www.ultralytics.com/glossary/model-weights#model-weights-vs-biases" rel="noopener noreferrer"&gt;model's weights&lt;/a&gt; are stored at lower precision, using 4-bit or 8-bit integers instead of the usual 16-bit or 32-bit floating point numbers. This significantly reduces the model size and memory footprint, although it may slightly impact accuracy.&lt;/p&gt;

&lt;p&gt;I tried larger models. My laptop became unusable. Fans spinning, apps freezing, the whole thing. So 1.5b it is.&lt;/p&gt;

&lt;p&gt;There's another quantized model I found that could work — &lt;code&gt;gemma3:1b-it-qat&lt;/code&gt;. I plan to test it sometime and see how it compares in terms of performance and resource usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  First attempt
&lt;/h2&gt;

&lt;p&gt;I swapped Claude with Ollama in my one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git diff &lt;span class="nt"&gt;--staged&lt;/span&gt; | ollama run qwen2.5-coder:1.5b &lt;span class="s2"&gt;"Provide a simple, one-line git commit message based on this diff following best practices. Output absolutely nothing else."&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran it against a change where I had removed the &lt;code&gt;tools&lt;/code&gt; section from some agent config front matter from 6 files. This Worked&lt;/p&gt;

&lt;p&gt;The commit message said it was a change to a README file.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤔 What does this mean? 🤔
&lt;/h3&gt;

&lt;p&gt;Despite qwen2.5-coder:1.5b's large native context window of 32,768 tokens, Ollama actually restricts the default context size when running without a Modelfile. &lt;/p&gt;

&lt;p&gt;I checked Ollama's logs and found this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;level=INFO source=routes.go:2073 msg="vram-based default context" total_vram="5.3 GiB" default_num_ctx=4096
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It shows that based on my machine's VRAM of 5.3 GiB, Ollama set a default &lt;code&gt;num_ctx&lt;/code&gt; of 4096 tokens. That's why the model only saw the beginning of the diff and guessed about the README file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second attempt
&lt;/h2&gt;

&lt;p&gt;I thought maybe I need a better prompt. So I ran it again with more instructions.&lt;/p&gt;

&lt;p&gt;This time it said the change was in &lt;code&gt;code-reviewer.md&lt;/code&gt;. That was one of the 6 files, and it completely ignored the other 5.&lt;/p&gt;

&lt;p&gt;The important thing here is that the model did not complain. It did not say "I couldn't read the rest". It just gave me a confident answer based on partial input.&lt;/p&gt;

&lt;p&gt;At this point I understood tuning the prompt alone is insufficient and I need to tune the model too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Modelfile
&lt;/h2&gt;

&lt;p&gt;This is something I just learned. A Modelfile is a config layer on top of a base model. You can change parameters and create a named model from it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; qwen2.5-coder:1.5b&lt;/span&gt;

PARAMETER num_ctx 8192 
PARAMETER temperature 0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things I changed:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;num_ctx 8192&lt;/code&gt; — While qwen2.5-coder:1.5b can handle up to 32k tokens natively, Ollama defaults to a smaller context window when run without a Modelfile (in my case, 4096 based on VRAM). I bumped it to 8k, and be memory-efficient on my 8GB machine.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;temperature 0.2&lt;/code&gt; — lower temperature for more predictable output. For commit messages I don't want creative, I want consistent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama create qwen-commit &lt;span class="nt"&gt;-f&lt;/span&gt; ./Modelfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I have a model called &lt;code&gt;qwen-commit&lt;/code&gt; that I can use for this specific task.&lt;/p&gt;

&lt;p&gt;By the way, a Modelfile is not the only way to set these. You can use the REST API directly, and pass an &lt;code&gt;options&lt;/code&gt; object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:11434/api/generate &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "model": "qwen2.5-coder:1.5b",
  "prompt": "${YOUR_PROMPT}",
  "options": {
    "temperature": 0.2,
    "num_ctx": 8192
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For my use case the Modelfile made more sense because I just want to call &lt;code&gt;ollama run qwen-commit&lt;/code&gt; and have everything pre-configured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Third attempt
&lt;/h2&gt;

&lt;p&gt;With the bigger context window, the model could now see all 6 files. But it still described the change as "⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ ⠋&lt;br&gt;
&lt;br&gt;
 `&lt;code&gt;diff feat(.opencode/agent): update tool list for code-reviewer, frontend-enginee frontend-engineer, go-backend-engineer, project-lead, req requirements-analyst, solution-architect&lt;/code&gt;". Better, Mouthful but wrong.&lt;/p&gt;
&lt;h3&gt;
  
  
  🤔 What does this mean? 🤔
&lt;/h3&gt;

&lt;p&gt;The model was reading the full diff now but commit message was technically correct, but nothing like what we would write in a commit message. Look at how it had &lt;code&gt;frontend-enginee frontend-engineer&lt;/code&gt; or &lt;code&gt;req requirements-analyst&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So I changed the prompt. Instead of making the model figure it out, I just told it.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
affected_files=$(git diff --staged --name-only | paste -sd, -)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Then added to the prompt: &lt;code&gt;"Note that the changes are located in these files: [$affected_files]"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After this the commit messages got much better. The model didn't have to guess anymore.&lt;/p&gt;
&lt;h2&gt;
  
  
  One more thing
&lt;/h2&gt;

&lt;p&gt;The commit messages were now accurate but the model kept wrapping them in weird formatting despite the prompt saying not to. Sometimes backticks. Sometimes it prefixed with "diff". Sometimes random quotes around the message.&lt;/p&gt;

&lt;p&gt;So I added a cleanup step to strip all of that out:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
msg=$(echo "$msg" | tr -d '\r' | sed -E \
  -e 's/

```(diff)?//g' \
  -e 's/^diff[[:space:]]+//I' \
  -e 's/^[[:space:]]+//;s/[[:space:]]+$//' \
  -e 's/^["'\'']//' -e 's/["'\'']$//')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Not elegant but it catches most of the junk the model adds. Till the time I tune the prompt and model this stays!&lt;/p&gt;

&lt;p&gt;I also switched from &lt;code&gt;git diff --staged&lt;/code&gt; to &lt;code&gt;git diff --staged --unified=0&lt;/code&gt;. By default, git shows 3 lines of context around each change. For a commit message, the model doesn't need that surrounding context. It just needs to know what changed. &lt;code&gt;--unified=0&lt;/code&gt; strips all that out, which means fewer tokens sent to the model. On a small context window, every token counts.&lt;/p&gt;

&lt;p&gt;Tada 🎉&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* b6f0abc (HEAD -&amp;gt; main, origin/main, origin/HEAD) fix: update tool list for all agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much bigger code related commit, you can see gradual improvements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* b13f344 (HEAD -&amp;gt; main) fix(inspection-workflow): add requirement for editing confirmed vess vessel profile
* 958053c sh fix(app_test.go, sqlite.go, sqlite_test.go, tasks.md): add save and cancel  behaviour tests for vessel profile editing
* 0f33259 sh fix: update vessel profile form and edit flow in App.svelte, add tests for  editing workflow, and improve styles in styles.css, update model in go/mode go/models.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The final Modelfile
&lt;/h2&gt;

&lt;p&gt;After all the iterations, my Modelfile looks quite different from where I started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; qwen2.5-coder:1.5b&lt;/span&gt;

PARAMETER num_ctx 8192
PARAMETER temperature 0.2
PARAMETER top_p 0.7
PARAMETER num_predict 256
PARAMETER repeat_penalty 1.2
PARAMETER stop "Changes to be committed:"
PARAMETER stop "Note:"
SYSTEM """
You are an expert developer's assistant. Your sole task is to generate a clean, concise one-line Git commit message based on the provided code diff.
Rules:
- Respond ONLY with the commit message text.
- Do NOT include markdown code blocks, backticks, explanations, intro text, or outro text.
- Use the Conventional Commits format (e.g., feat(scope): message, fix: message).
- Keep the one line under 100 characters.
- Use the imperative mood ("Add feature", not "Added feature" or "Adds feature").
"""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What each parameter does and why I added it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;temperature 0.2&lt;/code&gt;: controls randomness. Lower means more predictable. I don't want creative commit messages.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;top_p 0.7&lt;/code&gt;: works with temperature. It limits the model to only consider the top 70% most likely next words. Another way to keep the output focused and not wander off.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;num_predict 256&lt;/code&gt;: maximum number of tokens the model can output. A commit message is one line. I don't need the model writing an essay. This caps it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;repeat_penalty 1.2&lt;/code&gt;: penalizes the model for repeating itself. Without this I was getting things like &lt;code&gt;frontend-enginee frontend-engineer&lt;/code&gt; or &lt;code&gt;req requirements-analyst&lt;/code&gt;. The model would stutter and repeat parts of words.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;stop "Changes to be committed:"&lt;/code&gt; and &lt;code&gt;stop "Note:"&lt;/code&gt; — stop sequences. Sometimes the model would keep going after the commit message and start generating text that looked like git output. These tell the model to stop immediately if it starts outputting these strings.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;SYSTEM&lt;/code&gt; block is the prompt baked into the model. Every time I run &lt;code&gt;ollama run qwen-commit&lt;/code&gt;, this prompt is already there. I don't have to pass it every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The final function
&lt;/h2&gt;

&lt;p&gt;After all the iterations, here is what I ended up with. A custom shell function &lt;code&gt;gac&lt;/code&gt; and an alias &lt;code&gt;gacc&lt;/code&gt;. It defaults to the local model, but I can also use Claude when I want to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gac&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;# 1. Check for staged changes&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;git diff &lt;span class="nt"&gt;--cached&lt;/span&gt; &lt;span class="nt"&gt;--quiet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"❌ Error: No staged changes found. Run 'git add' first."&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi

  &lt;/span&gt;&lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;qwen&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;exit_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

  &lt;span class="c"&gt;# Gather file names for context&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;affected_files
  &lt;span class="nv"&gt;affected_files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git diff &lt;span class="nt"&gt;--staged&lt;/span&gt; &lt;span class="nt"&gt;--name-only&lt;/span&gt; | &lt;span class="nb"&gt;paste&lt;/span&gt; &lt;span class="nt"&gt;-sd&lt;/span&gt;, -&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;# ---------------------------------------------------------&lt;/span&gt;
  &lt;span class="c"&gt;# IMPROVED PROMPT: Strict rules for Conventional Commits&lt;/span&gt;
  &lt;span class="c"&gt;# ---------------------------------------------------------&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"You are a strict code assistant. Write a single-line Conventional Commit message for the provided diff.
Strict Rules:
1. Format must exactly match: type(scope): description
2. Allowed types ONLY: feat, fix, docs, style, refactor, perf, test, chore.
3. The 'scope' must be a single, broad feature/module name (e.g., vessel-profile, api). NEVER use file names.
4. The 'description' must summarize the high-level intent in the imperative mood (e.g., 'add form validation').
5. ABSOLUTELY DO NOT list specific file names, paths, or extensions in the commit message.
6. Output EXACTLY one line. No markdown blocks, no quotes, no explanations, and no stray prefixes like 'sh'.
Context: The files modified are [&lt;/span&gt;&lt;span class="nv"&gt;$affected_files&lt;/span&gt;&lt;span class="s2"&gt;]."&lt;/span&gt;

  &lt;span class="c"&gt;# 2. Execution Routing&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$mode&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"claude"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git diff &lt;span class="nt"&gt;--staged&lt;/span&gt; &lt;span class="nt"&gt;--unified&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 | claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$system_prompt&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--output-format&lt;/span&gt; text 2&amp;gt;&amp;amp;1&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;exit_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
  &lt;span class="k"&gt;else
    if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 2 http://localhost:11434 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"❌ Error: Local Ollama server is not running on port 11434."&lt;/span&gt;
      &lt;span class="k"&gt;return &lt;/span&gt;1
    &lt;span class="k"&gt;fi
    &lt;/span&gt;&lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git diff &lt;span class="nt"&gt;--staged&lt;/span&gt; &lt;span class="nt"&gt;--unified&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 | ollama run qwen-commit &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$system_prompt&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;exit_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;# 3. Robust Error Validation&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$exit_code&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$msg&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"❌ Error: Failed to generate a response via &lt;/span&gt;&lt;span class="nv"&gt;$mode&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Details received: &lt;/span&gt;&lt;span class="nv"&gt;$msg&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;# 4. Strict Text Cleaning Pipeline&lt;/span&gt;
  &lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$msg&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'\r'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/```(diff)?//g'&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/^[[:space:]]+//;s/[[:space:]]+$//'&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/^["'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;']//'&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/["'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;']$//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;# 5. Run git commit cleanly&lt;/span&gt;
  git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$msg&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Alias to explicitly force Claude&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;gacc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gac claude"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tell the model what you already know. Don't make it guess things you can easily extract.&lt;/li&gt;
&lt;li&gt;Low temperature for tasks where you want some determinism.&lt;/li&gt;
&lt;li&gt;Modelfiles are useful. You can create a named model configured for a specific job.&lt;/li&gt;
&lt;li&gt;Model size, (V)RAM, and context size are all connected. On a constrained machine, you have to be intentional about all three.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Is this perfect?
&lt;/h2&gt;

&lt;p&gt;No. It still sometimes misses the point of a change. It takes time on larger commits. There is room for improvement.&lt;/p&gt;

&lt;p&gt;Why not just use Claude directly? That's the easiest thing to do, but it still costs me tokens. And I wanted to learn how local models work. How context windows affect output. How to tune a model for a specific job. That was the whole point for me.&lt;/p&gt;

&lt;p&gt;It works offline, costs nothing 💰, and I understand every piece because I broke it and fixed it.&lt;/p&gt;

&lt;p&gt;I find the best way to learn is to find a real use case, however trivial. It helps you understand concepts one thing at a time.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Next up: My learnings building a green field product with OpenSpec meant for Brown field projects&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I welcome all constructive feedback and comments&lt;/em&gt;&lt;/p&gt;

</description>
      <category>genai</category>
      <category>ollama</category>
      <category>learning</category>
      <category>llm</category>
    </item>
    <item>
      <title>The case of disappearing metrics in Kubernetes</title>
      <dc:creator>Shyamala</dc:creator>
      <pubDate>Sun, 24 May 2020 15:46:30 +0000</pubDate>
      <link>https://dev.to/shyamala_u/the-case-of-disappearing-metrics-in-kubernetes-1kdh</link>
      <guid>https://dev.to/shyamala_u/the-case-of-disappearing-metrics-in-kubernetes-1kdh</guid>
      <description>&lt;h2&gt;
  
  
  🧐 Context 🧐
&lt;/h2&gt;

&lt;p&gt;My team hosts the kubernetes platform running on &lt;a href="https://aws.amazon.com/eks/"&gt;EKS&lt;/a&gt; for several teams. When EKS announced the support for Kubernetes version 1.16 we upgraded our infrastructure. This is pretty routine process for us. We use Prometheus and grafana dashboard to observe and monitor our infrastructure. &lt;/p&gt;

&lt;p&gt;After we upgraded, our metrics dashboard stopped showing RAM and CPU utilization of pods. I set out to find why?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/42wQXwITfQbDGKqUP7/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/42wQXwITfQbDGKqUP7/giphy.gif" alt="detective"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚠️ Disclaimer ⚠️
&lt;/h2&gt;

&lt;p&gt;Before we move ahead, &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I am a newbie to Kubernetes learning the tricks on the job&lt;/li&gt;
&lt;li&gt;This is my journey on how I debugged the issue, so what is obvious to a Kubernetes expert is not obvious to me.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  TL;DR
&lt;/h3&gt;

&lt;p&gt;If you already know what caused this issue, jump to final section&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging Step 1:
&lt;/h2&gt;

&lt;p&gt;So the dashboard is not showing metrics, normally Kubectl has commands that shows the CPU and RAM utilization of the Nodes and Pods. So I checked if those worked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; kubectl top nodes
NAME                                         CPU&lt;span class="o"&gt;(&lt;/span&gt;cores&lt;span class="o"&gt;)&lt;/span&gt;   CPU%   MEMORY&lt;span class="o"&gt;(&lt;/span&gt;bytes&lt;span class="o"&gt;)&lt;/span&gt;   MEMORY%
ip-xx-xx-xxx-xxx.ec2.internal   8xxm         5%     2xxxxMi         23%
ip-xx-xx-xxx-xxx.ec2.internal   2xxxxm       15%    3xxxxMi         29%
ip-....

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; kubectl top pods
NAME         CPU&lt;span class="o"&gt;(&lt;/span&gt;cores&lt;span class="o"&gt;)&lt;/span&gt;   MEMORY&lt;span class="o"&gt;(&lt;/span&gt;bytes&lt;span class="o"&gt;)&lt;/span&gt;
xxxx-wnpgj   xxm          xxMi
xxxx-r4k85   xxm          xxxMi
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  🤔 What does this mean? 🤔
&lt;/h3&gt;

&lt;p&gt;Kubernetes is able to fetch the metrics. These are the metrics that have to be used by the &lt;a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#how-does-the-horizontal-pod-autoscaler-work"&gt;HPA&lt;/a&gt; to scale the Pods based on the resource utilization. &lt;/p&gt;
&lt;h2&gt;
  
  
  Debugging Step 2:
&lt;/h2&gt;

&lt;p&gt;Since the dashboard does not show the resource utilization metrics, but the &lt;code&gt;kubectl top&lt;/code&gt; command works, my next thought was may be the &lt;code&gt;scaling does not work properly anymore&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://kubernetes.io/docs/tasks/debug-application-cluster/resource-usage-monitoring/#resource-metrics-pipeline"&gt;Resource usage monitoring&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;components such as the Horizontal Pod Autoscaler controller, as well as the kubectl top utility. These metrics are collected by the lightweight, short-term, in-memory metrics-server and are exposed via the metrics.k8s.io API&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So now I wanted to see what is the status with one of our teams.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; kubectl get hpa &lt;span class="nt"&gt;-n&lt;/span&gt; xxx 
NAME         REFERENCE               TARGETS           MINPODS   MAXPODS   REPLICAS   AGE
xxx-worker   Deployment/xxx-worker   82/150, 5%/100%   1         6         1          4d19h
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Since the Targets look fine in the above output. I thought let me see if at any point the scaling actually happened.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; kubectl describe hpa &lt;span class="nt"&gt;-n&lt;/span&gt; xxx 

Type     Reason                   Age                      From                       Message
&lt;span class="nt"&gt;----&lt;/span&gt;     &lt;span class="nt"&gt;------&lt;/span&gt;                   &lt;span class="nt"&gt;----&lt;/span&gt;                     &lt;span class="nt"&gt;----&lt;/span&gt;                       &lt;span class="nt"&gt;-------&lt;/span&gt;
Warning  FailedGetResourceMetric  25m &lt;span class="o"&gt;(&lt;/span&gt;x24 over 2d23h&lt;span class="o"&gt;)&lt;/span&gt;     horizontal-pod-autoscaler  unable to get metrics &lt;span class="k"&gt;for &lt;/span&gt;resource cpu: no metrics returned from resource metrics API
Normal   SuccessfulRescale        9m56s &lt;span class="o"&gt;(&lt;/span&gt;x233 over 2d23h&lt;span class="o"&gt;)&lt;/span&gt;  horizontal-pod-autoscaler  New size: 2&lt;span class="p"&gt;;&lt;/span&gt; reason: cpu resource utilization &lt;span class="o"&gt;(&lt;/span&gt;percentage of request&lt;span class="o"&gt;)&lt;/span&gt; above target
Normal   SuccessfulRescale        4m9s &lt;span class="o"&gt;(&lt;/span&gt;x233 over 2d22h&lt;span class="o"&gt;)&lt;/span&gt;   horizontal-pod-autoscaler  New size: 1&lt;span class="p"&gt;;&lt;/span&gt; reason: All metrics below target
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  🤔 What does this mean? 🤔
&lt;/h3&gt;

&lt;p&gt;As we can see at some point fetching the resource metric failed but we can also see that the system recovered and was able to scale successfully. &lt;/p&gt;

&lt;p&gt;Since there is a &lt;code&gt;FailedGetResourceMetric&lt;/code&gt; at some point, I thought this could lead me to why metrics do not appear in the dashboard.&lt;/p&gt;
&lt;h2&gt;
  
  
  Debugging Step 3:
&lt;/h2&gt;

&lt;p&gt;As we have seen from several documentation mentioned above and also from the error &lt;code&gt;no metrics returned from resource metrics API&lt;/code&gt;. There is some component in Kubernetes that is responsible for the metrics. So I cast a wide net&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; kubectl get all &lt;span class="nt"&gt;-n&lt;/span&gt; metrics | &lt;span class="nb"&gt;grep &lt;/span&gt;metrics | &lt;span class="nb"&gt;grep &lt;/span&gt;pod
pod/metrics-server-xxxxxxx-xxxxx                        1/1     Running            0          4d21h
pod/prometheus-kube-state-metrics-xxxx-xxxxx            1/1     Running            0          4d21h
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This sound promising. So &lt;code&gt;metrics-server&lt;/code&gt; sounds like some server dealing with metrics. Naturally I am interested in logs&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; k logs &lt;span class="nt"&gt;-n&lt;/span&gt; metrics pod/metrics-server-xxxxxxx-xxxxx
I0524 11:17:54.371856       1 manager.go:120] Querying &lt;span class="nb"&gt;source&lt;/span&gt;: kubelet_summary:ip-xx-xx-xxx-xxx.ec2.internal
I0524 11:17:54.388840       1 manager.go:120] Querying &lt;span class="nb"&gt;source&lt;/span&gt;: kubelet_summary:ip-xx-xx-xxx-xxx.ec2.internal
I0524 11:17:54.495829       1 manager.go:148] ScrapeMetrics: &lt;span class="nb"&gt;time&lt;/span&gt;: 233.232388ms, nodes: x, pods: xxx
E0524 11:18:18.542051       1 reststorage.go:160] unable to fetch pod metrics &lt;span class="k"&gt;for &lt;/span&gt;pod my-namespace/team-worker-xxxxx-xxxx: no metrics known &lt;span class="k"&gt;for &lt;/span&gt;pod
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  🤔 What does this mean? 🤔
&lt;/h3&gt;

&lt;p&gt;As soon as I saw the words &lt;code&gt;ScrapeMetrics&lt;/code&gt; (my brain translated it to Prometheus related) and the error &lt;code&gt;unable to fetch pod metrics for pod my-namespace/team-worker-xxxxx-xxxx: no metrics known for pod&lt;/code&gt;&lt;br&gt;
I thought I have found the issue.&lt;/p&gt;
&lt;h2&gt;
  
  
  Debugging Step 4:
&lt;/h2&gt;

&lt;p&gt;As any seasoned developer would do, I took the error message and googled it. Which resulted in some links and on a quick scan, I found couple of interesting Links &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QAQCHiO6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n0stnswd90ro145nvx0a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QAQCHiO6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n0stnswd90ro145nvx0a.png" alt="Links from google search"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After quickly scanning through the links, every one seem to suggest this following change in metrics-server&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/metrics-server&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--kubelet-insecure-tls&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;-- 🤓&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--kubelet-preferred-address-types=InternalIP&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;-- 🤓&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;--kubelet-insecure-tls&lt;/code&gt; seems like something you would do in a test setup. So I wanted to understand a bit more what this flags do.&lt;br&gt;
From the &lt;a href="https://github.com/kubernetes-sigs/metrics-server#configuration"&gt;documentation&lt;/a&gt;, it is clear that the above flag is intended for testing purposes only and in the logs of metrics server I did not find any error pertaining to certificates. So I did not add the flag.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--kubelet-preferred-address-types&lt;/code&gt; this configuration is to determine which address type to use to connect to the nodes for getting the metrics. &lt;br&gt;
So before adding this config I wanted to verify if the metrics server api was reachable or not.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; kubectl get apiservices | &lt;span class="nb"&gt;grep &lt;/span&gt;metrics-server
v1beta1.metrics.k8s.io                 kube-system/metrics-server       True        4d23h
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;since the status of this service is available, those two flags would not make any sense. I wanted to see if I can get some raw metrics by querying this API.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; kubectl get &lt;span class="nt"&gt;--raw&lt;/span&gt; &lt;span class="s2"&gt;"/apis/metrics.k8s.io/v1beta1/pods"&lt;/span&gt; | jq &lt;span class="s1"&gt;'.items | .[] | select(.metadata.namespace == "xxx")'&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"metadata"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"xxx-worker-6c5bd59857-xxxxx"&lt;/span&gt;,
    &lt;span class="s2"&gt;"namespace"&lt;/span&gt;: &lt;span class="s2"&gt;"xxx"&lt;/span&gt;,
    &lt;span class="s2"&gt;"selfLink"&lt;/span&gt;: &lt;span class="s2"&gt;"/apis/metrics.k8s.io/v1beta1/namespaces/xxx/pods/xxx-worker-6c5bd59857-xxxxx"&lt;/span&gt;,
    &lt;span class="s2"&gt;"creationTimestamp"&lt;/span&gt;: &lt;span class="s2"&gt;"2020-05-24T12:06:45Z"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"timestamp"&lt;/span&gt;: &lt;span class="s2"&gt;"2020-05-24T12:05:48Z"&lt;/span&gt;,
  &lt;span class="s2"&gt;"window"&lt;/span&gt;: &lt;span class="s2"&gt;"30s"&lt;/span&gt;,
  &lt;span class="s2"&gt;"containers"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"xxx-worker"&lt;/span&gt;,
      &lt;span class="s2"&gt;"usage"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"cpu"&lt;/span&gt;: &lt;span class="s2"&gt;"xxxxxn"&lt;/span&gt;,
        &lt;span class="s2"&gt;"memory"&lt;/span&gt;: &lt;span class="s2"&gt;"xxxxxxxKi"&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  🤔 What does this mean? 🤔
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/ql4LidslabKpi/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/ql4LidslabKpi/giphy.gif" alt="Frustration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So everything seems to function well. I do not know why the metrics I am clearly able to query via &lt;code&gt;Kubectl&lt;/code&gt; is not available for the dashboard.&lt;/p&gt;
&lt;h2&gt;
  
  
  Debugging Step 5:
&lt;/h2&gt;

&lt;p&gt;Now I want to see the source code of &lt;a href="https://github.com/kubernetes-sigs/metrics-server/blob/d1f4f6fc09cd3134e8ea5ba4e0bd2db4e8002ed8/pkg/storage/podmetrics/reststorage.go#L133"&gt;metrics-server&lt;/a&gt; to figure out under what circumstance this error happens&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="n"&gt;podMetrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPodMetrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;podMetrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no metrics known for pod &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;%s/%s&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;klog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unable to fetch pod metrics for pod %s/%s: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewNotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupResource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%v/%v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It is very clear from the code that the Pod exists but metrics is not available. So I tailed the logs and got the pod for which metrics is not found&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; kubectl get po &lt;span class="nt"&gt;-n&lt;/span&gt; xxx
NAME                          READY   STATUS    RESTARTS   AGE
xxx-worker-6c5bd59857-xxxxx   1/1     Running   0          1m5s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  🤔 What does this mean? 🤔
&lt;/h3&gt;

&lt;p&gt;It is very clear from above that all these pods where metrics are not available are pods that have been recently created &lt;br&gt;
and since metrics server scrapes for metrics periodically, it's just not available at the moment.&lt;/p&gt;

&lt;p&gt;In my opinion it is really not an &lt;code&gt;ERROR&lt;/code&gt; rather an expected behaviour. A &lt;code&gt;WARN&lt;/code&gt; or &lt;code&gt;INFO&lt;/code&gt; would be reasonable. It is good to see am not alone in this&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/kubernetes-sigs/metrics-server/issues/349"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://res.cloudinary.com/practicaldev/image/fetch/s--vWogaON8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-28d89282e0daa1e2496205e2f218a44c755b0dd6536bbadf5ed5a44a7ca54716.svg"&gt;
      &lt;span class="issue-title"&gt;
        Remove/propose different "no metrics known for pod" log
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#349&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/serathius"&gt;
        &lt;img class="github-liquid-tag-img" src="https://res.cloudinary.com/practicaldev/image/fetch/s--snv7FVcT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://avatars2.githubusercontent.com/u/5873433%3Fv%3D4" alt="serathius avatar"&gt;
      &lt;/a&gt;
      &lt;span class="arrow-left-outer"&gt;&lt;/span&gt;
      &lt;span class="arrow-left-inner"&gt;&lt;/span&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/serathius"&gt;serathius&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/kubernetes-sigs/metrics-server/issues/349"&gt;&lt;time&gt;Nov 05, 2019&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;There are a lot of issues where users are seeing metrics-server reporting error "no metrics known for pod" and asking for help.&lt;/p&gt;
&lt;p&gt;To my understanding this error is expected to occur in normal healthy metrics-server.
Metrics Server periodically scrapes all nodes to gather metrics and populate it's internal cache. When there is a request to Metrics API, metrics-server reaches to this cache and looks for existing value for pod. If there is no value for existing pod in k8s, metrics server reports error "no metrics known for pod". This means this error can happen in situation when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fresh metrics-server is deployed with clean cache&lt;/li&gt;
&lt;li&gt;query is about fresh pod/node that was not yet scraped&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Providing better information to users would greatly reduce throughput of tickets.&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kubernetes-sigs/metrics-server/issues/349"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;I have been chasing a wrong lead after all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l0W-_Spg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgflip.com/42nax3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l0W-_Spg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgflip.com/42nax3.jpg" alt="back to square one"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging Step 5:
&lt;/h2&gt;

&lt;p&gt;I have eliminated that Metrics server is not the reason for the bug. This got me to thinking, I should find how Prometheus is scraping the metrics.&lt;/p&gt;

&lt;p&gt;After a lot of reading I figured out Prometheus gets the metrics exposed by the &lt;code&gt;kube-state-metrics&lt;/code&gt;. It is very important to understand the difference between &lt;code&gt;metrics-server&lt;/code&gt; and &lt;code&gt;kube-state-metrics&lt;/code&gt;&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/kubernetes-sigs/metrics-server/issues/55#issuecomment-387229420"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://res.cloudinary.com/practicaldev/image/fetch/s--vWogaON8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-28d89282e0daa1e2496205e2f218a44c755b0dd6536bbadf5ed5a44a7ca54716.svg"&gt;
      &lt;span class="issue-title"&gt;
        Comment for
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#55&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/metalmatze"&gt;
        &lt;img class="github-liquid-tag-img" src="https://res.cloudinary.com/practicaldev/image/fetch/s--x9IZiTq1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://avatars1.githubusercontent.com/u/872251%3Fv%3D4" alt="metalmatze avatar"&gt;
      &lt;/a&gt;
      &lt;span class="arrow-left-outer"&gt;&lt;/span&gt;
      &lt;span class="arrow-left-inner"&gt;&lt;/span&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/metalmatze"&gt;metalmatze&lt;/a&gt;
        &lt;/strong&gt; commented on &lt;a href="https://github.com/kubernetes-sigs/metrics-server/issues/55#issuecomment-387229420"&gt;&lt;time&gt;May 07, 2018&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;kube-state-metrics focuses on&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...on the health of the various objects inside, such as deployments, nodes and pods.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;metrics-server on the other hand implements the &lt;a href="https://github.com/kubernetes/community/blob/master/contributors/design-proposals/instrumentation/resource-metrics-api.md"&gt;Resource Metrics API
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To summarize in a sentence: kube-state-metrics exposes metrics for all sorts of Kubernetes objects. metrics-server only exposes very few metrics to Kubernetes itself (not scrapable directly with Prometheus), like node &amp;amp; pod utilization.&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kubernetes-sigs/metrics-server/issues/55#issuecomment-387229420"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Looking at the logs of prometheus server and kube-state-metrics nothing jumped out of ordinary.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a&gt;Finally&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I asked for help, I explained what I have done so far and my team mate suggested the obvious, did you check if the query works!!&lt;/p&gt;

&lt;p&gt;so I went to prometheus query explorer and executed&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;container_cpu_usage_seconds_total{namespace="xxx", pod_name="xxx-worker-6c5bd59857-xxxxx"}

Returned no results

container_cpu_usage_seconds_total{namespace="xxx"}

Returned results

container_cpu_usage_seconds_total{namespace="xxx", pod="xxx-worker-6c5bd59857-xxxxx"}

Returned results
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  🤔 What does this mean? 🤔
&lt;/h3&gt;

&lt;p&gt;So some where during the EKS version upgrade the support for label &lt;code&gt;pod_name&lt;/code&gt; is &lt;a href="https://github.com/kubernetes/kubernetes/pull/80376/commits/e0b66c792b8daabc8f2dcc5209356bca2cb2b197"&gt;dropped&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After fixing the queries in grafana, I could see the metrics finally.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--leA0no1W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgflip.com/42nepy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--leA0no1W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgflip.com/42nepy.jpg" alt="case closed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Learn the components and its purpose before assuming&lt;/li&gt;
&lt;li&gt;Always read the release notes when &lt;a href="https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.16.md#removed-metrics"&gt;upgrading even minor versions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Removed cadvisor metric labels pod_name and container_name to match instrumentation guidelines. Any Prometheus queries that match pod_name and container_name labels (e.g. cadvisor or kubelet probe metrics) must be updated to use pod and container instead. (#80376, @ehashman)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;The solution is always the obvious solution
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NpNZI_ld--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgflip.com/42nqn2.jpg" alt="do not overthink just because it is kubernetes"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;I welcome all constructive feedback and comments&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>prometheus</category>
      <category>aws</category>
    </item>
    <item>
      <title>Microservices - Why wait for your Integration tests to fail when you have Pact?</title>
      <dc:creator>Shyamala</dc:creator>
      <pubDate>Tue, 02 Apr 2019 19:08:22 +0000</pubDate>
      <link>https://dev.to/shyamala_u/microservices-why-wait-for-your-integration-tests-to-fail-when-you-have-pact-2acd</link>
      <guid>https://dev.to/shyamala_u/microservices-why-wait-for-your-integration-tests-to-fail-when-you-have-pact-2acd</guid>
      <description>&lt;p&gt;With more and more software moving from monolith to microservices, we are swamped in the world of more APIs than ever. How can we write APIs that are meaningful, precise , testable and maintainable? Once an API is out there we are obligated not to break them or alternatively we have to know before hand that we have breaking changes. &lt;/p&gt;

&lt;p&gt;In this post I try to put together my experience on how Pact (&lt;a href="https://docs.pact.io"&gt;https://docs.pact.io&lt;/a&gt;) helps us in writing better APIs in our microservices architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Pact?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Pact (noun):&lt;br&gt;
A formal agreement between individuals or parties.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;According to &lt;a href="https://pact.io"&gt;&lt;code&gt;Pact Foundation&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pact is a &lt;code&gt;contract testing tool&lt;/code&gt;. Contract testing is a way to ensure that services (such as an API provider and a consumer) can communicate with each other. Without contract testing, the only way to know that services can communicate is by using expensive and brittle integration tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Parties
&lt;/h2&gt;

&lt;p&gt;Any API(Application Programming Interface) consists of two important parties:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provider:&lt;/strong&gt; One who exposes one or more API(s)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consumer(s)&lt;/strong&gt;: One or more client(s) who uses the API(s) exposed by the Provider&lt;/p&gt;

&lt;h2&gt;
  
  
  How does a Pact work?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Consumer captures an expectation&lt;/code&gt; as a separate pact, and &lt;code&gt;provider agrees to it&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages of using Pacts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Isolation:
&lt;/h3&gt;

&lt;p&gt;The Ability to test the provider and consumer &lt;strong&gt;in Isolation but in conjunction&lt;/strong&gt;, i.e. Pacts gives us the sophistication of&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;testing a consumer with provider's API from the comfort of a developer's machine Without having to make an actual call.&lt;/li&gt;
&lt;li&gt;testing a provider's API changes against one or more consumers to make sure that the changes do not accidentally break the existing consumers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quick Feedback
&lt;/h3&gt;

&lt;p&gt;We do not have to wait for our End to End tests to fail, the same feedback can be unit tested.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Centred API design
&lt;/h3&gt;

&lt;p&gt;Pacts are essentially &lt;code&gt;Consumer Driven Contracts&lt;/code&gt;, so the consumers are laying out the expectation which leads to better usable APIs&lt;/p&gt;

&lt;h3&gt;
  
  
  Overview of existing APIs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Pacts provides &lt;code&gt;Network Graph&lt;/code&gt; of dependant services&lt;/li&gt;
&lt;li&gt;Pacts helps us understand, if &lt;code&gt;one or more APIs are similar&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pacts can also show &lt;code&gt;unused APIs&lt;/code&gt;, when the consumers are not using them anymore.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Language independent
&lt;/h3&gt;

&lt;p&gt;Pact is a &lt;a href="https://github.com/pact-foundation/pact-specification#index"&gt;specification&lt;/a&gt;, which makes it perfect for microservices testing. You can find Pact consumer and provider libraries implemented in many programming languages &lt;a href="https://github.com/pact-foundation"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pact in Action:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  An Example Pact
&lt;/h3&gt;

&lt;p&gt;Let us take the most simple use case of authentication as a service.&lt;/p&gt;

&lt;p&gt;Consumer: I need to log the user in, I have user credentials&lt;br&gt;
Provider: I can authenticate a user given credentials&lt;/p&gt;

&lt;p&gt;For demonstration, we use the same pact as described above. Complete implementation can be found &lt;a href="https://github.com/shyamz-22/Pactit"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Consumer Pact in Go:
&lt;/h3&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"fmt"&lt;/span&gt;
        &lt;span class="s"&gt;"github.com/pact-foundation/pact-go/dsl"&lt;/span&gt;
        &lt;span class="s"&gt;"net/http"&lt;/span&gt;
        &lt;span class="s"&gt;"testing"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestClient_AuthenticateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"alice"&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"s3cr3t"&lt;/span&gt;

        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user exists"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;pact&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dsl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pact&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Consumer&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Quoki"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"UserManager"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;PactDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"../pacts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;LogDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"../pacts/logs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;pact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Teardown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;pact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
                &lt;span class="n"&gt;AddInteraction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
                &lt;span class="n"&gt;Given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user exists"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
                &lt;span class="n"&gt;UponReceiving&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a request to authenticate"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
                &lt;span class="n"&gt;WithRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dsl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;dsl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users/%s/authentication"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                    &lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dsl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MapMatcher&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dsl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Like&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/x-www-form-urlencoded"&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
                    &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dsl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MapMatcher&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dsl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Like&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
                &lt;span class="n"&gt;WillRespondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dsl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNoContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="n"&gt;pact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:%d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Running this successfully will generate,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"consumer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Quoki"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UserManager"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"interactions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a request to authenticate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"providerState"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user exists"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/users/alice/authentication"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/x-www-form-urlencoded"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"password=s3cr3t"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"matchingRules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"$.headers.Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"$.body.password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pactSpecification"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0.0"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Provider verification of Pact in Kotlin
&lt;/h3&gt;

&lt;p&gt;The Provider then takes this as requirement, then verifies by running this test&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;    &lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.shyamz.provider.authenticate&lt;/span&gt;

    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;au.com.dius.pact.provider.junit.Consumer&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;au.com.dius.pact.provider.junit.Provider&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;au.com.dius.pact.provider.junit.State&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;au.com.dius.pact.provider.junit.loader.PactFolder&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;au.com.dius.pact.provider.junit.target.HttpTarget&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;au.com.dius.pact.provider.junit.target.Target&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;au.com.dius.pact.provider.junit.target.TestTarget&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;au.com.dius.pact.provider.spring.SpringRestPactRunner&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.Before&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.runner.RunWith&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.beans.factory.annotation.Autowired&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.test.context.SpringBootTest&lt;/span&gt;


    &lt;span class="nd"&gt;@RunWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SpringRestPactRunner&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UserManager"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Consumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Quoki"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@PactFolder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"../consumer/src/consumer/http/pacts"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webEnvironment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SpringBootTest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebEnvironment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DEFINED_PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;properties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"server.port=8601"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuokiUserAuthenticatePactItTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nd"&gt;@Suppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unused"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nd"&gt;@JvmField&lt;/span&gt;
        &lt;span class="nd"&gt;@TestTarget&lt;/span&gt;
        &lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Target&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8601&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;lateinit&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;userRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt;

        &lt;span class="nd"&gt;@Before&lt;/span&gt;
        &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nd"&gt;@State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user exists"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;userExists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;QuokiUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"s3cr3t"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Running this will provide the result&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Verifying a pact between Quoki and UserManager
    Given user exists
        a request to authenticate
    returns a response which
        has status code 204 (OK)
        has a matching body (OK)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A Bad Pact
&lt;/h3&gt;

&lt;p&gt;A bad pact, dictates what it considers as a precondition for an invalid username and tightly couples the exact error message that is expected.&lt;/p&gt;

&lt;p&gt;This is then unit testing the provider's implementation detail rather than the contract itself. So the provider cannot change the validation rule or the error message without breaking the consumer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Given :&lt;/strong&gt; Alice does not exist&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upon receiving:&lt;/strong&gt; A request to create user, with user name that has special characters other than underscores&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt; Is 400 Bad Request&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response Body:&lt;/strong&gt; {"error": "username cannot contain special characters"}&lt;/p&gt;

&lt;h3&gt;
  
  
  A Good Pact
&lt;/h3&gt;

&lt;p&gt;A Good pact from a consumer &lt;strong&gt;hides the provider's implementation details. It must only capture expectation from consumer point of view&lt;/strong&gt;. In the below example a consumer shall not dictate what it considers an invalid username.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Given :&lt;/strong&gt; Alice does not exist&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upon receiving:&lt;/strong&gt; A request to create user with invalid username&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt; Is 400 Bad Request&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response Body:&lt;/strong&gt; {"error": "[a non empty string]"}&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;If you have more questions or if you need more information, you can find all the information &lt;a href="https://docs.pact.io/"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>pact</category>
      <category>testing</category>
    </item>
    <item>
      <title>Spring boot + Spring Security 5 + OAuth2/OIDC Client - Deep Dive</title>
      <dc:creator>Shyamala</dc:creator>
      <pubDate>Mon, 25 Mar 2019 21:51:04 +0000</pubDate>
      <link>https://dev.to/shyamala_u/spring-boot--spring-security-5--oauth2oidc-client---deep-dive-261l</link>
      <guid>https://dev.to/shyamala_u/spring-boot--spring-security-5--oauth2oidc-client---deep-dive-261l</guid>
      <description>&lt;p&gt;In my &lt;a href="https://dev.to/shyamala_u/spring-boot--spring-security-5--oauth2oidc-client---basics-4ibo"&gt;previous post&lt;/a&gt; we saw how easy it is to protect your application with Google Login.&lt;/p&gt;

&lt;p&gt;Now let us see what are all the components responsible for this to work.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Login process
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Request to access the protected Endpoint and Google Authentication process starts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;application.yml&lt;/code&gt; is &lt;a href="https://dev.to/shyamala_u/spring-boot--spring-security-5--oauth2oidc-client---basics-4ibo#step-3"&gt;configured&lt;/a&gt; with client and provider values&lt;/li&gt;
&lt;li&gt;Provider name in the property &lt;code&gt;spring.security.oauth2.client.registration.provider&lt;/code&gt; is set to google&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When access to &lt;code&gt;http://localhost:8080/me&lt;/code&gt; is requested, If you have only one identity provider configured then, Spring redirects you automatically to &lt;code&gt;http://localhost:8080/oauth2/authorization/google&lt;/code&gt;. &lt;code&gt;OAuth2AuthorizationRequestRedirectFilter&lt;/code&gt; which is registered to the url pattern &lt;code&gt;/oauth2/authorization/*&lt;/code&gt; will load the respective configuration and redirect to the Identity Provider. In our case Google.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want your users to choose between multiple providers, then configure your &lt;code&gt;application.yml&lt;/code&gt; for multiple providers, but you need a login page where you can have multiple links for the users to choose from.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  On Successful authentication google redirects to the app's redirect url
&lt;/h2&gt;

&lt;p&gt;Once the user authenticates with Google successfully, Google now redirects to the app's redirect url configured in Google's developer console. In our example we chose to have a particular url &lt;code&gt;http://localhost:8080/login/oauth2/code/google&lt;/code&gt;. This is because the Authentication Processing filter for OAuth2 &lt;code&gt;OAuth2LoginAuthenticationFilter&lt;/code&gt; is registered to listen to &lt;code&gt;/login/oauth2/code/*&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;OAuth2LoginAuthenticationFilter delegates authentication to &lt;code&gt;OidcAuthorizationCodeAuthenticationProvider&lt;/code&gt; which does 3 things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exchanges Code for token&lt;/li&gt;
&lt;li&gt;Validates id_token&lt;/li&gt;
&lt;li&gt;Populates User Info by calling the User Info endpoint, from Google's well known configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you might ask what if I have registered a different redirect URI, and want OAuth2LoginAuthenticationFilter to listen to this. It is pretty simple all you need to do is have the following Security Configuration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@EnableWebSecurity&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecurityConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WebSecurityConfigurerAdapter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HttpSecurity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorizeRequests&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;anyRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;oauth2Login&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirectionEndpoint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;baseUri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/oauth/callback/*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Next
&lt;/h2&gt;

&lt;p&gt;As a result of successful authentication, you will get an Authentication object of type &lt;code&gt;OAuth2AuthenticationToken&lt;/code&gt;. This token will contain all the necessary information from &lt;code&gt;id_token&lt;/code&gt; and the &lt;code&gt;user info endpoint&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can access all data about the logged in user  by&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;SecurityContextHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;authentication&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthenticationToken&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt; &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/me"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthenticationToken&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OAuth2AuthenticationToken&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  But what about Access and Refresh Tokens?
&lt;/h2&gt;

&lt;p&gt;As a result of successful OpenID Connect flow, a client application receives three tokens, &lt;code&gt;access_token, refresh_token and id_token&lt;/code&gt;. We might want to use this access token to access some protected resource from a resource server &lt;a href="https://developers.google.com/tasks/v1/reference/tasks/list"&gt;like tasks API of google&lt;/a&gt;. The &lt;code&gt;OAuth2AuthorizedClientService&lt;/code&gt; keeps track of the tokens associated with the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;currentUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SecurityContextHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;authentication&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthenticationToken&lt;/span&gt;
 &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;currentUserClientConfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;oAuth2AuthorizedClientService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadAuthorizedClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;authorizedClientRegistrationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AccessToken: ${currentUserClientConfig.accessToken.tokenValue}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RefreshToken: ${currentUserClientConfig.refreshToken.tokenValue}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  But Access Tokens can expire
&lt;/h2&gt;

&lt;p&gt;When access tokens expire, the resource server like &lt;a href="https://developers.google.com/tasks/v1/reference/tasks/list"&gt;like tasks API of google&lt;/a&gt; will return 401 HTTP status, the simplest solution is to throw an &lt;code&gt;OAuth2AuthorizationException&lt;/code&gt; which is a type of &lt;code&gt;AuthenticationException&lt;/code&gt; that will trigger the login flow again. &lt;/p&gt;

&lt;p&gt;But we can also use Refresh Tokens to automatically refresh our tokens, by customizing &lt;code&gt;RestTemplate&lt;/code&gt; with a request interceptor that will refresh the tokens on expiry&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BearerTokenInterceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;oAuth2AuthorizedClientService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthorizedClientService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ClientHttpRequestInterceptor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LoggerFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BearerTokenInterceptor&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;accessTokenExpiresSkew&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ofMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;clock&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;systemUTC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;intercept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ByteArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;execution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ClientHttpRequestExecution&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ClientHttpResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;currentUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SecurityContextHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;authentication&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthenticationToken&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;currentUserClientConfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clientConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isExpired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentUserClientConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AccessToken expired, refreshing automatically"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentUserClientConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;AUTHORIZATION&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Bearer ${currentUserClientConfig.accessToken.tokenValue}"&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;execution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthenticationToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clientConfig&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthorizedClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;oAuth2AuthorizedClientService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadAuthorizedClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;authorizedClientRegistrationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;CredentialsExpiredException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"could not load client config for $name, reauthenticate"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthorizedClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthenticationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;atr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;refreshTokenClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;atr&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;atr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to refresh token for ${currentUser.name}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;refreshToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;atr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refreshToken&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="n"&gt;currentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refreshToken&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;updatedClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthorizedClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;currentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clientRegistration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;currentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;principalName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;atr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;refreshToken&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;oAuth2AuthorizedClientService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;saveAuthorizedClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updatedClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;refreshTokenClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthorizedClient&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AccessTokenResponse&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;formParameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LinkedMultiValueMap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="n"&gt;formParameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OAuth2ParameterNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GRANT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AuthorizationGrantType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;REFRESH_TOKEN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;formParameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OAuth2ParameterNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;REFRESH_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refreshToken&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;tokenValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;formParameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OAuth2ParameterNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clientRegistration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redirectUriTemplate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;requestEntity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RequestEntity&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clientRegistration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;providerDetails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tokenUri&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CONTENT_TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;APPLICATION_FORM_URLENCODED_VALUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formParameters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;restTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clientRegistration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clientRegistration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;responseEntity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestEntity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AccessTokenResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;responseEntity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthorizationException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unable to refresh token ${e.error.errorCode}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthenticationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isExpired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AccessToken&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;now&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instant&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;expiresAt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accessTokenExpiresSkew&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;restTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;RestTemplate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;RestTemplateBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;additionalMessageConverters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="nc"&gt;FormHttpMessageConverter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                        &lt;span class="nc"&gt;OAuth2AccessTokenResponseHttpMessageConverter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errorHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OAuth2ErrorResponseErrorHandler&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;So far I have not found that the oauth2-client can automatically refresh tokens within the user session, Let me know if this is the case :)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I tried to put together all pieces involved, Please give me feedback if I missed something :)&lt;/p&gt;

</description>
      <category>oauth2</category>
      <category>java</category>
      <category>spring</category>
      <category>springsecurity</category>
    </item>
    <item>
      <title>Spring boot + Spring Security 5 + OAuth2/OIDC Client - Basics</title>
      <dc:creator>Shyamala</dc:creator>
      <pubDate>Sun, 24 Mar 2019 17:45:04 +0000</pubDate>
      <link>https://dev.to/shyamala_u/spring-boot--spring-security-5--oauth2oidc-client---basics-4ibo</link>
      <guid>https://dev.to/shyamala_u/spring-boot--spring-security-5--oauth2oidc-client---basics-4ibo</guid>
      <description>&lt;p&gt;Since a long time I wanted to integrate an OpenID Connect provider using Spring Security, The last time I tried, I felt it was very complicated and wrote my own &lt;a href="https://github.com/shyamz-22/openid-connect-client"&gt;library&lt;/a&gt;. Since Spring Security 5 has native support for OAuth2 Client and extended its use for OpenID connect, I wanted to see how easy it is to integrate.&lt;/p&gt;

&lt;p&gt;For this example we are going to build a simple app, the redirects to google when we try to access a protected endpoint&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 1:
&lt;/h1&gt;

&lt;p&gt;Create a spring boot project from &lt;a href="https://start.spring.io"&gt;https://start.spring.io&lt;/a&gt; with following dependencies&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.springframework.boot:spring-boot-starter-oauth2-client'&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.springframework.boot:spring-boot-starter-security'&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.springframework.boot:spring-boot-starter-web'&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'com.fasterxml.jackson.module:jackson-module-kotlin'&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.jetbrains.kotlin:kotlin-reflect'&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.jetbrains.kotlin:kotlin-stdlib-jdk8'&lt;/span&gt;
    &lt;span class="n"&gt;testImplementation&lt;/span&gt; &lt;span class="s1"&gt;'org.springframework.boot:spring-boot-starter-test'&lt;/span&gt;
    &lt;span class="n"&gt;testImplementation&lt;/span&gt; &lt;span class="s1"&gt;'org.springframework.security:spring-security-test'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Step 2:
&lt;/h1&gt;

&lt;p&gt;Create an endpoint that will show current user's authentication data&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/me"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OAuth2AuthenticationToken&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OAuth2AuthenticationToken&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Step 3:
&lt;/h1&gt;

&lt;p&gt;Configure OAuth2 Client information in application.yml. In google's developer console configure the app's redirect uri as &lt;code&gt;http://localhost:8080/login/oauth2/code/google&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# @see https://console.developers.google.com/apis/ to create your client credentials&lt;/span&gt;
&lt;span class="s"&gt;logging.level.org.springframework&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;INFO&lt;/span&gt;
&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;oauth2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;registration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;google&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;google&lt;/span&gt;
            &lt;span class="na"&gt;client-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;your-client-id&amp;gt;&amp;gt;&lt;/span&gt;
            &lt;span class="na"&gt;client-secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;your-client-secret&amp;gt;&amp;gt;&lt;/span&gt; 
            &lt;span class="na"&gt;client-authentication-method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;basic&lt;/span&gt;
            &lt;span class="na"&gt;authorization-grant-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;authorization_code&lt;/span&gt;
            &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;openid&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;email&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;profile&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https://www.googleapis.com/auth/tasks.readonly&lt;/span&gt;
        &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;google&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;issuer-uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://accounts.google.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Step 4:
&lt;/h1&gt;

&lt;p&gt;Run the application, goto &lt;a href="http://localhost:8080/me"&gt;http://localhost:8080/me&lt;/a&gt; , complete the login process and you will see this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"authorities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"authority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ROLE_USER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"at_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"28AV0o6xKM8f3UQlljlGuw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10080000000000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"email_verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://accounts.google.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"given_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Syamala"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"picture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://lh6.googleusercontent.com/photo.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"client-id"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"azp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"client-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Syamala Umamaheswaran"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2019-03-24T18:27:19Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"family_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Umamaheswaran"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2019-03-24T17:27:19Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxx@gmail.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"idToken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"userInfo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"authenticated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"authorizedClientRegistrationId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"credentials"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10080000000000000"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Mind Blown:
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/3ohc0Xk1JCH35wvrVu/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/3ohc0Xk1JCH35wvrVu/giphy.gif" alt="Mind Blown"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As much as it blows my mind that without writing any code for security we are able to integrate with an OpenID Connect provider, I needed to know how this is working so easily. The Devil is in the details, Stay tuned for my &lt;a href="https://dev.to/shyamala_u/spring-boot--spring-security-5--oauth2oidc-client---deep-dive-261l"&gt;next blog post&lt;/a&gt; where I explain the behind the scenes and How to access a protected resource and how to refresh tokens automatically. &lt;/p&gt;

&lt;p&gt;Complete Source Code @ &lt;a href="https://github.com/shyamz-22/oidc-spring-security-5"&gt;https://github.com/shyamz-22/oidc-spring-security-5&lt;/a&gt;&lt;/p&gt;

</description>
      <category>oauth2</category>
      <category>java</category>
      <category>springboot</category>
      <category>springsecurity</category>
    </item>
    <item>
      <title>Beyond the login screen - Part II</title>
      <dc:creator>Shyamala</dc:creator>
      <pubDate>Mon, 12 Mar 2018 03:03:45 +0000</pubDate>
      <link>https://dev.to/shyamala_u/beyond-the-login-screen---part-ii--450n</link>
      <guid>https://dev.to/shyamala_u/beyond-the-login-screen---part-ii--450n</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/shyamala_u/beyond-the-login-screen---part-i--29g3"&gt;Previous&lt;/a&gt; post, we reasoned around why transparency while collecting user data is important. In this part let us dig deeper into one of the Open Standards OpenID Connect and see how the standard gives flexibility and transparency around user data management.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Familiar OAuth2 dance
&lt;/h2&gt;

&lt;p&gt;OpenID Connect is built on top of OAuth2,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_FF-J4zQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/q6qv6y3adoe6zwakir2n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_FF-J4zQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/q6qv6y3adoe6zwakir2n.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before jumping on to technicalities of OpenID Connect, let us brush our memory on how we &lt;code&gt;(The Users)&lt;/code&gt; are used to signing in with Google &lt;code&gt;(The Identity Provider)&lt;/code&gt; to Meetup &lt;code&gt;(The App)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Move 1:&lt;/strong&gt; User goes to &lt;code&gt;Meetup&lt;/code&gt; app and clicks on log in with &lt;code&gt;Google&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Move 2:&lt;/strong&gt; &lt;code&gt;Meetup&lt;/code&gt; app then asks &lt;code&gt;Google&lt;/code&gt; to authenticate the user and share the &lt;code&gt;basic profile&lt;/code&gt; of the User&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Move 3:&lt;/strong&gt; &lt;code&gt;Google&lt;/code&gt; then asks the &lt;code&gt;user&lt;/code&gt; to login&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Move 4:&lt;/strong&gt; On successful authentication of the &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;Google&lt;/code&gt; now asks &lt;code&gt;User&lt;/code&gt; if it is ok for them to share their &lt;code&gt;basic profile&lt;/code&gt; with &lt;code&gt;Meetup&lt;/code&gt; and &lt;code&gt;User&lt;/code&gt; &lt;strong&gt;consents&lt;/strong&gt; to &lt;code&gt;Google&lt;/code&gt; to share their data to &lt;code&gt;Meetup&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Move 5:&lt;/strong&gt; &lt;code&gt;Google&lt;/code&gt; now shares an ephemeral Key to &lt;code&gt;Meetup&lt;/code&gt; that can unlock the &lt;code&gt;User's&lt;/code&gt; &lt;strong&gt;consented&lt;/strong&gt; data  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Move 6:&lt;/strong&gt; &lt;code&gt;Meetup&lt;/code&gt; uses the key to get &lt;code&gt;Basic profile&lt;/code&gt; data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What User Data?
&lt;/h2&gt;

&lt;p&gt;At Move 2, &lt;code&gt;Meetup&lt;/code&gt; knows that it only needs &lt;code&gt;basic profile&lt;/code&gt; data like &lt;code&gt;Name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;profile picture&lt;/code&gt; to complete its user profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why User Data?
&lt;/h2&gt;

&lt;p&gt;At Move 4, &lt;code&gt;Google&lt;/code&gt; asks the &lt;code&gt;User's&lt;/code&gt; &lt;strong&gt;consent&lt;/strong&gt; to share the data to &lt;code&gt;Meetup&lt;/code&gt; explaining that this will be used as a profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flexibility and Transparency
&lt;/h2&gt;

&lt;p&gt;Now replace &lt;code&gt;Google&lt;/code&gt; with your own Identity Provider, then you have achieved a new level of flexibility towards transparent data management.&lt;/p&gt;

&lt;p&gt;Assume Meetup needs this new feature, that can show meetups nearby, So now Meetup app needs to know the postal code of the user, all Meetup app now needs to do is request for a new scope &lt;code&gt;pincode&lt;/code&gt; along with &lt;code&gt;basic_profile&lt;/code&gt; and now the Identity Provider would ask consent for the newly added scope to user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behind the scenes
&lt;/h2&gt;

&lt;p&gt;If you know the dance, you can skip to Conclusion. If you want to learn the moves, continue reading&lt;/p&gt;

&lt;h3&gt;
  
  
  Move 1&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;User goes to &lt;code&gt;Meetup&lt;/code&gt; app and clicks on log in with &lt;code&gt;Google&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In order for this to be possible,The App should register itself with the Identity Provider, by providing minimum the Name it wants its Users to see in Move 4 and a url for the Identity Provider to communicate the result to.&lt;/p&gt;

&lt;p&gt;This is &lt;a href="https://openid.net/specs/openid-connect-registration-1_0.html"&gt;Client Registration&lt;/a&gt;. As a result of registration now the App gets an Id and a Secret.&lt;/p&gt;

&lt;h3&gt;
  
  
  Move 2&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Meetup&lt;/code&gt; app then asks &lt;code&gt;Google&lt;/code&gt; to authenticate the user and share the &lt;code&gt;basic profile&lt;/code&gt; of the User&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now The app forms a request with its Id and registered Url and requests for a &lt;code&gt;scope&lt;/code&gt; e.g. Basic Profile using &lt;code&gt;response_type&lt;/code&gt; one of the three flows (Basic, Implicit or Hybrid) to receive the user's data&lt;/p&gt;

&lt;p&gt;This is an &lt;a href="https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest"&gt;Authentication Request&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Move 3&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Google&lt;/code&gt; then asks the &lt;code&gt;user&lt;/code&gt; to login&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At this point The Identity Provider checks for the validity of client (id and registered url), and asks the user to login.&lt;/p&gt;

&lt;p&gt;This is an Identity Provider doing its identity thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Move 4&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Google&lt;/code&gt; now asks &lt;code&gt;User&lt;/code&gt; if it is ok for them to share their &lt;code&gt;basic profile&lt;/code&gt; with &lt;code&gt;Meetup&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On successful authentication, now Identity Provider shows the consent screen to the user describing the&lt;br&gt;
what data the app needs and why.&lt;/p&gt;

&lt;p&gt;This is Getting the user's consent&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How user gets authenticated or consents is out of scope for the OpenID Connect standard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Move 5&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Google&lt;/code&gt; now shares an ephemeral Key to &lt;code&gt;Meetup&lt;/code&gt; that can unlock the &lt;code&gt;User's&lt;/code&gt; &lt;strong&gt;consented&lt;/strong&gt; data  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On successful authentication and receiving user's consent, Now Identity Provider has to share this with the App. How this information is shared to the app is decided by the &lt;code&gt;response_type&lt;/code&gt; value provided by the app in Move 1&lt;/p&gt;

&lt;p&gt;The end result of any flow (Basic, Implicit, Hybrid) is generating one or more of &lt;code&gt;access_token&lt;/code&gt; &lt;code&gt;refresh_token&lt;/code&gt; and &lt;code&gt;id_token&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is &lt;a href="https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse"&gt;successful token response&lt;/a&gt; from &lt;a href="https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint"&gt;token endpoint&lt;/a&gt; for Basic and Hybrid flows or &lt;a href="https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest"&gt;authorize endpoint&lt;/a&gt; for Implicit flow.&lt;/p&gt;

&lt;h4&gt;
  
  
  Choosing flows
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ItLK3-hF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/kv19qcqfwahq3q8nwmpy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ItLK3-hF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/kv19qcqfwahq3q8nwmpy.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Move 6&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Meetup&lt;/code&gt; uses the key to get &lt;code&gt;Basic profile&lt;/code&gt; data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now the App can use this short lived &lt;code&gt;access_token&lt;/code&gt; to get information about user.&lt;/p&gt;

&lt;p&gt;This is &lt;a href="https://openid.net/specs/openid-connect-core-1_0.html#UserInfo"&gt;User Info endpoint&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Questions we need to ask,
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PfBwGL1C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/u60yc2chlby6tsiphflz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PfBwGL1C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/u60yc2chlby6tsiphflz.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So Identity Provider can validate App and User,  But &lt;strong&gt;How does the App know&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is indeed &lt;code&gt;The Identity Provider&lt;/code&gt; that authenticated the user and responded to it?&lt;/li&gt;
&lt;li&gt;How long since the user was last authenticated? Given all the SSO convenience going on?&lt;/li&gt;
&lt;li&gt;With which level of security (1FA or 2FA) the user was authenticated? Given the App deals with of course money, we need to have iron clad security don't we?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Answer is &lt;code&gt;id_token&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is what makes OpenID Connect different about OAuth2, It provides an Identity layer on top of the OAuth2 standard. It gives feedback to the App about the authenticated user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ickk7tOl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/75gty0dmvj8zyafmb1kj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ickk7tOl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/75gty0dmvj8zyafmb1kj.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can think of &lt;a href="https://openid.net/specs/openid-connect-core-1_0.html#IDToken"&gt;id_token&lt;/a&gt; as digital secure identity card of the user. It is a &lt;a href="https://jwt.io"&gt;JWT&lt;/a&gt; token signed by the &lt;code&gt;Identity Provider's&lt;/code&gt; private key. The Public key is shared through &lt;a href="https://tools.ietf.org/html/rfc7517"&gt;keys endpoint&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By abstracting away authentication and authorization data of the user we can achieve flexibility on how and when to collect the data there by building up a User Profile. The Identity Provider can define scopes, that allows an App to not only get user data but also to write user data through Scope Management.&lt;/p&gt;

&lt;p&gt;And the standard gives you enough trust and confidence that the data is being exchanged between the App, User and Identity Provider in a secure and transparent manner.&lt;/p&gt;

</description>
      <category>security</category>
      <category>transparency</category>
      <category>openidconnect</category>
      <category>dataprivacy</category>
    </item>
    <item>
      <title>Beyond the login screen - Part I</title>
      <dc:creator>Shyamala</dc:creator>
      <pubDate>Fri, 09 Mar 2018 11:49:15 +0000</pubDate>
      <link>https://dev.to/shyamala_u/beyond-the-login-screen---part-i--29g3</link>
      <guid>https://dev.to/shyamala_u/beyond-the-login-screen---part-i--29g3</guid>
      <description>&lt;p&gt;Recently I came across this tweet,&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--HdSlcxba--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/803507008842256385/7viWqOvs_normal.jpg" alt="██ Oliver █ L██████ profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        ██ Oliver █ L██████
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @eey0re
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      If you're collecting personal data, "how should I protect this?" is actually your third question.&lt;br&gt;&lt;br&gt;"Should I collect this?" is only the second question.&lt;br&gt;&lt;br&gt;The first question is "what would the worst people do if they got hold of this?"
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      03:49 AM - 04 Mar 2018
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=970144255745212416" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=970144255745212416" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=970144255745212416" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;This tweet got me thinking, Personal data even how trivial it is can be dangerous in the hands of a wrong person. But can we stop collecting data about the user? Can we say with certainty that the system we are building is impenetrable? &lt;/p&gt;

&lt;p&gt;We as part of a product team has responsibility to be transparent to our users about &lt;strong&gt;What&lt;/strong&gt; data of the user our product needs and &lt;strong&gt;Why&lt;/strong&gt; the user has to share it, So the user is conscious of the data being shared and there by implications.&lt;/p&gt;

&lt;p&gt;Let us not be very greedy to ask for all data at once from the user. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--058NFlED--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/izfgmkdmn4w11rtz0ocj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--058NFlED--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/izfgmkdmn4w11rtz0ocj.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think we can also apply lean principle in collecting the data from the user, by asking for user's data only when we need them and of course explaining the Whats and Whys.&lt;/p&gt;

&lt;p&gt;We can craft a unique solution for each of our suite of products ourselves or we can abstract away the method of collecting some of the most common user data that we collect in a safe, secure and trusted way. &lt;/p&gt;

&lt;p&gt;There are a number of Open standards for exchanging the authentication and authorization data between users and services/products. Most popular among them are SAML, OAuth2 and OpenId Connect. These standards gives us the trust that a user data is exchanged in a secure manner that is transparent to the user.&lt;/p&gt;

&lt;p&gt;Let us bring a change in thought process that authentication or authorization is not mere a login screen. Let us think about these aspects early in our product design phase.&lt;/p&gt;

&lt;p&gt;So may be it is time for us to think about these Federated Authentication and Authorization standards, that gives users the &lt;strong&gt;right to grant and revoke access to their data&lt;/strong&gt; at any point?&lt;/p&gt;

&lt;p&gt;In the next part , I will go deeper in to one of these Open Standards OpenID Connect..&lt;/p&gt;

</description>
      <category>security</category>
      <category>transparency</category>
      <category>openidconnect</category>
      <category>dataprivacy</category>
    </item>
    <item>
      <title>Hi, I'm Shyamala</title>
      <dc:creator>Shyamala</dc:creator>
      <pubDate>Tue, 14 Mar 2017 08:00:41 +0000</pubDate>
      <link>https://dev.to/shyamala_u/hi-im-shyamala</link>
      <guid>https://dev.to/shyamala_u/hi-im-shyamala</guid>
      <description>&lt;p&gt;I have been coding for 6 years.&lt;/p&gt;

&lt;p&gt;You can find me on Twitter as &lt;a href="https://twitter.com/shyamala_u" rel="noopener noreferrer"&gt;@shyamala_u&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I mostly program in these language: Java.&lt;/p&gt;

&lt;p&gt;I am currently learning more about functional programming &lt;/p&gt;

&lt;p&gt;Nice to meet you.&lt;/p&gt;

</description>
      <category>introduction</category>
    </item>
  </channel>
</rss>
