<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title></title>
  <subtitle></subtitle>
  <id>https://www.endpointdev.com/blog/tags/security/</id>
  <link href="https://www.endpointdev.com/blog/tags/security/"/>
  <link href="https://www.endpointdev.com/blog/tags/security/" rel="self"/>
  <updated>2026-05-11T00:00:00+00:00</updated>
  <author>
    <name>End Point Dev</name>
  </author>
  
    <entry>
      <title>Introducing EP Audit</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2026/05/introducing-ep-audit/"/>
      <id>https://www.endpointdev.com/blog/2026/05/introducing-ep-audit/</id>
      <published>2026-05-11T00:00:00+00:00</published>
      <author>
        <name>Dan Briones</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2026/05/introducing-ep-audit/cover.webp&#34; alt=&#34;A panoramic view from a mountain overlooks a vast valley stretching to the horizon under a stormy sky.&#34;&gt;&lt;br&gt;
Photo by Bimal Gharti Magar, 2026.&lt;/p&gt;
&lt;p&gt;In the complex landscape of cloud security, staying compliant and prepared for audits is important. Enter EP Audit, a compliance-focused audit process, AI-powered but led by senior engineers. It&amp;rsquo;s designed for Azure, AWS, and Google Cloud setups. EP Audit emerged from necessity: When a financial services client required an Azure security audit, we at End Point built a solution that not only met their immediate needs, but also evolved into an internal tool we continue to refine. Today, it serves as a key part of our multi-cloud security audit framework and is tested against our own cloud accounts before being used in client engagements.&lt;/p&gt;
&lt;h4 id=&#34;the-problem&#34;&gt;The Problem&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Cloud environments are dynamic&lt;/strong&gt;: New projects, personnel changes, and vendor updates can significantly alter configurations over time. As environments evolve, the security posture originally reviewed and approved by your engineers may no longer fully match what is running in production. Unused resources, overly permissive access, and orphaned infrastructure can also accumulate over time, increasing both risk and monthly costs. This often goes unnoticed until an audit, customer review, or security incident forces a closer look.&lt;/p&gt;
&lt;p&gt;Modern compliance frameworks—SOC 2, ISO 27001, HIPAA, PCI DSS—require regular, documented security reviews. Yet many organizations still struggle to provide consistent documentation with timestamps, severity tracking, and proof of remediation. EP Audit helps address this gap by generating a structured audit trail throughout the review process.&lt;/p&gt;
&lt;h4 id=&#34;what-ep-audit-looks-like&#34;&gt;What EP Audit Looks Like&lt;/h4&gt;
&lt;p&gt;An EP Audit engagement is structured as a scheduled review cycle with two phases: a Base scan to establish a security snapshot, and a Final scan performed after remediation work to verify fixes. Both phases use a command-line runner with a read-only audit identity, ensuring no changes are made during the audit itself. Each Base scan produces nine deliverables designed to provide both high-level summaries and detailed technical findings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Executive Summary:&lt;/strong&gt; Severity-ranked audit findings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client Report:&lt;/strong&gt; Full narrative with risk scorecard and roadmap.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structured Findings Report:&lt;/strong&gt; Engineer-friendly findings by category.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pre-filled Hardening Checklist:&lt;/strong&gt; CIS-aligned checklist with auto-populated FAIL statuses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fillable Hardening Checklist:&lt;/strong&gt; A remediation tool for your team.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network Diagram:&lt;/strong&gt; Auto-generated deployment topology.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Findings Workbook:&lt;/strong&gt; Excel file with color-coded severity tabs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remediation Plan:&lt;/strong&gt; Phased plan with affected resources.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Partner Remediation Identity Setup:&lt;/strong&gt; Guide for temporary write scope during remediation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These deliverables are standardized across Azure, AWS, and GCP, helping simplify multi-cloud audit reviews and recurring engagements.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2026/05/introducing-ep-audit/epaudit-findings-category.webp&#34; alt=&#34;Findings by severity and category&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sample summary of findings by severity and category on EP Audit.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Every finding in EP Audit is mapped to controls from recognized security frameworks. For Azure, we reference the CIS Microsoft Azure Foundations Benchmark and the Microsoft Cloud Security Benchmark. AWS findings align with the CIS AWS Foundations Benchmark, AWS Foundational Security Best Practices, and related guidance. GCP findings follow the CIS GCP Foundation Benchmark and the Google Cloud Architecture Framework Security Pillar. This helps keep audit findings grounded in established industry standards rather than proprietary scoring systems or arbitrary recommendations.&lt;/p&gt;
&lt;h4 id=&#34;a-service-not-just-a-tool&#34;&gt;A Service, Not Just a Tool&lt;/h4&gt;
&lt;p&gt;There are already plenty of cloud configuration scanners available. What matters is how the findings are reviewed, prioritized, and applied in real environments.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Senior Engineering Review:&lt;/strong&gt; Our engineers provide context and actionable recommendations for each finding, bridging the gap between automated results and practical solutions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;End Point-Specific Enhancements:&lt;/strong&gt; We include checks beyond published frameworks, such as billing data analysis for orphaned resources and third-party security tool integration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Internal Validation:&lt;/strong&gt; Every framework change is tested on End Point&amp;rsquo;s own cloud accounts before client deployment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A single audit provides a snapshot in time, but recurring audits make it easier to identify trends, validate remediation progress, and detect configuration drift over time. EP Audit includes year-over-year comparison reporting to help highlight both improvements and regressions. In practice, this helps support compliance reviews, procurement questionnaires, and ongoing security review processes while providing better visibility into how environments evolve over time.&lt;/p&gt;
&lt;h4 id=&#34;phased-remediation&#34;&gt;Phased Remediation&lt;/h4&gt;
&lt;p&gt;EP Audit generates remediation scripts and recommendations organized by severity:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;P1 (0–7 days):&lt;/strong&gt; Critical issues like exposed management ports.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P2 (30 days):&lt;/strong&gt; High-priority issues such as encryption gaps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P3 (90 days):&lt;/strong&gt; Medium-priority defense-in-depth weaknesses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P4 (Maintenance):&lt;/strong&gt; Low-priority configuration hygiene.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Scripts default to dry-run mode, allowing proposed changes to be reviewed before execution. Our team can assist with remediation directly, or clients can implement the recommendations independently using the provided guidance.&lt;/p&gt;
&lt;p&gt;Over multiple engagements, organizations begin building a clearer picture of how their cloud environments evolve over time. Recurring audits make it easier to track remediation progress, identify recurring issues, and detect configuration drift before it becomes a larger problem. The resulting documentation can also help support compliance reviews, customer security questionnaires, cyber insurance requirements, internal security tracking, and reduce monthly costs.&lt;/p&gt;
&lt;h4 id=&#34;working-with-end-point&#34;&gt;Working with End Point&lt;/h4&gt;
&lt;p&gt;If your organization operates workloads in Azure, AWS, or Google Cloud and needs a structured, standards-aligned cloud security review process, feel free to &lt;a href=&#34;/contact/&#34;&gt;contact us&lt;/a&gt;. If you are working with us already, you can talk with your representative to find out more.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Risks of Paper Checks for Secure Transactions</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2025/08/risks-of-paper-checks-for-secure-transactions/"/>
      <id>https://www.endpointdev.com/blog/2025/08/risks-of-paper-checks-for-secure-transactions/</id>
      <published>2025-08-18T00:00:00+00:00</published>
      <author>
        <name>Joanne Tipton</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2025/08/risks-of-paper-checks-for-secure-transactions/rail-barrier-opening.webp&#34; alt=&#34;A rail barrier is in the process of opening, creating a strong line from the bottom right to the top left of the image. Behind is a train on the tracks in from of a deep gold sunrise&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Seth Jensen, 2025 --&gt;
&lt;p&gt;Fraud is on the rise, and paper checks are one of the easiest targets. In the not-so-distant past, a grandparent could tuck a crisp $5 bill into an envelope and trust it would arrive. Today, even checks sent to pay your bills are being stolen, altered, and cashed by criminals before they reach their destination.&lt;/p&gt;
&lt;p&gt;So, the practice of sending a paper check in the mail to reach a creditor should be stopped. According to the US Financial Crimes Enforcement Network (FinCEN), there were &lt;strong&gt;over 660,000 check fraud reports in 2024&lt;/strong&gt;. This number has stayed in the 600,000s every year since 2022, when it nearly doubled from the previous year.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; The U.S. Postal Service has seen similar increases in mail theft, with criminals targeting collection boxes and even robbing postal carriers for paper checks.&lt;/p&gt;
&lt;p&gt;Fraudsters have many ways to manipulate checks. Intercepting checks that are not in a secured location by simply paying attention to where mail is lying around is at the top of the list. Once they get a check in hand, they can “wash” original amounts and payees and replace them with higher numbers and their own names.&lt;/p&gt;
&lt;p&gt;A recent incident at our company showed how risky paper checks can be. A check mailed to End Point by one of our long time customers was intercepted and fraudulently cashed by someone else. We contacted the customer multiple times to collect on the open invoice. They were sure they had paid the invoice. When they sent us a photo of the front and back of the cancelled check, we could see that an unknown person had endorsed the check and cashed it! In some cases, the sender is left holding the financial loss with no way to recover the funds.&lt;/p&gt;
&lt;p&gt;At End Point, we don’t cash customer checks in person — all payments are deposited directly into our business accounts. While this adds some protection, it’s useless if the check never reaches us.&lt;/p&gt;
&lt;p&gt;The safest way to pay today is electronically. &lt;strong&gt;We strongly encourage customers to switch to secure electronic methods&lt;/strong&gt; such as ACH transfers or direct deposits. If a check is unavoidable, send it via Certified Mail with tracking. Protecting your payment protects both your business and ours.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Data aggregated from &lt;a href=&#34;https://www.fincen.gov/reports/sar-stats&#34;&gt;https://www.fincen.gov/reports/sar-stats&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

      </content>
    </entry>
  
    <entry>
      <title>How to Analyze Application Logs and Extract Actionable Insights</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2025/02/how-to-analyze-application-logs-and-extract-actionable-insights/"/>
      <id>https://www.endpointdev.com/blog/2025/02/how-to-analyze-application-logs-and-extract-actionable-insights/</id>
      <published>2025-02-28T00:00:00+00:00</published>
      <author>
        <name>Edgar Mlowe</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2025/02/how-to-analyze-application-logs-and-extract-actionable-insights/logs.webp&#34; alt=&#34;A side-on view of a large pile of wooden logs&#34;&gt;&lt;/p&gt;
&lt;p&gt;Photo by &lt;a href=&#34;https://unsplash.com/@tcdinger&#34;&gt;Timo C. Dinger&lt;/a&gt; on &lt;a href=&#34;https://unsplash.com/photos/brown-and-black-wood-logs-Oo3L5fL1lBU&#34;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Logs often accumulate unnoticed—until something breaks. Then, they suddenly become vital tools for diagnosing issues. By combining just a few command-line techniques, you can quickly spot recurring problems, identify suspicious activity, and strengthen your application’s defenses.&lt;/p&gt;
&lt;p&gt;Below is a sample error log we’ll reference in the examples:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plaintext&#34; data-lang=&#34;plaintext&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2025-01-01 12:34:56] [client 192.168.1.1:12345] PHP Notice: Undefined variable $foo in /var/www/html/index.php on line 45 | referer: http://example.com/index.php
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2025-01-01 12:34:56] [client 192.168.1.1:12345] PHP Notice: Undefined variable $foo in /var/www/html/index.php on line 45 | referer: http://example.com/index.php
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2025-01-01 12:34:57] [client 192.168.1.1:12345] PHP Notice: Undefined variable $foo in /var/www/html/index.php on line 45 | referer: http://example.com/index.php
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2025-01-01 12:35:00] [client 10.0.0.2:6789] PHP Warning: Division by zero in /var/www/html/script.php on line 23 | referer: http://example.com/script.php
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2025-01-01 13:35:01] [client 10.0.0.2:6789] PHP Warning: Division by zero in /var/www/html/script.php on line 23 | referer: http://example.com/script.php
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2025-01-01 13:36:10] [client 192.168.1.1:12345] PHP Fatal error: Call to undefined function baz() in /var/www/html/lib.php on line 78 | referer: http://example.com/index.php
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2025-01-01 14:36:11] [client 172.16.0.5:54321] PHP Fatal error: Call to undefined function baz() in /var/www/html/lib.php on line 78 | referer: http://example.com/index.php&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;a-note-on-commands&#34;&gt;A Note on Commands&lt;/h3&gt;
&lt;p&gt;Throughout this blog, you’ll notice repeated use of commands like &lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;sed&lt;/code&gt;, &lt;code&gt;uniq&lt;/code&gt;, and &lt;code&gt;sort&lt;/code&gt;. These tools are indispensable for log analysis, allowing you to filter, transform, and summarize data efficiently. Here are some tips for learning these tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;man &amp;lt;command&amp;gt;&lt;/code&gt; (e.g., &lt;code&gt;man awk&lt;/code&gt;) to access the manual and dive deeper into a specific command&lt;/li&gt;
&lt;li&gt;Experiment with small examples to understand how commands like &lt;code&gt;awk&lt;/code&gt; process patterns or how &lt;code&gt;grep&lt;/code&gt; extracts data&lt;/li&gt;
&lt;li&gt;For a broader look at how these commands enhance productivity, check out my blog post: &lt;a href=&#34;/blog/2024/06/practical-linux-comandline-tips/&#34;&gt;Practical Linux Command Line Tips for Productivity and Efficiency&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These commands are not just for log analysis—they’re powerful for any data manipulation task you encounter. With practice, they can become essential to your workflow.&lt;/p&gt;
&lt;h3 id=&#34;1-debugging-application-errors&#34;&gt;1. Debugging Application Errors&lt;/h3&gt;
&lt;h4 id=&#34;11-summarize-frequent-issues&#34;&gt;1.1 Summarize Frequent Issues&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sed -E &amp;#39;s/^\[.*\] //&amp;#39; error.log | sort | uniq -c | sort -nr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Remove timestamps/​client&amp;rsquo;s IP, then sort and count duplicates&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outcome&lt;/strong&gt;: A quick snapshot of which errors appear most often&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plaintext&#34; data-lang=&#34;plaintext&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;3 PHP Notice: Undefined variable $foo ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2 PHP Warning: Division by zero ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2 PHP Fatal error: Call to undefined function baz() ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;12-categorize-errors-by-type&#34;&gt;1.2 Categorize Errors by Type&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;grep -oE &amp;#39;PHP [^:]+&amp;#39; error.log | sort | uniq -c | sort -nr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Extract error types (e.g., Notice, Warning, Fatal error)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outcome&lt;/strong&gt;: Know whether notices, warnings, or fatal errors dominate&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plaintext&#34; data-lang=&#34;plaintext&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;3 Notice
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2 Warning
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2 Fatal error&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;13-find-problematic-scripts&#34;&gt;1.3 Find Problematic Scripts&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;awk -F&amp;#39;in &amp;#39; &amp;#39;{print $2}&amp;#39; error.log | awk &amp;#39;{print $1}&amp;#39; | sort | uniq -c | sort -nr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Identify which files generate the most errors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outcome&lt;/strong&gt;: Pinpoint error hotspots for focused debugging&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plaintext&#34; data-lang=&#34;plaintext&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;3 /var/www/html/index.php
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2 /var/www/html/script.php
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2 /var/www/html/lib.php&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;2-monitoring-system-behavior&#34;&gt;2. Monitoring System Behavior&lt;/h3&gt;
&lt;h4 id=&#34;21-track-problematic-ips&#34;&gt;2.1 Track Problematic IPs&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;awk -F&amp;#39;\[client &amp;#39; &amp;#39;{print $2}&amp;#39; error.log | awk -F&amp;#39;:&amp;#39; &amp;#39;{print $1}&amp;#39; | sort | uniq -c | sort -nr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Count how many errors each IP triggers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outcome&lt;/strong&gt;: Identify suspicious or high-traffic IPs&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plaintext&#34; data-lang=&#34;plaintext&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;4 192.168.1.1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2 10.0.0.2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1 172.16.0.5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;22-spot-high-error-time-periods&#34;&gt;2.2 Spot High-Error Time Periods&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;awk -F&amp;#39;[][]&amp;#39; &amp;#39;{split($2, time, &amp;#34;:&amp;#34;); print $1, time[1]&amp;#34;:&amp;#34;time[2]&amp;#34;:00&amp;#34;}&amp;#39; error.log | sort | uniq -c | sort -nr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Group errors by minute, revealing spikes or trends&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outcome&lt;/strong&gt;: Correlate error surges with deployments or traffic peaks&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plaintext&#34; data-lang=&#34;plaintext&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;3 2025-01-01 12:34:00
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1 2025-01-01 14:36:00
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1 2025-01-01 13:36:00
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;23-analyze-ipreferer-pairs&#34;&gt;2.3 Analyze IP–Referer Pairs&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;grep &amp;#34;referer:&amp;#34; error.log | awk -F&amp;#39;\\[client &amp;#39; &amp;#39;{print $2}&amp;#39; | awk -F&amp;#39;:| referer: &amp;#39; &amp;#39;{print $1, $3}&amp;#39; | sort | uniq -c | sort -nr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Match IP addresses with the URLs they refer from&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outcome&lt;/strong&gt;: Detect repeat offenders or malicious traffic patterns&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plaintext&#34; data-lang=&#34;plaintext&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;3 192.168.1.1 Undefined variable $foo ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2 10.0.0.2 Division by zero ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1 172.16.0.5 Call to undefined function baz() ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;3-enhancing-security&#34;&gt;3. Enhancing Security&lt;/h3&gt;
&lt;h4 id=&#34;31-watch-sensitive-urls&#34;&gt;3.1 Watch Sensitive URLs&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;grep -E &amp;#34;admin|login&amp;#34; script_error_frequency.txt | sort -nr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Flag frequent hits on admin or login pages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outcome&lt;/strong&gt;: Gauge whether sensitive endpoints are under attack&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plaintext&#34; data-lang=&#34;plaintext&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2 /var/www/html/login.php
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2 /var/www/html/admin.php&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;32-pinpoint-critical-times&#34;&gt;3.2 Pinpoint Critical Times&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;grep -E &amp;#34;admin|login&amp;#34; error.log | awk -F&amp;#39;[][]&amp;#39; &amp;#39;{split($2, time, &amp;#34;:&amp;#34;); print $1, time[1]&amp;#34;:&amp;#34;time[2]&amp;#34;:00&amp;#34;}&amp;#39; | sort | uniq -c | sort -nr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Identify specific time windows for sensitive access&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outcome&lt;/strong&gt;: Cross-reference suspicious activity with your security logs&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plaintext&#34; data-lang=&#34;plaintext&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2 2025-01-01 12:34:00
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1 2025-01-01 13:35:00
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1 2025-01-01 12:35:00&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;These commands demonstrate how quickly you can glean insights from logs. With a little creativity, you can expand them to track response times, detect performance bottlenecks, and safeguard critical endpoints. Whether you’re tackling PHP errors or any other type of log data, the same principles apply: filter, sort, count, and investigate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Curious to learn more?&lt;/strong&gt; Combine these strategies with automation tools, integrate them into CI/CD pipelines, or hook them up to visual dashboards. Your logs will become a gold mine of actionable information.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Streamlining SELinux Policies: From Policy Modules to Modules and Silent SELinux Denials</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2024/08/streamlining-selinux-policies/"/>
      <id>https://www.endpointdev.com/blog/2024/08/streamlining-selinux-policies/</id>
      <published>2024-08-26T00:00:00+00:00</published>
      <author>
        <name>Bharathi Ponnusamy</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2024/08/streamlining-selinux-policies/banner.webp&#34; alt=&#34;Cars drive across a road bridge over light blue water. Above are several large hotels, and white clouds against a light blue sky.&#34;&gt;&lt;/p&gt;
&lt;!-- photo by Bharathi Ponnusamy --&gt;
&lt;h3 id=&#34;introduction&#34;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;SELinux (Security-Enhanced Linux) provides a robust security layer that enforces security policies to control system access. When dealing with SELinux, you often encounter the terms &amp;ldquo;policy_module&amp;rdquo; and &amp;ldquo;module&amp;rdquo;. Understanding the difference between these and knowing how to convert between them is crucial for efficient system administration.&lt;/p&gt;
&lt;h3 id=&#34;what-is-a-policy_module&#34;&gt;What is a policy_module?&lt;/h3&gt;
&lt;p&gt;A policy_module in SELinux is a type of module used to define additional policies. These modules encapsulate specific security rules that can be loaded into the SELinux policy to grant or restrict permissions. Policy modules are particularly useful for adding or modifying policies without changing the base SELinux policy.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;policy_module(my_policy, 1.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    type my_app_t;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#============= my_app_t ==============
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;allow my_app_t my_log_t:file read;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;what-is-a-module&#34;&gt;What is a module?&lt;/h3&gt;
&lt;p&gt;A module in SELinux is a compiled version of a policy module. The compilation process translates the high-level policy rules into a binary format that SELinux can enforce. Modules are loaded into the SELinux policy store to extend or modify the active policy.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;module my_module 1.0;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    type my_app_t;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    class file { read write };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#============= my_app_t ===============
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;allow my_app_t my_log_t:file { read write };&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;converting-policy_module-to-module&#34;&gt;Converting policy_module to module&lt;/h3&gt;
&lt;p&gt;In many scenarios, it&amp;rsquo;s necessary to convert a policy_module to a module. This conversion ensures compatibility and avoids the need for additional utilities such as selinux-polgenui.&lt;/p&gt;
&lt;p&gt;Here’s how you can do it:&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;.te&lt;/code&gt; file. This file contains the policy rules.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;policy_module(my_policy, 1.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    type my_app_t;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#============= my_app_t ==============
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;allow my_app_t my_log_t:file read;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Compile the policy module. Use the &lt;code&gt;checkmodule&lt;/code&gt; and &lt;code&gt;semodule_package&lt;/code&gt; tools to compile the policy module into a module.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;checkmodule -M -m -o my_policy.mod my_policy.te
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;semodule_package -o my_policy.pp -m my_policy.mod&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Load the module. Use &lt;code&gt;semodule&lt;/code&gt; to load the compiled module into SELinux (using &lt;code&gt;-i&lt;/code&gt; for &amp;ldquo;install&amp;rdquo;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;semodule -i my_policy.pp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;the-silent-blocks-of-selinux&#34;&gt;The silent blocks of SELinux&lt;/h3&gt;
&lt;p&gt;Sometimes, SELinux can quietly block your software, leading to silent failures. This can be particularly tricky because SELinux usually logs issues in &lt;code&gt;/var/log/audit/auditd.log&lt;/code&gt; or &lt;code&gt;/var/log/messages&lt;/code&gt;. However, if a permission or property has the dontaudit setting applied, it won&amp;rsquo;t be logged, making it harder to troubleshoot.&lt;/p&gt;
&lt;p&gt;In many cases, system administrators expect SELinux to be vocal about problems. When SELinux doesn&amp;rsquo;t log an issue due to dontaudit, it can quietly block your software without any visible errors.&lt;/p&gt;
&lt;h3 id=&#34;troubleshooting-silent-blocks&#34;&gt;Troubleshooting silent blocks&lt;/h3&gt;
&lt;p&gt;To diagnose whether SELinux is causing a problem, you can temporarily set SELinux to &amp;ldquo;permissive&amp;rdquo; mode:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;setenforce 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If your script works in permissive mode and stops when you switch back to enforcing mode (with &lt;code&gt;setenforce 1&lt;/code&gt;), SELinux is likely the culprit.&lt;/p&gt;
&lt;p&gt;The next step is to temporarily disable dontaudit settings by building all policy modules with the &lt;code&gt;-D&lt;/code&gt;/&lt;code&gt;--disable_dontaudit&lt;/code&gt; option.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;semodule -DB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This command will allow you to collect all the failing rules related to your problem. You can then use these logs to create a custom SELinux module.&lt;/p&gt;
&lt;p&gt;After you&amp;rsquo;ve finished creating your module, re-enable the dontaudit silencing feature by rebuilding policy modules:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;semodule -B&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This prevents your logs from becoming cluttered with too many messages.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Secure Your Dockerized Nginx with Let&#39;s Encrypt SSL Certificates</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2024/06/docker-nginx-acme/"/>
      <id>https://www.endpointdev.com/blog/2024/06/docker-nginx-acme/</id>
      <published>2024-06-27T00:00:00+00:00</published>
      <author>
        <name>Jeffry Johar</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2024/06/docker-nginx-acme/lock.webp&#34; alt=&#34;A rusted lock at at an old wooden door&#34;&gt;&lt;/p&gt;
&lt;p&gt;Photo by &lt;a href=&#34;https://www.pexels.com/@animesh-srivastava-3019173/&#34;&gt;Animesh Srivastava&lt;/a&gt; from &lt;a href=&#34;https://www.pexels.com/photo/close-up-of-an-old-and-rusty-padlock-8497499/&#34;&gt;Pexels&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this tutorial I will demonstrate how to secure Nginx on Docker using HTTPS, leveraging free certificates from Let’s Encrypt. Let’s Encrypt certificates provide trusted and secure encryption at no cost, although they require renewal every 90 days. Fortunately, this renewal process can be automated with various tools. We will use &lt;a href=&#34;https://github.com/acmesh-official/acme.sh&#34;&gt;acme.sh&lt;/a&gt;, a versatile Bash script compatible with major platforms. The tutorial will guide you through obtaining Let’s Encrypt certificates on the host system and mounting them as a volume in the Nginx container. Please ensure the following prerequisites are met before proceeding:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Working Docker Engine&lt;/li&gt;
&lt;li&gt;Working domain name&lt;/li&gt;
&lt;li&gt;A host with ports 80 and 443 that is accessible from the internet&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;1-domain-validation&#34;&gt;1. Domain validation&lt;/h3&gt;
&lt;p&gt;First, we need an Nginx instance on Docker that will expose port 80 and have a directory on the host mounted for its web root. This is required by acme.sh for its file-based domain validation. I’ve prepared a Docker Compose file (&lt;code&gt;docker-compose.yml&lt;/code&gt;) and an Nginx configuration file (&lt;code&gt;nginx.conf&lt;/code&gt;) for this purpose. Git clone the following repository and change into the directory&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git clone https://github.com/aburayyanjeffry/nginx-docker-acme.git
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd nginx-docker-acme&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code&gt;nginx.conf&lt;/code&gt;, please note that the lines exposing port 443 and adding SSL certificates are commented because we don’t have the certificate yet. Listening on port 444 and trying to enable SSL without a valid certificate will cause errors and prevent Nginx from starting up. Port 443 is also commented out in &lt;code&gt;docker-compose.yml&lt;/code&gt; because Nginx is not exposing port 443 yet, as is the line copying the &lt;code&gt;ssh&lt;/code&gt; folder. Let&amp;rsquo;s start Nginx using Docker Compose:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker compose up -d&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;2-get-the-acmesh&#34;&gt;2. Get the acme.sh&lt;/h3&gt;
&lt;p&gt;The following command will install acme.sh in the current user&amp;rsquo;s home directory. Make sure to use your email in the command.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl https://get.acme.sh | sh -s email=jeffry@email.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Log out and log in again to enable the acme.sh alias for the user. If acme.sh is not working, it&amp;rsquo;s probably because you missed this step. If the alias is not enabled, the acme.sh script is not defined.&lt;/p&gt;
&lt;h3 id=&#34;3-set-the-ca&#34;&gt;3. Set the CA&lt;/h3&gt;
&lt;p&gt;Set Let&amp;rsquo;s Encrypt as the default Certificate Authority.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;acme.sh --set-default-ca --server letsencrypt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;4-issue-the-certificate&#34;&gt;4. Issue the certificate&lt;/h3&gt;
&lt;p&gt;Now we&amp;rsquo;ll proceed with issuing the certificate, a step that involves domain validation. Upon successful validation, the certificate will be issued.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;acme.sh --issue -d jeffry.temphost.net -w /home/jeffry/nginx-docker-acme/nginx/html --keylength 4096&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Make sure to replace &lt;code&gt;jeffry.temphost.net&lt;/code&gt; with your domain and &lt;code&gt;/home/jeffry/nginx-docker-acme/nginx/html&lt;/code&gt; with your web root directory. Note that &lt;code&gt;/home/jeffry&lt;/code&gt; is the directory where the code was downloaded, making it the working directory. Be sure to update it to reflect your own working directory.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;-d&lt;/code&gt; flag specifies the domain, while &lt;code&gt;-w&lt;/code&gt; designates the web root directory. This directory will be mounted as Nginx&amp;rsquo;s web root in Docker, where acme.sh will write the validation file.&lt;/p&gt;
&lt;p&gt;We need to know the container name in order to restart it. The container name is the string in the last column from the &lt;code&gt;docker ps&lt;/code&gt; output. In this example the container name is &lt;code&gt;nginx-docker-acme-web-1&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[jeffry@docker ~]$ docker ps
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                               NAMES
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;754c055d5b5e   nginx     &amp;#34;/docker-entrypoint....&amp;#34;   16 minutes ago   Up 16 minutes   0.0.0.0:80-&amp;gt;80/tcp, :::80-&amp;gt;80/tcp   nginx-docker-acme-web-1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;5-install-the-certificate&#34;&gt;5. Install the certificate&lt;/h3&gt;
&lt;p&gt;Uncomment the port 443 and SSL lines in &lt;code&gt;nginx.conf&lt;/code&gt; and &lt;code&gt;docker-compose.yaml&lt;/code&gt;. This will enable port 443 for Nginx and will make Docker expose it to the host after a restart it through Docker Compose later.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker-compose.yaml&lt;/code&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  web:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    image: nginx
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ports:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &amp;#34;80:80&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &amp;#34;443:443&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    volumes:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - ./nginx/html:/usr/share/nginx/html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - ./nginx/ssl:/etc/nginx/ssl&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;nginx/nginx.conf&lt;/code&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;server {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    listen 80;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    listen 443 ssl;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server_name localhost;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ssl_certificate /etc/nginx/ssl/cert.pem;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ssl_certificate_key /etc/nginx/ssl/key.pem;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    location / {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        root /usr/share/nginx/html;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        index index.html;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Create the &lt;code&gt;ssl&lt;/code&gt; directory for Nginx and install the certificates there. This directory will be mounted by Nginx in Docker. Use the container name obtained from the previous steps as the value for the &lt;code&gt;--reloadcmd&lt;/code&gt; switch. This is crucial because it will be used to restart Nginx when certificates are updated.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir /home/jeffry/nginx-docker-acme/nginx/ssl
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;acme.sh --install-cert -d jeffry.temphost.net --key-file /home/jeffry/nginx-docker-acme/nginx/ssl/key.pem --fullchain-file /home/jeffry/nginx-docker-acme/nginx/ssl/cert.pem --reloadcmd &amp;#34;docker exec nginx-docker-acme-web-1 nginx -s reload&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;6-restart-the-nginx-container&#34;&gt;6. Restart the Nginx container&lt;/h3&gt;
&lt;p&gt;Refresh the Nginx container by stopping and starting it from Docker Compose. You should be able to access Nginx over HTTPS with the Let’s Encrypt certificates.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker compose down
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker compose up -d&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&#34;/blog/2024/06/docker-nginx-acme/browser.webp&#34; alt=&#34;A browser showing Let&amp;rsquo;s Encrypt cert&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;7-certificate-renewal-mechanism&#34;&gt;7. Certificate Renewal Mechanism&lt;/h3&gt;
&lt;p&gt;The certificate will be automatically renewed by the cron job which was installed by acme.sh. You can verify the cron job by running &lt;code&gt;crontab -l&lt;/code&gt; .&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[jeffry@docker ~]$ crontab -l
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;13 7 * * * &amp;#34;/home/jeffry/.acme.sh&amp;#34;/acme.sh --cron --home &amp;#34;/home/jeffry/.acme.sh&amp;#34; &amp;gt; /dev/null&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This ensures that the renewal process runs regularly and without manual intervention.&lt;/p&gt;
&lt;p&gt;Setting up Let&amp;rsquo;s Encrypt SSL certificates for Nginx in a Docker environment using acme.sh is an easy process that enhances the security of your web applications. By leveraging acme.sh, you automate the certificate issuance and renewal process, ensuring your sites remain secure without manual intervention. Following the steps outlined in this tutorial, you now have a robust setup where Nginx serves your applications over HTTPS, backed by trusted SSL certificates from Let&amp;rsquo;s Encrypt. Thank you for following this tutorial. Have a great day!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Google Chrome Yum/​RPM package update fails on RHEL/​CentOS 7</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2023/06/google-chrome-update-fails-rhel-7-centos-7/"/>
      <id>https://www.endpointdev.com/blog/2023/06/google-chrome-update-fails-rhel-7-centos-7/</id>
      <published>2023-06-08T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2023/06/google-chrome-update-fails-rhel-7-centos-7/willgard-krause-painted-ship-wreck-jungle.webp&#34; alt=&#34;Fantasy painting of a shipwreck in a jungle backlit by sunlight&#34;&gt;
&lt;a href=&#34;https://pixabay.com/photos/fantasy-painted-ship-wreck-jungle-7422641/&#34;&gt;Painting by Willgard Krause&lt;/a&gt;, Pixabay license&lt;/p&gt;
&lt;p&gt;One of our clients uses the Chrome web browser running on their continuous integration server with Jenkins for automated e2e (end-to-end) testing of their website. That server runs Red Hat Enterprise Linux (RHEL) 7—actually the rebuild CentOS 7.&lt;/p&gt;
&lt;p&gt;Last month, in May 2023, Google started signing Chrome RPMs with a GnuPG subkey, where they before had signed with the main key. Now &lt;code&gt;yum upgrade&lt;/code&gt; fails when trying to update Chrome, giving this error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;warning: /var/cache/yum/x86_64/7/google-chrome/packages/google-chrome-stable-114.0.5735.106-1.x86_64.rpm: Header V4 RSA/SHA512 Signature, key ID a3b88b8b: NOKEY
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Retrieving key from https://dl.google.com/linux/linux_signing_key.pub
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;The GPG keys listed for the &amp;#34;google-chrome&amp;#34; repository are already installed but they are not correct for this package.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Check that the correct key URLs are configured for this repository.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; Failing package is: google-chrome-stable-114.0.5735.106-1.x86_64
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; GPG Keys are configured as: https://dl.google.com/linux/linux_signing_key.pub&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To double-check, we tried to manually verify the signature on the downloaded RPM package with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# rpm -K /var/cache/yum/x86_64/7/google-chrome/packages/google-chrome-stable-114.0.5735.106-1.x86_64.rpm
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/var/cache/yum/x86_64/7/google-chrome/packages/google-chrome-stable-114.0.5735.106-1.x86_64.rpm: RSA sha1 ((MD5) PGP) md5 NOT OK (MISSING KEYS: (MD5) PGP#a3b88b8b)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That showed it is not just a Yum problem, but affects RPM too.&lt;/p&gt;
&lt;h3 id=&#34;sweatin-to-the-oldies&#34;&gt;Sweatin’ to the oldies&lt;/h3&gt;
&lt;p&gt;Long ago (see reference below), people reported that RPM wasn&amp;rsquo;t working with GnuPG subkeys for signatures, and Red Hat confirmed this is the case for RHEL 7 and earlier. They added support, but that first appeared in RHEL 8.&lt;/p&gt;
&lt;p&gt;RHEL 7 has another year of support left, until end of June 2024. But it was released in 2014 and is so old that apparently Google isn&amp;rsquo;t testing the RPM packages it produces on RHEL 7 anymore.&lt;/p&gt;
&lt;p&gt;Our client is planning to move this system that runs tests with Chrome to Rocky Linux 9, but for the next few months they need it to keep working on CentOS 7.&lt;/p&gt;
&lt;p&gt;So to cope, we used &lt;code&gt;scp&lt;/code&gt; to copy that RPM file to a RHEL 8 or 9 server, imported the Google signing public key, and used the newer version of &lt;code&gt;rpm&lt;/code&gt; there to verify the signature:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# rpm -K google-chrome-stable-114.0.5735.106-1.x86_64.rpm
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;google-chrome-stable-114.0.5735.106-1.x86_64.rpm: digests signatures OK&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then back on the RHEL 7 server we had no qualms skipping the signature check during upgrade because we had just manually checked it elsewhere:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# rpm -Uvh --nosignature /var/cache/yum/x86_64/7/google-chrome/packages/google-chrome-stable-114.0.5735.106-1.x86_64.rpm
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Preparing...                          ################################# [100%]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Updating / installing...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   1:google-chrome-stable-114.0.5735.1################################# [ 50%]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Cleaning up / removing...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   2:google-chrome-stable-113.0.5672.6################################# [100%]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then we ran &lt;code&gt;yum upgrade&lt;/code&gt; again to get the rest of that server&amp;rsquo;s pending package updates. Yum didn&amp;rsquo;t care about Chrome anymore since we had updated it already.&lt;/p&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.reddit.com/r/chrome/comments/13s799o/googlechromebeta_1140573545_rpm_invalid_signature/&#34;&gt;Reddit discussion on this situation&lt;/a&gt;—thanks especially to user Jskud&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://bugzilla.redhat.com/show_bug.cgi?id=227632&#34;&gt;Red Hat Bugzilla #227632&lt;/a&gt; where the problem is confirmed and the future fix announced&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Identifying Vulnerabilities in Code Using Horusec</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2023/03/identifying-vulnerabilities-using-horusec/"/>
      <id>https://www.endpointdev.com/blog/2023/03/identifying-vulnerabilities-using-horusec/</id>
      <published>2023-03-28T00:00:00+00:00</published>
      <author>
        <name>Indra Pranesh Palanisamy</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2023/03/identifying-vulnerabilities-using-horusec/pexels-indra-pranesh-palanisamy-15837790.webp&#34; alt=&#34;The sun has just started to rise over the horizon. A group of fishermen can be seen pushing their small wooden boats into the water, preparing to start their day&amp;rsquo;s work. The air is still cool and quiet, with only the sound of waves gently lapping against the shore as the fishermen row out into the open sea, ready to begin their early morning fishing expedition.&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Indra Pranesh Palanisamy, 2022 --&gt;
&lt;p&gt;&lt;a href=&#34;https://horusec.io/site/&#34;&gt;Horusec&lt;/a&gt; is an open source tool which, by orchestrating other security tools, identifies security flaws and vulnerabilities in source code. It puts all the possible vulnerabilities it finds into a database for analysis.&lt;/p&gt;
&lt;p&gt;Currently, Horusec supports C#, Java, Kotlin, Python, Ruby, Go, JavaScript, TypeScript, PHP, Swift, C, Dart, Elixir, shell, Terraform, Kubernetes, nginx, HTML, and JSON. You can see an up-to-date list of &lt;a href=&#34;https://docs.horusec.io/docs/cli/analysis-tools/overview/#available-programming-languages-and-tools&#34;&gt;supported languages&lt;/a&gt; in Horusec&amp;rsquo;s docs.&lt;/p&gt;
&lt;p&gt;It can also be integrated with CI/CD to execute the scan every time a developer creates a pull request or merge request.&lt;/p&gt;
&lt;h3 id=&#34;horusec-cli-installation&#34;&gt;Horusec CLI Installation&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt;: Docker, Git.&lt;/p&gt;
&lt;p&gt;The easiest installation method listed in the docs is &lt;code&gt;curl&lt;/code&gt;ing Horusec&amp;rsquo;s install script and piping it into &lt;code&gt;bash&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -fsSL https://raw.githubusercontent.com/ZupIT/horusec/main/deployments/scripts/install.sh | bash -s latest&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Be aware that there is risk to piping unseen commands into the shell like this: It can lead to unintended consequences and it is a bad security practice.&lt;/p&gt;
&lt;p&gt;If a user blindly pipes the output of a website response to be run by a shell without fully understanding what each command does, they may inadvertently execute malicious code or perform actions that could compromise the security of the system.&lt;/p&gt;
&lt;p&gt;To mitigate these risks, one solution is to use a virtual machine specifically set up for code scanning. This virtual machine should have no production data, no secret keys, and no other sensitive information. It should be used only for the purpose of scanning source code and nothing else.&lt;/p&gt;
&lt;p&gt;To instead download an executable directly, as well as for instructions for different platforms, see the &lt;a href=&#34;https://docs.horusec.io/docs/cli/installation/&#34;&gt;installation docs&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;vs-code-extension&#34;&gt;VS Code Extension&lt;/h3&gt;
&lt;p&gt;Horusec has a &lt;a href=&#34;https://docs.horusec.io/docs/extensions/visual-studio-code/&#34;&gt;VS Code extension&lt;/a&gt; which is helpful for making complete code analysis with a single click.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2023/03/identifying-vulnerabilities-using-horusec/horusec-vscode.webp&#34; alt=&#34;Screenshot of Horusec VS Code extension&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;usage&#34;&gt;Usage&lt;/h3&gt;
&lt;p&gt;Navigate to the application directory and run the &lt;code&gt;generate&lt;/code&gt; command to make a configuration file called &lt;code&gt;horusec-config.json&lt;/code&gt; which has a set of &lt;a href=&#34;https://docs.horusec.io/docs/cli/commands-and-flags/#global-flags&#34;&gt;customisations&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd /path/to/project
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;horusec generate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;start&lt;/code&gt; command executes the code scan throughout the repository, searching for possible vulnerabilities:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;horusec start -p . --output-format=json --json-output-file=&amp;lt;filename&amp;gt;.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;All the detected vulnerabilities are stored in the named JSON output file.&lt;/p&gt;
&lt;h3 id=&#34;classification-of-vulnerabilities&#34;&gt;Classification of Vulnerabilities&lt;/h3&gt;
&lt;p&gt;Horusec may identify (or, as the docs say, accuse) possible vulnerabilities that aren&amp;rsquo;t vulnerabilities at all. Possible vulnerabilites need to be classified to sort out those that are wrong.&lt;/p&gt;
&lt;p&gt;Here are the classification types from &lt;a href=&#34;https://docs.horusec.io/docs/tutorials/how-to-classify-a-vulnerability/#classification-types&#34;&gt;Horusec&amp;rsquo;s docs&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;False positive:&lt;/strong&gt; Vulnerability found is wrong, because it is accused in a test file or it is not a vulnerability in fact, and is safe code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accepted Risk:&lt;/strong&gt; Vulnerability that was accused, but at the moment, you don&amp;rsquo;t have the option to correct it, so it is classified as an accepted risk to not raise alarms in future runs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Corrected:&lt;/strong&gt; Vulnerability that doesn&amp;rsquo;t exist and can be considered as corrected.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vulnerability:&lt;/strong&gt; A possible security problem found and accused by the analysis.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The hashes to be ignored can be added to the configuration file, &lt;code&gt;horusec-config.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;horusecCliFalsePositiveHashes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;f9e5abe187ad4246daa4e9113e0a11a175347e793fbc40acd7663df67b2f89d2&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;878c35116d043311403a0a2e8a64f2c8d00479a1c23373dcd50372ff35e123c8&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;horusecCliRiskAcceptHashes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;5c9af42834ca77233a0e7afc98df317fb0e6041ea69a109754f278331039f844&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;b494003277bcd7792390f032ba39e9a860da66ddb1ccfcd8a581978eab744561&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;ignore-files&#34;&gt;Ignore Files&lt;/h4&gt;
&lt;p&gt;To entirely ignore a file or certain paths under the directory, add the paths to the configuration file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;horusecCliFilesOrPathsToIgnore&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;**/tmp/**&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;**/.vscode/**&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;code-scanning-in-epitrax&#34;&gt;Code Scanning in EpiTrax&lt;/h3&gt;
&lt;p&gt;The use of a code scan tool has become an integral part of our work with the EpiTrax disease surveillance system for public health.&lt;/p&gt;
&lt;p&gt;By integrating Horusec into our software development process, we have been able to identify potential security vulnerabilities, code quality issues, and other problems before they can cause any significant harm.&lt;/p&gt;
&lt;p&gt;Moreover, it has helped us reduce the overall cost and time associated with bug-fixing and maintenance tasks, making our projects more efficient and effective. By automating the scanning process, we can identify problems quickly and accurately, allowing us to prioritize and address the most critical ones first.&lt;/p&gt;
&lt;p&gt;By leveraging the power of code scans, we can ensure that our code is of the highest quality, and our projects are as secure and reliable as possible.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;em&gt;Special thanks to my colleagues Kürşat Kutlu Aydemir and Edgar Mlowe for their insights and feedback on this blog post.&lt;/em&gt;&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>ssh-askpass on macOS for SSH agent confirmation</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/11/ssh-askpass-on-mac-os-for-agent-confirmation/"/>
      <id>https://www.endpointdev.com/blog/2022/11/ssh-askpass-on-mac-os-for-agent-confirmation/</id>
      <published>2022-11-23T00:00:00+00:00</published>
      <author>
        <name>Bharathi Ponnusamy</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/11/ssh-askpass-on-mac-os-for-agent-confirmation/night-street.webp&#34; alt=&#34;A city street at night. A man sits on a bench, looking at his laptop as cyclists pass by.&#34;&gt;&lt;br&gt;
&lt;a href=&#34;https://flic.kr/p/2nUPsJQ&#34;&gt;Photo&lt;/a&gt; by &lt;a href=&#34;https://www.flickr.com/people/kristoffer-trolle/&#34;&gt;Kristoffer Trolle&lt;/a&gt;, &lt;a href=&#34;https://creativecommons.org/licenses/by/2.0/&#34;&gt;CC BY 2.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;At End Point Dev we mostly use SSH keys for authentication when connecting to remote servers and Git services. The majority of the time, the servers we are trying to visit are barred from direct access and require a middle &amp;ldquo;jump server&amp;rdquo; instead.&lt;/p&gt;
&lt;p&gt;Enabling SSH agent forwarding makes it easier to reuse SSH private keys. It keeps the private keys on our local machine and uses them to authenticate with each server in the chain without entering a password.&lt;/p&gt;
&lt;p&gt;However, this approach comes with an inherent risk of the agent being hijacked if one of the servers is compromised. This means a bad guy could use the SSH keys to compromise downstream servers.&lt;/p&gt;
&lt;p&gt;In this post, we’ll cover a simple way to protect against SSH agent hijacking. We will see in detail on macOS how to configure a system-wide agent using ssh-askpass to pop up a graphical window to ask for confirmation before using the agent.&lt;/p&gt;
&lt;h3 id=&#34;how-it-works&#34;&gt;How it works&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/11/ssh-askpass-on-mac-os-for-agent-confirmation/ssh-askpass.webp&#34; alt=&#34;Agent confirmation dialog reading &amp;ldquo;Allow us of key /Users/(blank)/.ssh/id_rsa? Key fingerprint (blank) (blank). The cancel button is highlighted.&#34;&gt;&lt;/p&gt;
&lt;p&gt;It is strongly recommended to use the &lt;code&gt;-c&lt;/code&gt; option on the &lt;code&gt;ssh-add&lt;/code&gt; command when adding your SSH keys to the agent in order to protect yourself against SSH agent hijacking.&lt;/p&gt;
&lt;p&gt;With this, every time a request is made to utilize the private key stored in the SSH agent, ssh-askpass will display a prompt on your local computer asking you to approve the usage of the key. By doing this,  it becomes more difficult for a remote attacker to use the private key without authorization.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t want to use the ssh-askpass agent confirmation, I recommend using the OpenSSH feature ProxyJump rather than agent forwarding to get an equivalent level of security.&lt;/p&gt;
&lt;h3 id=&#34;installing-ssh-askpass-on-macos&#34;&gt;Installing ssh-askpass on macOS&lt;/h3&gt;
&lt;p&gt;So let&amp;rsquo;s set this up. The recommended way is with Homebrew:&lt;/p&gt;
&lt;h4 id=&#34;install-ssh-askpass-with-homebrew&#34;&gt;Install ssh-askpass with Homebrew&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew tap theseal/ssh-askpass
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew install ssh-askpass&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You might see some warnings. Go ahead and proceed with them.&lt;/p&gt;
&lt;p&gt;Now we need to start the Homebrew services. Note that this is a brew service, not a regular macOS daemon service.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew services start theseal/ssh-askpass/ssh-askpass
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;=&amp;gt; Successfully started `ssh-askpass` (label: homebrew.mxcl.ssh-askpass)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Behind the scenes, it just sets the &lt;code&gt;SSH_ASKPASS&lt;/code&gt; and &lt;code&gt;SUDO_ASKPASS&lt;/code&gt; environment variables and stops &lt;code&gt;ssh-agent&lt;/code&gt;, so that the SSH agent can pick up these environment variables when it restarts.&lt;/p&gt;
&lt;p&gt;To list the services and make sure it’s started:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew services list | grep ssh-askpass
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ssh-askpass started ~/Library/LaunchAgents/homebrew.mxcl.ssh-askpass.plist&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;install-ssh-askpass-with-macports&#34;&gt;Install ssh-askpass with MacPorts&lt;/h4&gt;
&lt;p&gt;If you prefer MacPorts, do:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo port install ssh-askpass&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;install-ssh-askpass-from-source-code&#34;&gt;Install ssh-askpass from source code&lt;/h4&gt;
&lt;p&gt;And of course you can install from source if you wish:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cp ssh-askpass /usr/local/bin/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cp ssh-askpass.plist ~/Library/LaunchAgents/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ launchctl load -w ~/Library/LaunchAgents/ssh-askpass.plist&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;configure-the-ssh-agent-with-the-ssh-add--c-option&#34;&gt;Configure the SSH agent with the &lt;code&gt;ssh-add -c&lt;/code&gt; option&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Let&amp;rsquo;s first verify that the agent is running, then add the private key with the confirmation option:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ssh-add -l
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;The agent has no identities.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ssh-add -c .ssh/id_rsa
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Enter passphrase for .ssh/id_rsa (will confirm after each use):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Identity added: .ssh/id_rsa (.ssh/id_rsa)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;The user must confirm each use of the key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Identity will get added if you provide the correct passphrase for the key. This can be confirmed by listing the keys again with &lt;code&gt;ssh-add -l&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;ssh-askpass-agent-confirmation&#34;&gt;ssh-askpass agent confirmation&lt;/h3&gt;
&lt;p&gt;Now let&amp;rsquo;s log into a remote server.&lt;/p&gt;
&lt;p&gt;You will be prompted to confirm the private key’s usage with the pop-up window:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/11/ssh-askpass-on-mac-os-for-agent-confirmation/ssh-askpass.webp&#34; alt=&#34;Agent confirmation dialog. Identical to the previous dialog.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;set-up-keyboard-shortcuts&#34;&gt;Set up keyboard shortcuts&lt;/h3&gt;
&lt;p&gt;Since it&amp;rsquo;s too easy to hit the spacebar and accept a connection, ssh-askpass defaults to the cancel option. We can use keyboard shortcuts to press &amp;ldquo;OK&amp;rdquo; by following the below steps.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go to &amp;ldquo;System Preferences&amp;rdquo; and then &amp;ldquo;Keyboard&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;macos-1015-catalina-11-big-sur-12-monterey&#34;&gt;macOS 10.15 Catalina, 11 Big Sur, 12 Monterey&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Go to &amp;ldquo;Shortcuts&amp;rdquo; tab&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;check the option &amp;ldquo;Use keyboard navigation to move focus between controls&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/11/ssh-askpass-on-mac-os-for-agent-confirmation/keyboard_shortcuts.webp&#34; alt=&#34;macOS 10.15+ settings open to the Keyboard section. First highlighted is the &amp;ldquo;Shortcuts&amp;rdquo; tab, and second is a checkbox at the bottom of the window reading &amp;ldquo;Use keyboard navigation to move focus between controls.&amp;rdquo;&#34;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;macos-13-ventura&#34;&gt;macOS 13 Ventura&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Turn on the &amp;ldquo;Keyboard navigation&amp;rdquo; option.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/11/ssh-askpass-on-mac-os-for-agent-confirmation/keyboard_shortcuts_on_ventura.webp&#34; alt=&#34;macOS 13.0 settings open to the keyboard tab, with a slider button reading &amp;ldquo;Keyboard navigation&amp;rdquo; highlighted.&#34;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now you can press tab ⇥ and then the spacebar to select &amp;ldquo;OK&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Vulnerability Scanning</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/09/vulnerability-scanning/"/>
      <id>https://www.endpointdev.com/blog/2022/09/vulnerability-scanning/</id>
      <published>2022-09-15T00:00:00+00:00</published>
      <author>
        <name>Jeremy Freeman</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/09/vulnerability-scanning/ridgeline.webp&#34; alt=&#34;A mountain ridge. One side of the ridge is still covered in shadows, while the sunrise illuminates the other side.&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Seth Jensen, 2022 --&gt;
&lt;h3 id=&#34;define-your-terms&#34;&gt;Define Your Terms&lt;/h3&gt;
&lt;p&gt;A security vulnerability is a flaw or bug that could be exploited by a threat agent/​threat actor. According to CrowdStrike, &amp;ldquo;A threat actor, also known as a malicious actor, is any person or organization that intentionally causes harm in the digital sphere.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Once a bug or flaw is deemed a vulnerability, it is registered by the MITRE Corporation as a Common Vulnerability or Exposure (CVE) and stored in their CVE database. A CVE is given an identifying number by a CVE Numbering Authority (CNA), for example, Red Hat, Microsoft, and other designated authorities.&lt;/p&gt;
&lt;p&gt;Threat levels are quantified by assigning a Common Vulnerability Scoring System (CVSS) score from 0 to 10. CVSS is a free and open standard for evaluating the level of threat to a business or organization maintained by the Forum of Incident Response and Security Teams (FIRST).&lt;/p&gt;
&lt;p&gt;The National Institute of Standards and Technology (NIST) is a federal agency housing the National Vulnerability Database (NVD). NIST provides a CVSS calculator. The NIST NVD database synchronizes with the MITRE CVE database. Only the NVD includes CVSS scores.&lt;/p&gt;
&lt;h3 id=&#34;the-attackers&#34;&gt;The Attackers&lt;/h3&gt;
&lt;p&gt;Real live people spend a lot of time and money trying to break into specific high-value targets, as do bots that clever people have weaponized to attack more cheaply and broadly at all hours of the day.&lt;/p&gt;
&lt;h3 id=&#34;main-security-vulnerability-categories&#34;&gt;Main Security Vulnerability Categories&lt;/h3&gt;
&lt;p&gt;The main information security vulnerability categories are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Broken Authentication&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When security credentials are stolen, attackers can usurp user identities and sessions as if they were the user.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SQL Injection&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Attackers can hijack database content by injecting malicious code. It can allow attackers to acquire sensitive data, modify or delete data, impersonate identities, and conduct other nefarious activities.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cross-site scripting (XSS)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This type of attack inserts malicious code into a website. Its target is the website user, threatening sensitive user information.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cross-Site Request Forgery (CSRF)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This attack may mislead the user into an action that they would do only unwittingly. For example, social engineering may hoodwink the user into clicking a link in an email or chat to get the user to perform an action of the attacker&amp;rsquo;s choice. It could be having their browser use JavaScript to submit a form that they don&amp;rsquo;t even know about.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security Misconfiguration&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A configuration error that can be exploited by attackers. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Default passwords left in place&lt;/li&gt;
&lt;li&gt;No password strength requirements to prevent users from setting weak passwords that can easily be found in dictionary attacks&lt;/li&gt;
&lt;li&gt;Web server directory listings left enabled, possibly exposing files that shouldn&amp;rsquo;t be seen&lt;/li&gt;
&lt;li&gt;Unused software modules or plugins left enabled, increasing the attack surface&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Software Vulnerabilities&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Especially out-of-date software. As more time passes without getting security updates, attackers can examine code for bugs and flaws that could be exploited. Thus, a given piece of software that was secure at one time may be vulnerable six months later or even the next day.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more on vulnerability categories, see our blog post on the &lt;a href=&#34;/blog/2019/02/owasp-top-ten-application-security-risks/&#34;&gt;OWASP Top 10&lt;/a&gt;. OWASP is the Open Web Application Security Project, which defines itself as “a nonprofit foundation that works to improve the security of software. Through community-led open-source software projects, hundreds of local chapters worldwide, tens of thousands of members, and leading educational and training conferences, the OWASP Foundation is the source for developers and technologists to secure the web.”&lt;/p&gt;
&lt;h3 id=&#34;solutions&#34;&gt;Solutions&lt;/h3&gt;
&lt;p&gt;Applications to scan for vulnerabilities are available. Some are free and/​or open source, some are paid, and some have a free version and a paid version.&lt;/p&gt;
&lt;p&gt;Vulnerability scanners are automated tools that look into a database of known CVEs for the specific software and library versions running on a given server. They also examine systems for flaw types that could be exploited.&lt;/p&gt;
&lt;p&gt;In general, the vulnerabilities fall into three classifications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;System/Network:&lt;/strong&gt; The application scans for system misconfiguration and network CVEs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web:&lt;/strong&gt; The application scans for SQL Injections, XSS, CSRF, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Software Analysis:&lt;/strong&gt; Static Application Security Testing (SAST). SAST analyzes source code to find security vulnerabilities. Each SAST has a set of languages it can scan, sometimes one or two, sometimes up to a dozen or more.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary, vulnerability scanning is a vital process that spans many aspects of Information Technology (IT). Its solutions are constantly evolving to keep up with new threats, an endless cat-and-mouse game where the cats must be ever adaptive to keep ahead of the ever-evolving malicious mice.&lt;/p&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.crowdstrike.com/cybersecurity-101/threat-actor/&#34;&gt;CrowdStrike&amp;rsquo;s explanation of a threat actor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mitre.org/&#34;&gt;MITRE Corporation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cve.org/&#34;&gt;CVE database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://first.org/&#34;&gt;Forum of Incident Response and Security Teams (FIRST)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://nvd.nist.gov/vuln-metrics/cvss&#34;&gt;Common Vulnerability Scoring System (CVSS) at NIST&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/Common_Vulnerability_Scoring_System&#34;&gt;CVSS at Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://nist.gov/&#34;&gt;National Institute of Standards and Technology (NIST)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://nvd.nist.gov/&#34;&gt;National Vulnerability Database (NVD)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://owasp.org/&#34;&gt;Open Web Application Security Project (OWASP)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Implementing Authentication in ASP.NET Core Web APIs</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/"/>
      <id>https://www.endpointdev.com/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/</id>
      <published>2022-06-17T00:00:00+00:00</published>
      <author>
        <name>Kevin Campusano</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/stockholm-buildings.webp&#34; alt=&#34;Several buildings with a lightly cloudy blue sky&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Seth Jensen --&gt;
&lt;p&gt;Authentication is a complex space. There are many problem scenarios and many more solutions. When it comes to &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-6.0&amp;amp;tabs=visual-studio-code&#34;&gt;Web APIs&lt;/a&gt; written with &lt;a href=&#34;https://github.com/dotnet/aspnetcore&#34;&gt;ASP.NET Core&lt;/a&gt;, there are various fully featured options like &lt;a href=&#34;https://duendesoftware.com/&#34;&gt;Duende IdentityServer&lt;/a&gt; or &lt;a href=&#34;https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-protect-backend-with-aad&#34;&gt;Azure Active Directory&lt;/a&gt;. These promise to be &amp;ldquo;everything but the kitchen sink&amp;rdquo; solutions which are robust and allow you to deal with many complex requirements.&lt;/p&gt;
&lt;p&gt;But what if our requirements dictate that we need something simpler? Do we have to roll out our own from scratch? Or does ASP.NET Core offer smaller, customizable, somewhat independent puzzle pieces that we can put together without having to write all the code ourselves and still have a good amount of control?&lt;/p&gt;
&lt;p&gt;Spoiler alert: The answer to that last question is yes. And we&amp;rsquo;re going to talk about it in this very article.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There is a &lt;a href=&#34;#table-of-contents&#34;&gt;Table of contents&lt;/a&gt; at the end of this post.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;two-approaches-to-authentication-jwt-and-api-keys&#34;&gt;Two approaches to authentication: JWT and API Keys&lt;/h3&gt;
&lt;p&gt;In this article, we&amp;rsquo;ll take an existing ASP.NET Core Web API and add authentication capabilities to it. Specifically, we&amp;rsquo;ll support two authentication schemes commonly used for Web APIs: JWT and API Keys. Also, we will use our own database for storage of user accounts and credentials.&lt;/p&gt;
&lt;p&gt;The project that we will work with is a simple ASP.NET Web API backed by a &lt;a href=&#34;https://www.postgresql.org/&#34;&gt;Postgres&lt;/a&gt; database. It has a few endpoints for &lt;a href=&#34;https://en.wikipedia.org/wiki/Create,_read,_update_and_delete&#34;&gt;CRUD&lt;/a&gt;ing automotive related data and for calculating values of vehicles based on various aspects of them. You can read all about the process of building it &lt;a href=&#34;https://www.endpointdev.com/blog/2021/07/dotnet-5-web-api/&#34;&gt;here&lt;/a&gt;. I also added a few database integration tests for it &lt;a href=&#34;https://www.endpointdev.com/blog/2022/01/database-integration-testing-with-dotnet/&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can find it &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-5-web-api&#34;&gt;on GitHub&lt;/a&gt;. If you&amp;rsquo;d like to follow along, clone the repository and checkout this commit: &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-5-web-api/tree/cd290c765fcd2c6693008d3dc76fa931098dcaa0&#34;&gt;cd290c765fcd2c6693008d3dc76fa931098dcaa0&lt;/a&gt;. It represents the project as it was before applying all the changes from this article.&lt;/p&gt;
&lt;p&gt;You can follow the instructions in &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-5-web-api/blob/json-web-token/VehicleQuotes/README.md&#34;&gt;the project&amp;rsquo;s README file&lt;/a&gt; in order to get the app up and running.&lt;/p&gt;
&lt;h3 id=&#34;managing-user-accounts-with-aspnet-core-identity&#34;&gt;Managing user accounts with ASP.NET Core Identity&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s deal first with the requirement of storing the user accounts in our own database.&lt;/p&gt;
&lt;p&gt;Luckily for us, ASP.NET Core provides us with &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-6.0&amp;amp;tabs=netcore-cli&#34;&gt;Identity&lt;/a&gt;. This is an API that offers a comprehensive solution for authentication. It can connect with the aforementioned Duende
IdentityServer for &lt;a href=&#34;https://openid.net/connect/&#34;&gt;OpenID Connect&lt;/a&gt; and &lt;a href=&#34;https://oauth.net/2/&#34;&gt;OAuth 2.0&lt;/a&gt;, supports authentication via third parties like Facebook or Google, and can fully integrate with user interfaces on web apps, complete with scaffolding/​autogeneration capabilities too.&lt;/p&gt;
&lt;p&gt;Most importantly for us, it supports management of user accounts stored in our own database. It includes data for third party logins, passwords, roles for authorization, email confirmation, access tokens, etc.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s a lot of of functionality baked in there. But we don&amp;rsquo;t need all that, so let&amp;rsquo;s see how we can take only the bits and pieces that we need in order to fulfill our specific requirements.&lt;/p&gt;
&lt;p&gt;Specifically, we want:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tables in our database for storage of user accounts and passwords.&lt;/li&gt;
&lt;li&gt;A programmatic way to create, fetch and validate users using those tables.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It&amp;rsquo;s actually pretty simple.&lt;/p&gt;
&lt;h4 id=&#34;install-the-necessary-nuget-packages&#34;&gt;Install the necessary NuGet packages&lt;/h4&gt;
&lt;p&gt;First, we need to install a couple of &lt;a href=&#34;https://www.nuget.org/&#34;&gt;NuGet&lt;/a&gt; packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.nuget.org/packages/Microsoft.AspNetCore.Identity/&#34;&gt;Microsoft.AspNetCore.Identity&lt;/a&gt; which contains the core library.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.nuget.org/packages/Microsoft.AspNetCore.Identity.EntityFrameworkCore&#34;&gt;Microsoft.AspNetCore.Identity.EntityFrameworkCore&lt;/a&gt; which includes the classes that Identity needs in order to properly interact with &lt;a href=&#34;https://docs.microsoft.com/en-us/ef/&#34;&gt;Entity Framework&lt;/a&gt;, which is what we&amp;rsquo;re using in our Web API for interfacing with the database.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can install them by running these two commands from the &lt;code&gt;VehicleQuotes&lt;/code&gt; directory:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ dotnet add package Microsoft.AspNetCore.Identity
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That will add the following lines to the &lt;code&gt;VehicleQuotes/VehicleQuotes.csproj&lt;/code&gt; project file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;Project Sdk=&amp;#34;Microsoft.NET.Sdk.Web&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;ItemGroup&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    &amp;lt;PackageReference Include=&amp;#34;Microsoft.AspNetCore.Identity&amp;#34; Version=&amp;#34;2.2.0&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    &amp;lt;PackageReference Include=&amp;#34;Microsoft.AspNetCore.Identity.EntityFrameworkCore&amp;#34; Version=&amp;#34;6.0.5&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;     ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;/ItemGroup&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;/Project&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;update-the-dbcontext-to-include-the-identity-tables&#34;&gt;Update the DbContext to include the Identity tables&lt;/h4&gt;
&lt;p&gt;Next step is to configure our &lt;code&gt;DbContext&lt;/code&gt; class so that it includes the new tables that we need from the Identity library. So let&amp;rsquo;s go to &lt;code&gt;VehicleQuotes/Data/VehicleQuotesContext.cs&lt;/code&gt; and update it to do so.&lt;/p&gt;
&lt;p&gt;We need to include these new &lt;code&gt;using&lt;/code&gt; statements at the top of the file so that we have access to the classes that we need from the NuGet packages we just installed:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Identity&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Identity.EntityFrameworkCore&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, instead of &lt;code&gt;DbContext&lt;/code&gt;, the &lt;code&gt;VehicleQuotesContext&lt;/code&gt; class should inherit from &lt;code&gt;IdentityUserContext&amp;lt;IdentityUser&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;IdentityUserContext&lt;/code&gt; is a class provided by ASP.NET Core Identity that&amp;rsquo;s designed so that our &lt;code&gt;DbContext&lt;/code&gt; can inherit from it and gain user management functionality. Namely, it includes a new &lt;code&gt;DbSet&lt;/code&gt; (and consequently, a table) for holding user accounts (aptly named &lt;code&gt;Users&lt;/code&gt;), among other things that we won&amp;rsquo;t need.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;IdentityUserContext&lt;/code&gt; has a more feature rich counterpart called &lt;code&gt;IdentityDbContext&lt;/code&gt;, which also includes &lt;code&gt;DbSet&lt;/code&gt;s to support roles based authorization. We don&amp;rsquo;t need all that so we use its simpler cousin. Feel free to explore the &lt;a href=&#34;https://github.com/dotnet/aspnetcore/blob/main/src/Identity/EntityFrameworkCore/src/IdentityDbContext.cs&#34;&gt;source code on GitHub&lt;/a&gt; to see all it offers.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;The &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/standard/generics/&#34;&gt;generic type parameter&lt;/a&gt; that we give it, &lt;code&gt;IdentityUser&lt;/code&gt;, is a class that&amp;rsquo;s also provided by the Identity library. Its purpose is to serve as a default &lt;a href=&#34;https://docs.microsoft.com/en-us/ef/core/modeling/entity-types?tabs=data-annotations&#34;&gt;Entity Type&lt;/a&gt; for our user model and, as a consequence, our &lt;code&gt;Users&lt;/code&gt; &lt;code&gt;DbSet&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In summary, by having our &lt;code&gt;DbContext&lt;/code&gt; class inherit from &lt;code&gt;IdentityUserContext&amp;lt;IdentityUser&amp;gt;&lt;/code&gt;, we&amp;rsquo;re telling Identity that we want it to augment our &lt;code&gt;DbContext&lt;/code&gt; (and database) to include the core user management tables and that we want our users table to have the same columns as &lt;code&gt;IdentityUser&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If we wanted to include more columns in our users table, what we would have to do is create a new class, make it inherit from &lt;code&gt;IdentityUser&lt;/code&gt;, define any additional fields that we want on it, and use that class as a type parameter to &lt;code&gt;IdentityUserContext&lt;/code&gt;. For us for now, the default works just fine. You can learn more about customizing Identity in &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-6.0&#34;&gt;the official docs&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;The change looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-public class VehicleQuotesContext : DbContext
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+public class VehicleQuotesContext : IdentityUserContext&amp;lt;IdentityUser&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, &lt;code&gt;IdentityUserContext&lt;/code&gt; has some logic that it needs to run when it is being created. In order to allow it to run that logic, let&amp;rsquo;s add the following line to our &lt;code&gt;VehicleQuotesContext&lt;/code&gt;&amp;rsquo;s &lt;code&gt;OnModelCreating&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; protected override void OnModelCreating(ModelBuilder modelBuilder)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    base.OnModelCreating(modelBuilder);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;     // ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This calls &lt;code&gt;IdentityUserContext&lt;/code&gt;&amp;rsquo;s own &lt;code&gt;OnModelCreating&lt;/code&gt; implementation so that it can set itself up properly.&lt;/p&gt;
&lt;p&gt;The complete file should be looking like this now:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Identity&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Identity.EntityFrameworkCore&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Models&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotesContext&lt;/span&gt; : IdentityUserContext&amp;lt;IdentityUser&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; VehicleQuotesContext (DbContextOptions&amp;lt;VehicleQuotesContext&amp;gt; options)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            : &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;base&lt;/span&gt;(options)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; DbSet&amp;lt;Make&amp;gt; Makes { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; DbSet&amp;lt;Size&amp;gt; Sizes { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; DbSet&amp;lt;BodyType&amp;gt; BodyTypes { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; DbSet&amp;lt;Model&amp;gt; Models { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; DbSet&amp;lt;ModelStyle&amp;gt; ModelStyles { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; DbSet&amp;lt;ModelStyleYear&amp;gt; ModelStyleYears { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; DbSet&amp;lt;QuoteRule&amp;gt; QuoteRules { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; DbSet&amp;lt;QuoteOverride&amp;gt; QuoteOverides { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; DbSet&amp;lt;Quote&amp;gt; Quotes { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;protected&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;void&lt;/span&gt; OnModelCreating(ModelBuilder modelBuilder)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;base&lt;/span&gt;.OnModelCreating(modelBuilder);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            modelBuilder.Entity&amp;lt;Size&amp;gt;().HasData(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Size { ID = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;, Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Subcompact&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Size { ID = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;, Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Compact&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Size { ID = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;3&lt;/span&gt;, Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Mid Size&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Size { ID = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;5&lt;/span&gt;, Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Full Size&amp;#34;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            modelBuilder.Entity&amp;lt;BodyType&amp;gt;().HasData(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; BodyType { ID = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;, Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Coupe&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; BodyType { ID = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;, Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Sedan&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; BodyType { ID = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;3&lt;/span&gt;, Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Hatchback&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; BodyType { ID = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;4&lt;/span&gt;, Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Wagon&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; BodyType { ID = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;5&lt;/span&gt;, Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Convertible&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; BodyType { ID = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;6&lt;/span&gt;, Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;SUV&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; BodyType { ID = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;7&lt;/span&gt;, Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Truck&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; BodyType { ID = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;8&lt;/span&gt;, Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Mini Van&amp;#34;&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; BodyType { ID = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;9&lt;/span&gt;, Name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Roadster&amp;#34;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;applying-database-changes&#34;&gt;Applying database changes&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;Users&lt;/code&gt; &lt;code&gt;DbSet&lt;/code&gt; that we added into our data model by inheriting from &lt;code&gt;IdentityUserContext&amp;lt;IdentityUser&amp;gt;&lt;/code&gt; will become a table once we create and apply a database migration. That&amp;rsquo;s what we will do next.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s simple in ASP.NET Core. All we need to do is run a command like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet ef migrations add AddIdentityTables&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That should produce a new migration file named something like &lt;code&gt;20220605003253_AddIdentityTables.cs&lt;/code&gt;. If you explore it, you&amp;rsquo;ll see how it contains the definitions for a few new database tables. Including the one that we want: &lt;code&gt;AspNetUsers&lt;/code&gt;. That&amp;rsquo;s the one that we will use to store our user account records.&lt;/p&gt;
&lt;p&gt;Next, we apply the migration with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet ef database update&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you now connect to the database, you&amp;rsquo;ll see the new tables in there.&lt;/p&gt;
&lt;p&gt;If you have the database running in a Docker container like we discussed in the beginning of the article, you should be able to connect to it with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ psql -h localhost -U vehicle_quotes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then, to see the tables:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vehicle_quotes=# \dt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    List of relations
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; Schema |         Name          | Type  |     Owner      
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;--------+-----------------------+-------+----------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; public | AspNetUserClaims      | table | vehicle_quotes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; public | AspNetUserLogins      | table | vehicle_quotes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; public | AspNetUserTokens      | table | vehicle_quotes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; public | AspNetUsers           | table | vehicle_quotes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(14 rows)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And that&amp;rsquo;s pretty much it when it comes to having a sensible storage for user accounts. That was pretty inexpensive, wasn&amp;rsquo;t it? All we had to do was install some NuGet packages, tweak our existing DbContext, and run some migrations.&lt;/p&gt;
&lt;p&gt;The best thing is that we&amp;rsquo;re not done yet. We can also take advantage of ASP.NET Core Identity to manage users programmatically. Instead of interacting with these tables directly, we will use the service classes provided by Identity to create new users, fetch existing ones and validate their credentials.&lt;/p&gt;
&lt;p&gt;Before we can do that though, we must first do some configuration in our app&amp;rsquo;s &lt;code&gt;Startup&lt;/code&gt; class so that said services are properly set up to our liking and are made available to our application.&lt;/p&gt;
&lt;!-- Next step for us is creating and fetching users. Let&#39;s see how we can make that happen. --&gt;
&lt;h4 id=&#34;configuring-the-identity-services&#34;&gt;Configuring the Identity services&lt;/h4&gt;
&lt;p&gt;We need to add some code to &lt;code&gt;VehicleQuotes/Startup.cs&lt;/code&gt;. With it, we configure the Identity system and add a few service classes to ASP.NET Core&amp;rsquo;s &lt;a href=&#34;https://en.wikipedia.org/wiki/Inversion_of_control&#34;&gt;IoC&lt;/a&gt; &lt;a href=&#34;https://www.tutorialsteacher.com/ioc/ioc-container&#34;&gt;container&lt;/a&gt; so that they are available to our app via &lt;a href=&#34;https://en.wikipedia.org/wiki/Dependency_injection&#34;&gt;Dependency Injection&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We need a new &lt;code&gt;using&lt;/code&gt; statement:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Identity&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And the following code added to the &lt;code&gt;ConfigureServices&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .AddIdentityCore&amp;lt;IdentityUser&amp;gt;(options =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options.SignIn.RequireConfirmedAccount = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options.User.RequireUniqueEmail = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options.Password.RequireDigit = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options.Password.RequiredLength = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;6&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options.Password.RequireNonAlphanumeric = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options.Password.RequireUppercase = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options.Password.RequireLowercase = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .AddEntityFrameworkStores&amp;lt;VehicleQuotesContext&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The call to &lt;code&gt;AddIdentityCore&lt;/code&gt; makes several Identity utility classes available to the application. Among those, &lt;code&gt;UserManager&lt;/code&gt; is the only one we will use. We will use it later to&amp;hellip; well, manage users. You can also see how we&amp;rsquo;ve set a few options related to how the user accounts are handled. &lt;code&gt;options.SignIn.RequireConfirmedAccount&lt;/code&gt; controls whether new accounts need to be confirmed via email before they are available. With &lt;code&gt;options.User.RequireUniqueEmail&lt;/code&gt;, we tell Identity to enforce uniqueness of emails on user accounts. And finally the &lt;code&gt;options.Password.*&lt;/code&gt; options configure the password strength requirements.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can explore all the available options in &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.identityoptions?view=aspnetcore-6.0&#34;&gt;the official docs&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Then, the call to &lt;code&gt;AddEntityFrameworkStores&lt;/code&gt; tells the Identity system that it should use our &lt;code&gt;VehicleQuotesContext&lt;/code&gt; for data storage.&lt;/p&gt;
&lt;h4 id=&#34;creating-users&#34;&gt;Creating users&lt;/h4&gt;
&lt;p&gt;With that configuration out of the way, we can now write some code to create new user accounts. To keep things simple, we&amp;rsquo;ll add a new &lt;code&gt;UsersController&lt;/code&gt; to our project that will expose a new endpoint that offers that functionality.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start with this in &lt;code&gt;VehicleQuotes/Controllers/UsersController.cs&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Identity&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Controllers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;    [Route(&amp;#34;api/[controller]&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;    [ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;UsersController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; UserManager&amp;lt;IdentityUser&amp;gt; _userManager;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; UsersController(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            UserManager&amp;lt;IdentityUser&amp;gt; userManager
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            _userManager = userManager;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see, this controller defines a dependency on &lt;code&gt;UserManager&amp;lt;IdentityUser&amp;gt;&lt;/code&gt;, an instance of which is injected via the constructor. This is one of the classes made available to us when we configured the Identity core services in our app&amp;rsquo;s &lt;code&gt;Startup&lt;/code&gt;. We will use it to create new user records.&lt;/p&gt;
&lt;p&gt;Before that though, we need to define a new class that encapsulates the payload for a request to our endpoint. When it comes to creating user accounts, all we need is a username, a password, and an email. As such, we add the following class in a new &lt;code&gt;VehicleQuotes/ResourceModels/User.cs&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System.ComponentModel.DataAnnotations&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.ResourceModels&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;User&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;        [Required]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; UserName { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;        [Required]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Password { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;        [Required]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Email { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, we shall use this type as the parameter for a new &lt;code&gt;PostUser&lt;/code&gt; action method in the &lt;code&gt;UserController&lt;/code&gt; which will expose the new user account creation endpoint in our API. The method looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// POST: api/Users&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;[HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; Task&amp;lt;ActionResult&amp;lt;User&amp;gt;&amp;gt; PostUser(User user)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (!ModelState.IsValid)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; BadRequest(ModelState);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _userManager.CreateAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; IdentityUser() { UserName = user.UserName, Email = user.Email },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        user.Password
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (!result.Succeeded)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; BadRequest(result.Errors);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    user.Password = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; Created(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;, user);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Be sure to also add the following &lt;code&gt;using&lt;/code&gt; statements:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System.Threading.Tasks&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.ResourceModels&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;System.Threading.Tasks&lt;/code&gt; allows us to reference the class &lt;code&gt;Task&amp;lt;&amp;gt;&lt;/code&gt; which is the return type of the new &lt;code&gt;PostUser&lt;/code&gt; method. &lt;code&gt;VehicleQuotes.ResourceModels&lt;/code&gt; is where our new &lt;code&gt;User&lt;/code&gt; class lives.&lt;/p&gt;
&lt;p&gt;Thanks to the instance of &lt;code&gt;UserManager&amp;lt;IdentityUser&amp;gt;&lt;/code&gt; that we&amp;rsquo;re holding onto, this method is very straightforward. The most interesting portion is this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _userManager.CreateAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; IdentityUser() { UserName = user.UserName, Email = user.Email },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    user.Password
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here we&amp;rsquo;re &lt;code&gt;new&lt;/code&gt;ing up an &lt;code&gt;IdentityUser&lt;/code&gt; instance using the given request parameters and passing it on to &lt;code&gt;UserManager&amp;lt;IdentityUser&amp;gt;&lt;/code&gt;&amp;rsquo;s &lt;code&gt;CreateAsync&lt;/code&gt; method, along with the password that was also given in the incoming request. This puts the Identity system to work for us and properly create a new user account.&lt;/p&gt;
&lt;p&gt;Then, we can inspect its return value (which we capture in the &lt;code&gt;result&lt;/code&gt; variable) to determine if the operation was successful. That way we can respond appropriately to our API&amp;rsquo;s caller.&lt;/p&gt;
&lt;p&gt;Finally, with&amp;hellip;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user.Password = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; Created(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;, user);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;we return the data representing the newly created user account but we&amp;rsquo;re discreet and make sure not to include the password.&lt;/p&gt;
&lt;p&gt;With that, we can test our API. Fire it up with &lt;code&gt;dotnet run&lt;/code&gt; (or &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/test/hot-reload?view=aspnetcore-6.0&#34;&gt;&lt;code&gt;dotnet watch&lt;/code&gt;&lt;/a&gt;!) and send a &lt;code&gt;POST&lt;/code&gt; request to our new endpoint at &lt;code&gt;http://0.0.0.0:5000/api/Users&lt;/code&gt; with a payload like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;userName&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;newuser000&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;email&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;newuser000@endpointdev.com&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I just tried it in &lt;a href=&#34;https://www.postman.com/&#34;&gt;Postman&lt;/a&gt; and this is what it looked like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/post-user.webp&#34; alt=&#34;Successful user creation request in Postman&#34;&gt;&lt;/p&gt;
&lt;p&gt;Feel free to test it out further. Try repeated emails or usernames. Try passwords that don&amp;rsquo;t meet the criteria we defined when configuring Identity in the app&amp;rsquo;s &lt;code&gt;Startup&lt;/code&gt; class. It all works as you&amp;rsquo;d expect.&lt;/p&gt;
&lt;p&gt;You can inspect the database and see the newly created record too:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vehicle_quotes=# select id, user_name, email, password_hash from &amp;#34;AspNetUsers&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-[ RECORD 1 ]-+--------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;id            | aaad07b4-f109-4255-8caa-185fd7694c72
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user_name     | newuser000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;email         | newuser000@endpointdev.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;password_hash | AQAAAAEAACcQAAAAEJJTV7M2Ejqd3K3iC...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And there it is, that&amp;rsquo;s our record. With a hashed password and everything.&lt;/p&gt;
&lt;p&gt;Now let&amp;rsquo;s see what would an endpoint to fetch users look like:&lt;/p&gt;
&lt;h4 id=&#34;fetching-users&#34;&gt;Fetching users&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;UserManager&amp;lt;IdentityUser&amp;gt;&lt;/code&gt; offers a &lt;code&gt;FindByNameAsync&lt;/code&gt; method that we can use to retrieve users by name. We can add a new endpoint that leverages it like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// GET: api/Users/username&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;[HttpGet(&amp;#34;{username}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; Task&amp;lt;ActionResult&amp;lt;User&amp;gt;&amp;gt; GetUser(&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; username)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    IdentityUser user = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _userManager.FindByNameAsync(username);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (user == &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; NotFound();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; User
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        UserName = user.UserName,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Email = user.Email
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is even more straightforward than the previous one. We just call the &lt;code&gt;FindByNameAsync&lt;/code&gt; method by giving it the username of the account we want, make sure that we actually found it, and then return the data.&lt;/p&gt;
&lt;p&gt;For the response, we use the same &lt;code&gt;User&lt;/code&gt; class that we created to represent the input for the creation endpoint. If we added more fields to the user profile, we could include them here. Alas, we only have username and email for now.&lt;/p&gt;
&lt;p&gt;Restart the app and we can now make a request like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/get-user.webp&#34; alt=&#34;Successful user fetch request in Postman&#34;&gt;&lt;/p&gt;
&lt;p&gt;Pretty neat, huh? Try a username that does not exist and you should see the API respond with a 404.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One quick improvement that we can do before we move on: let&amp;rsquo;s change &lt;code&gt;PostUser&lt;/code&gt;&amp;rsquo;s return statement to this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; CreatedAtAction(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;GetUser&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; { username = user.UserName }, user);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;All that does is include a &lt;code&gt;Location&lt;/code&gt; header on the POST Users endpoint&amp;rsquo;s response that contains the URL for the newly created user. &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201&#34;&gt;That&amp;rsquo;s just being a good citizen&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;implementing-jwt-bearer-token-authentication&#34;&gt;Implementing JWT Bearer Token authentication&lt;/h3&gt;
&lt;p&gt;Now that we have the user management capabilities that we need, let&amp;rsquo;s implement some actual authentication. We will start by adding &lt;a href=&#34;https://jwt.io/&#34;&gt;JWT&lt;/a&gt;-based authentication to our API.&lt;/p&gt;
&lt;p&gt;The strategy that we will use is to create a new API endpoint that clients can &lt;code&gt;POST&lt;/code&gt; credentials to and that will respond to them with a fresh, short-lived token. They will then be able to use that token for subsequent requests by including it via headers.&lt;/p&gt;
&lt;p&gt;We will pick a random endpoint to secure, just to serve as an example. That is, an endpoint that requires authentication in order to be accessed. &lt;code&gt;GET api/BodyTypes&lt;/code&gt; is a good candidate. It is defined in &lt;code&gt;VehicleQuotes/Controllers/BodyTypesController.cs&lt;/code&gt;&amp;rsquo;s &lt;code&gt;GetBodyTypes&lt;/code&gt; action method. Feel free to test it out.&lt;/p&gt;
&lt;h4 id=&#34;creating-tokens&#34;&gt;Creating tokens&lt;/h4&gt;
&lt;p&gt;Let&amp;rsquo;s start by creating the new endpoint that clients will call to obtain the auth tokens. In order to do so, we need a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A class that represents incoming request data.&lt;/li&gt;
&lt;li&gt;A class that represents outgoing response data.&lt;/li&gt;
&lt;li&gt;A class that can generate tokens for a given user.&lt;/li&gt;
&lt;li&gt;A new action method in &lt;code&gt;UsersController&lt;/code&gt; that does the work.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&#34;the-request-data-structure&#34;&gt;The request data structure&lt;/h5&gt;
&lt;p&gt;To deal with step one, let&amp;rsquo;s define a new class in &lt;code&gt;VehicleQuotes/ResourceModels/AuthenticationRequest.cs&lt;/code&gt; that looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System.ComponentModel.DataAnnotations&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.ResourceModels&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;AuthenticationRequest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;        [Required]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; UserName { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;        [Required]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Password { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;All users need to authenticate is provide a set of credentials: username and password. So we have this class that contains fields for both and that will be used as input for our endpoint.&lt;/p&gt;
&lt;h5 id=&#34;the-response-data-structure&#34;&gt;The response data structure&lt;/h5&gt;
&lt;p&gt;Next, we need to define a class to represent that endpoint&amp;rsquo;s response. I&amp;rsquo;ve added the following class in &lt;code&gt;VehicleQuotes/ResourceModels/AuthenticationResponse.cs&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.ResourceModels&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;AuthenticationResponse&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Token { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; DateTime Expiration { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Just a simple data structure containing the token itself and a date letting clients know when they can expect it to expire.&lt;/p&gt;
&lt;h5 id=&#34;the-class-that-creates-jwts&#34;&gt;The class that creates JWTs&lt;/h5&gt;
&lt;p&gt;Step 3 is creating a class that can produce the tokens. This is the most interesting part in terms of complexity. Before we can do that though, let&amp;rsquo;s add the following configurations to &lt;code&gt;VehicleQuotes/appsettings.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Jwt&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;Key&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;this is the secret key for the jwt, it must be kept secure&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;Issuer&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;vehiclequotes.endpointdev.com&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;Audience&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;vehiclequotes.endpointdev.com&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;Subject&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;JWT for vehiclequotes.endpointdev.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;These are values that we&amp;rsquo;ll need when creating the tokens. We&amp;rsquo;ll go over the purpose of each them as they come up as we continue writing our code.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Here we&amp;rsquo;ll gloss over some details on the inner workings of JWTs as a standard. You can learn more about them at &lt;a href=&#34;https://jwt.io/introduction&#34;&gt;jwt.io&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;For now, let&amp;rsquo;s add the following class in &lt;code&gt;VehicleQuotes/Services/JwtService.cs&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System.IdentityModel.Tokens.Jwt&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System.Security.Claims&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System.Text&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Identity&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.Extensions.Configuration&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.IdentityModel.Tokens&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.ResourceModels&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Services&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;JwtService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;int&lt;/span&gt; EXPIRATION_MINUTES = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; IConfiguration _configuration;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; JwtService(IConfiguration configuration)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            _configuration = configuration;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; AuthenticationResponse CreateToken(IdentityUser user)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; expiration = DateTime.UtcNow.AddMinutes(EXPIRATION_MINUTES);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; token = CreateJwtToken(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                CreateClaims(user),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                CreateSigningCredentials(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                expiration
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; tokenHandler = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; JwtSecurityTokenHandler();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; AuthenticationResponse {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Token = tokenHandler.WriteToken(token),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Expiration = expiration
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; JwtSecurityToken CreateJwtToken(Claim[] claims, SigningCredentials credentials, DateTime expiration) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; JwtSecurityToken(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                _configuration[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Jwt:Issuer&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                _configuration[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Jwt:Audience&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                claims,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                expires: expiration,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                signingCredentials: credentials
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; Claim[] CreateClaims(IdentityUser user) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt;[] {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(JwtRegisteredClaimNames.Sub, _configuration[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Jwt:Subject&amp;#34;&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(ClaimTypes.NameIdentifier, user.Id),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(ClaimTypes.Name, user.UserName),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(ClaimTypes.Email, user.Email)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; SigningCredentials CreateSigningCredentials() =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; SigningCredentials(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; SymmetricSecurityKey(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Encoding.UTF8.GetBytes(_configuration[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Jwt:Key&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                SecurityAlgorithms.HmacSha256
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;CreateToken&lt;/code&gt; method is the element that&amp;rsquo;s most worth discussing here. It receives an instance of &lt;code&gt;IdentityUser&lt;/code&gt; as a parameter (which, remember, is the entity class that represents our user accounts), uses it to construct a JWT, and returns it within a &lt;code&gt;AuthenticationResponse&lt;/code&gt; object (which is what we decided that our endpoint would return). To do so, we use various classes built into .NET.&lt;/p&gt;
&lt;p&gt;The main class that represents the JWT is &lt;code&gt;JwtSecurityToken&lt;/code&gt;. We &lt;code&gt;new&lt;/code&gt; up one of those in the &lt;code&gt;CreateJwtToken&lt;/code&gt; method with this call:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; JwtSecurityToken (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _configuration[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Jwt:Issuer&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _configuration[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Jwt:Audience&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    claims,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expires: expiration,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    signingCredentials: credentials
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&amp;ldquo;Issuer&amp;rdquo; and &amp;ldquo;Audience&amp;rdquo; are two important values for how JWTs work. They specify which entity is creating the token (i.e. the &amp;ldquo;Issuer&amp;rdquo;) and which entity is the token intended for (i.e. the &amp;ldquo;Audience&amp;rdquo;). We use the &lt;code&gt;IConfiguration&lt;/code&gt; instance that we got as a dependency to fetch their values from the &lt;code&gt;VehicleQuotes/appsettings.json&lt;/code&gt; file.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &amp;ldquo;Issuer&amp;rdquo; and &amp;ldquo;Audience&amp;rdquo; parameters in &lt;code&gt;JwtSecurityToken&lt;/code&gt;&amp;rsquo;s constructor correspond to JWT claims &lt;code&gt;iss&lt;/code&gt; and &lt;code&gt;aud&lt;/code&gt;, respectively. You can learn more about them and other claims in &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc7519#section-4.1&#34;&gt;the RFC&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;The next parameter that &lt;code&gt;JwtSecurityToken&lt;/code&gt;&amp;rsquo;s constructor needs is the claims array. In JWT terms, a claim is essentially a statement about the entity for which the token is generated, some data that identifies it. For example, if we&amp;rsquo;re generating a token for a user, what you would expect to see in such a token&amp;rsquo;s claims are things like username, email, and any other non-secret profile info.&lt;/p&gt;
&lt;p&gt;In our case, as you can see in the &lt;code&gt;CreateClaims&lt;/code&gt; method, we add a number of claims:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt;[] {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(JwtRegisteredClaimNames.Sub, _configuration[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Jwt:Subject&amp;#34;&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(ClaimTypes.NameIdentifier, user.Id),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(ClaimTypes.Name, user.UserName),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(ClaimTypes.Email, user.Email)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Along with the user id, name and email, we also add &lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;jti&lt;/code&gt; and &lt;code&gt;iat&lt;/code&gt; claims. These are standardized claims of which you can learn more about in &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc7519#section-4.1&#34;&gt;the RFC&lt;/a&gt;. The data that we put in here will make its way into the encoded token that our API caller eventually sees. We&amp;rsquo;ll see that later.&lt;/p&gt;
&lt;p&gt;The next parameter to &lt;code&gt;JwtSecurityToken&lt;/code&gt; is the expiration date of the token. Here we are setting it to just one minute in the future to comply with our original requirement that the token should be short-lived so that it only allows a handful of requests over a short period of time.&lt;/p&gt;
&lt;p&gt;Finally there&amp;rsquo;s the &lt;code&gt;signingCredentials&lt;/code&gt; parameter which tells the &lt;code&gt;JwtSecurityToken&lt;/code&gt; how to cryptographically sign the token. As you can see in the code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; SigningCredentials(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; SymmetricSecurityKey(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Encoding.UTF8.GetBytes(_configuration[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Jwt:Key&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    SecurityAlgorithms.HmacSha256
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That&amp;rsquo;s a &lt;code&gt;SigningCredentials&lt;/code&gt; instance that we create using the &amp;ldquo;key&amp;rdquo; that we have configured in &lt;code&gt;VehicleQuotes/appsettings.json&lt;/code&gt;, along with the algorithm to use to produce it.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s about it. There isn&amp;rsquo;t much else to this class. It is somewhat involved but that&amp;rsquo;s just how JWTs are created in .NET.&lt;/p&gt;
&lt;h5 id=&#34;the-action-method-that-puts-it-all-together&#34;&gt;The action method that puts it all together&lt;/h5&gt;
&lt;p&gt;Now all we need to do is actually create an endpoint that exposes this functionality to clients. Since we have the core logic encapsulated in our &lt;code&gt;JwtService&lt;/code&gt; class, the actual action method is simple. Here are the changes that we need to make in order to implement it:&lt;/p&gt;
&lt;p&gt;First, on &lt;code&gt;VehicleQuotes/Controllers/UsersController.cs&lt;/code&gt;, we add a new using statement so that we can reference our new &lt;code&gt;JwtService&lt;/code&gt; class:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Services&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we declare a new parameter of type &lt;code&gt;JwtService&lt;/code&gt; in the controller&amp;rsquo;s constructor so that we signal to ASP.NET Core&amp;rsquo;s Dependency Injection subsystem that we want to use one of those. We also hold onto it via a new instance variable called &lt;code&gt;_jwtService&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; public class UsersController : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     private readonly UserManager&amp;lt;IdentityUser&amp;gt; _userManager;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    private readonly JwtService _jwtService;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     public UsersController(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         UserManager&amp;lt;IdentityUser&amp;gt; userManager,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        JwtService jwtService
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;     ) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         _userManager = userManager;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        _jwtService = jwtService;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;     }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     // ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We also need to tell ASP.NET Core that &lt;code&gt;JwtService&lt;/code&gt; should be available for Dependency Injection. To do so, we can add this line in &lt;code&gt;VehicleQuotes/Startup.cs&lt;/code&gt;&amp;rsquo;s &lt;code&gt;ConfigureServices&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddScoped&amp;lt;Services.JwtService&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that this way of defining dependencies is not recommended and only done this way here to keep things simple for an illustrative app that&amp;rsquo;s never going to run in production. Ideally what you want to do here is define the dependency as an abstraction (i.e. an interface) and a concrete implementation that fulfills it. For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddScoped&amp;lt;ITokenCreationService, JwtService&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This way, classes that depend on this service can reference the interface, not the concrete type. In doing that, they adhere to the &lt;a href=&#34;https://en.wikipedia.org/wiki/Dependency_inversion_principle&#34;&gt;Dependency Inversion&lt;/a&gt; principle and become more easily testable because they allow mocks to be provided as dependencies.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Finally, we write the actual action method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// POST: api/Users/BearerToken&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;[HttpPost(&amp;#34;BearerToken&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; Task&amp;lt;ActionResult&amp;lt;AuthenticationResponse&amp;gt;&amp;gt; CreateBearerToken(AuthenticationRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (!ModelState.IsValid)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Bad credentials&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; user = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _userManager.FindByNameAsync(request.UserName);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (user == &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Bad credentials&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; isPasswordValid = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _userManager.CheckPasswordAsync(user, request.Password);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (!isPasswordValid)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Bad credentials&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; token = _jwtService.CreateToken(user);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; Ok(token);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here too we&amp;rsquo;re leveraging the &lt;code&gt;UserManager&lt;/code&gt; instance that ASP.NET Core Identity so graciously provided us with. In summary, we find the user account by name using the incoming request data, check if the given password is correct, then ask our &lt;code&gt;JwtService&lt;/code&gt; to create a token for this user, and finally return it wrapped in a 200 response.&lt;/p&gt;
&lt;p&gt;If you hit that endpoint with a POST and a set of existing credentials, you should see something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/post-bearer-token.webp&#34; alt=&#34;Successful JTW creation in Postman&#34;&gt;&lt;/p&gt;
&lt;p&gt;If you take that big string that came back in the &lt;code&gt;&amp;quot;token&amp;quot;&lt;/code&gt; field in the response JSON, and paste it in &lt;a href=&#34;https://jwt.io/&#34;&gt;jwt.io&lt;/a&gt;&amp;rsquo;s token decoder, you should be able to see all the claims that we added to the token:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/jwt-decoded.webp&#34; alt=&#34;The JTW decoded showing all the claims&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Notice that the keywords here are &amp;ldquo;Encoded&amp;rdquo; and &amp;ldquo;Decoded&amp;rdquo;. The claims that we put in our JWTs are not protected in any way. As such, we should never put secrets in there.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h4 id=&#34;securing-an-endpoint-with-jwt-authentication&#34;&gt;Securing an endpoint with JWT authentication&lt;/h4&gt;
&lt;p&gt;Now that we have a way for obtaining tokens, let&amp;rsquo;s see how we can actually use them to gain access to some resources. To demonstrate that, let&amp;rsquo;s secure an endpoint in a way that it denies unauthenticated requests.&lt;/p&gt;
&lt;h5 id=&#34;applying-the-authorize-attribute&#34;&gt;Applying the Authorize attribute&lt;/h5&gt;
&lt;p&gt;First we need to signal ASP.NET Core that the endpoint requires auth. We do that by annotating the corresponding action method with the &lt;code&gt;Authorize&lt;/code&gt; attribute. We&amp;rsquo;ve already decided that we were going to use &lt;code&gt;VehicleQuotes/Controllers/BodyTypesController.cs&lt;/code&gt;&amp;rsquo;s &lt;code&gt;GetBodyTypes&lt;/code&gt; method as a guinea pig. So, let&amp;rsquo;s go into that file and add the following &lt;code&gt;using&lt;/code&gt; statement:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Authorization&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That will allow us access to the attribute, which we can apply to the action method like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // GET: api/BodyTypes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+ [Authorize]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;  [HttpGet]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  public async Task&amp;lt;ActionResult&amp;lt;IEnumerable&amp;lt;BodyType&amp;gt;&amp;gt;&amp;gt; GetBodyTypes()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      return await _context.BodyTypes.ToListAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With this, we&amp;rsquo;ve told ASP.NET Core that we want it to require auth for this endpoint.&lt;/p&gt;
&lt;h5 id=&#34;enabling-and-configuring-the-jwt-authentication&#34;&gt;Enabling and configuring the JWT authentication&lt;/h5&gt;
&lt;p&gt;Now, we need to tell it how to actually perform the check. To do that, we need to install the &lt;a href=&#34;https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer&#34;&gt;&lt;code&gt;Microsoft.AspNetCore.Authentication.JwtBearer&lt;/code&gt;&lt;/a&gt; NuGet package. We can do that from the &lt;code&gt;VehicleQuotes&lt;/code&gt; directory with the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once we have that installed, we get access to additional services which we can use to configure the &amp;ldquo;JwtBearer&amp;rdquo; authentication scheme. As usual, we do the configuration in &lt;code&gt;VehicleQuotes/Startup.cs&lt;/code&gt;&amp;rsquo;s. Here&amp;rsquo;s what we need to do:&lt;/p&gt;
&lt;p&gt;Add a few new &lt;code&gt;using&lt;/code&gt; statements:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Authentication.JwtBearer&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.IdentityModel.Tokens&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System.Text&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Add the following code at the end of the &lt;code&gt;ConfigureServices&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .AddJwtBearer(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options.TokenValidationParameters = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; TokenValidationParameters()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ValidateIssuer = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ValidateAudience = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ValidateLifetime = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ValidateIssuerSigningKey = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ValidAudience = Configuration[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Jwt:Audience&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ValidIssuer = Configuration[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Jwt:Issuer&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            IssuerSigningKey = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; SymmetricSecurityKey(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Encoding.UTF8.GetBytes(Configuration[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Jwt:Key&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The call to &lt;code&gt;AddAuthentication&lt;/code&gt; includes all the internal core service classes that are needed to do authentication in our app.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;JwtBearerDefaults.AuthenticationScheme&lt;/code&gt; parameter that we give it is the name of the authentication scheme to use as the default. More on that later. For now, know that ASP.NET Core supports multiple authentication schemes to be used at the same time. In fact, our own plan here is to eventually support two auth schemes: JWTs and API Keys. We&amp;rsquo;re starting with JWT first and as such, that&amp;rsquo;s the one we specify as the default.&lt;/p&gt;
&lt;p&gt;The call to &lt;code&gt;AddJwtBearer&lt;/code&gt; configures the JWT authentication scheme. That is, it allows the app to perform authentication checks based on an incoming JWT token.&lt;/p&gt;
&lt;p&gt;The most important part of the configuration is the values that we are passing to &lt;code&gt;TokenValidationParameters&lt;/code&gt;. As you can see, we are able to specify which aspects of the incoming JWTs to validate. You can see all available options for &lt;code&gt;TokenValidationParameters&lt;/code&gt; in &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.tokens.tokenvalidationparameters?view=azure-dotnet&#34;&gt;the official documentation&lt;/a&gt;. Here we&amp;rsquo;ve chosen to validate obvious things like the issuer, audience, and signing key.&lt;/p&gt;
&lt;p&gt;The last configuration step is to add this line right before &lt;code&gt;app.UseAuthorization();&lt;/code&gt; in the &lt;code&gt;Configure&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app.UseAuthentication();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That will enable the authentication &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0&#34;&gt;middleware&lt;/a&gt;. That way the framework will perform authentication checks as part of the request processing pipeline. In other words, actually put all the configuration that we&amp;rsquo;ve done to good use.&lt;/p&gt;
&lt;p&gt;Alright, now that we have all the configuration ready and have secured an endpoint, let&amp;rsquo;s try hitting it with a GET on &lt;code&gt;api/BodyTypes&lt;/code&gt; and see what happens:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/unauthorized-response.webp&#34; alt=&#34;Unauthorized response for unauthenticated request&#34;&gt;&lt;/p&gt;
&lt;p&gt;Ok! So far so good. We made an unauthenticated request into an endpoint that requires authentication and as a result we got a 401 back. That&amp;rsquo;s just what we wanted.&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s get a token by POSTing to &lt;code&gt;api/Users/BearerToken&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/post-bearer-token-2.webp&#34; alt=&#34;Another successful JTW creation in Postman&#34;&gt;&lt;/p&gt;
&lt;p&gt;We can copy that token and include as a header in the GET request to &lt;code&gt;api/BodyTypes&lt;/code&gt;. The header key should be &lt;code&gt;Authorization&lt;/code&gt; and the value should be &lt;code&gt;Bearer &amp;lt;our token&amp;gt;&lt;/code&gt;. In Postman, we can setup a similar request if we choose the &amp;ldquo;Authorization&amp;rdquo; tab, select &amp;ldquo;Bearer Token&amp;rdquo; in the &amp;ldquo;Type&amp;rdquo; drop-down list and paste the token in the &amp;ldquo;Token&amp;rdquo; text box.&lt;/p&gt;
&lt;p&gt;Do that, and you should now see a 200 response from the endpoint:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/authorized-response-jwt.webp&#34; alt=&#34;Successful response for request authenticated with JWT&#34;&gt;&lt;/p&gt;
&lt;p&gt;Neat! Now that we passed a valid token, it wants to talk to us again. Let a few minutes pass, enough for the token to expire and try the request again to see how it&amp;rsquo;s 401&amp;rsquo;ing again.&lt;/p&gt;
&lt;h3 id=&#34;implementing-api-key-authentication&#34;&gt;Implementing API Key authentication&lt;/h3&gt;
&lt;p&gt;Now let&amp;rsquo;s change gears from JWT and implement an alternate authentication strategy in our Web API: API Keys. Authentication with API Keys is fairly common in the web service world.&lt;/p&gt;
&lt;p&gt;The core idea of API Keys is that the API provider (in this case, us) produces a secret string that is given to the clients for safekeeping. The clients then can use that key to access the API and make as many requests as they want. So it&amp;rsquo;s essentially just a glorified password.&lt;/p&gt;
&lt;p&gt;The main functional differences when it comes to JWT as we have implemented it is that the API Keys won&amp;rsquo;t expire and that we will store them in our database. Then, when processing incoming requests, as part of the authentication step, our app will check if the given API Key exists in our internal records. If it does, then the request is allowed to go through, if it does not, then a 401 is sent back.&lt;/p&gt;
&lt;p&gt;This is going to be interesting because ASP.NET Core does not include an authentication scheme out of the box for this kind of behavior. For JWT, we were able to use the &lt;code&gt;Microsoft.AspNetCore.Authentication.JwtBearer&lt;/code&gt; package. For this we have to get our hands dirty and actually implement a custom authentication handler that will run the logic that we need.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s get started.&lt;/p&gt;
&lt;h4 id=&#34;creating-api-keys&#34;&gt;Creating API Keys&lt;/h4&gt;
&lt;p&gt;Similarly to when we implemented JWTs, we&amp;rsquo;ll start by creating the new endpoint that clients will call to obtain their keys. In order to make it work, we&amp;rsquo;ll need:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A new model and database table to store the keys&lt;/li&gt;
&lt;li&gt;A service class to create the keys.&lt;/li&gt;
&lt;li&gt;A new action method in &lt;code&gt;UsersController&lt;/code&gt; that does the work.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&#34;the-api-key-model-and-database-table&#34;&gt;The API Key model and database table&lt;/h5&gt;
&lt;p&gt;For storing API Keys, we just need a simple table that is linked to users via a one-to-many relationship and includes a name and a value. Using Entity Framework, our model could look like this in &lt;code&gt;VehicleQuotes/Models/UserApiKey.cs&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System.ComponentModel.DataAnnotations&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System.Text.Json.Serialization&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Identity&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Models&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;    [Index(nameof(Value), IsUnique = true)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;UserApiKey&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;        [JsonIgnore]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;int&lt;/span&gt; ID { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;        [Required]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Value { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;        [JsonIgnore]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;        [Required]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; UserID { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;        [JsonIgnore]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; IdentityUser User { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Easy enough. We express the relationship between the &amp;ldquo;keys&amp;rdquo; and &amp;ldquo;users&amp;rdquo; tables via the &lt;code&gt;User&lt;/code&gt; navigation property and the &lt;code&gt;UserID&lt;/code&gt; field. We also define a unique index on the &lt;code&gt;Value&lt;/code&gt; field and annotate some fields with &lt;code&gt;Required&lt;/code&gt; because we don&amp;rsquo;t want them to allow null. Interestingly, we also annotated some of the fields with the &lt;code&gt;JsonIgnore&lt;/code&gt; attribute so that when we include objects of this type in API responses (which, as you&amp;rsquo;ll see, we will), those fields are not included.&lt;/p&gt;
&lt;p&gt;We also need to add a new &lt;code&gt;DbSet&lt;/code&gt; on our DbContext class at &lt;code&gt;VehicleQuotes/Data/VehicleQuotesContext.cs&lt;/code&gt; so that Entity Framework picks it up as part of the data model:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; DbSet&amp;lt;UserApiKey&amp;gt; UserApiKeys { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With that, let&amp;rsquo;s now create and run a migration so that the new table can be added to the database. All it takes is running this to create the migration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet ef migrations add AddUserApiKeysTable&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And this to apply it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet ef database update&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now the new table should appear in the database:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vehicle_quotes=# \dt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    List of relations
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; Schema |         Name          | Type  |     Owner      
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;--------+-----------------------+-------+----------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; public | user_api_keys         | table | vehicle_quotes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (15 rows)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5 id=&#34;the-class-that-creates-api-keys&#34;&gt;The class that creates API Keys&lt;/h5&gt;
&lt;p&gt;The next step is very straightforward. All we need is some logic that, when given a user account (in the form of an instance of &lt;code&gt;IdentityUser&lt;/code&gt;), can generate a new API Key, insert it in the database, and return it. The following unremarkable class, defined in &lt;code&gt;VehicleQuotes/Services/ApiKeyService.cs&lt;/code&gt;, encapsulates said logic:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Identity&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Models&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Services&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;ApiKeyService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; VehicleQuotesContext _context;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; ApiKeyService(VehicleQuotesContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            _context = context;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; UserApiKey CreateApiKey(IdentityUser user)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; newApiKey = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; UserApiKey
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                User = user,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Value = GenerateApiKeyValue()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            _context.UserApiKeys.Add(newApiKey);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            _context.SaveChanges();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; newApiKey;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; GenerateApiKeyValue() =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;$&amp;#34;{Guid.NewGuid().ToString()}-{Guid.NewGuid().ToString()}&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see, this class&amp;rsquo;s single public method, aptly named &lt;code&gt;CreateApiKey&lt;/code&gt;, just takes the given user and creates a new &lt;code&gt;UserApiKey&lt;/code&gt; that&amp;rsquo;s associated with it and saves it into the database. The key values themselves are just two &lt;a href=&#34;https://en.wikipedia.org/wiki/Universally_unique_identifier&#34;&gt;GUIDs&lt;/a&gt; concatenated.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is an admittedly simplistic and not-all-that-secure method for generating the keys themselves. For simplicity, we&amp;rsquo;ve gone with this. In a production app however, it&amp;rsquo;d be better to use a true cryptographically secure string. &lt;a href=&#34;https://jonathancrozier.com/blog/how-to-generate-a-cryptographically-secure-random-string-in-dot-net-with-c-sharp&#34;&gt;Here&amp;rsquo;s an article&lt;/a&gt; that explains how to do that with .NET.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Naturally, this class needs to be used elsewhere in the application, so let&amp;rsquo;s make it available for Dependency Injection by adding the following line to &lt;code&gt;VehicleQuotes/Startup.cs&lt;/code&gt;&amp;rsquo;s &lt;code&gt;ConfigureServices&lt;/code&gt; method.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services.AddScoped&amp;lt;Services.ApiKeyService&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5 id=&#34;the-action-method-that-puts-it-all-together-1&#34;&gt;The action method that puts it all together&lt;/h5&gt;
&lt;p&gt;Now let&amp;rsquo;s finally write a new action method on &lt;code&gt;VehicleQuotes/Controllers/UsersController.cs&lt;/code&gt; so that there&amp;rsquo;s an endpoint that clients can call to get API Keys. Before adding the action method though, as usual, we need to declare the following dependency as a constructor parameter and hold it in an instance variable:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; public class UsersController : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     private readonly UserManager&amp;lt;IdentityUser&amp;gt; _userManager;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     private readonly JwtService _jwtService;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    private readonly ApiKeyService _apiKeyService;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     public UsersController(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         UserManager&amp;lt;IdentityUser&amp;gt; userManager,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         JwtService jwtService,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        ApiKeyService apiKeyService 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;     ) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         _userManager = userManager;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         _jwtService = jwtService;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        _apiKeyService = apiKeyService;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;     }}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The action method itself would look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// POST: api/Users/ApiKey&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;[HttpPost(&amp;#34;ApiKey&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; Task&amp;lt;ActionResult&amp;gt; CreateApiKey(AuthenticationRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (!ModelState.IsValid)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; BadRequest(ModelState);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; user = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _userManager.FindByNameAsync(request.UserName);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (user == &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Bad credentials&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; isPasswordValid = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _userManager.CheckPasswordAsync(user, request.Password);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (!isPasswordValid)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; BadRequest(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Bad credentials&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; token = _apiKeyService.CreateApiKey(user);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; Ok(token);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This method is very similar to the other one that generates JWTs. Painfully so. In fact the only difference is that this one calls the &lt;code&gt;ApiKeyService&lt;/code&gt;&amp;rsquo;s &lt;code&gt;CreateApiKey&lt;/code&gt; method instead of &lt;code&gt;JwtService&lt;/code&gt;&amp;rsquo;s &lt;code&gt;CreateToken&lt;/code&gt;, for obvious reasons.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;These two are ripe for some refactoring to eliminate duplication but we won&amp;rsquo;t do that here. Take a look at the &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-5-web-api/tree/json-web-token&#34;&gt;finished project on GitHub&lt;/a&gt; to see &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-5-web-api/commit/9a078015cec2e8a42a4898e203a1a50db69731e8&#34;&gt;a neat refactoring option&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Anyway, you can now try a POST into &lt;code&gt;api/Users/ApiKey&lt;/code&gt;, pass it a valid set of credentials, and you should see it respond with a brand new API Key:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/post-api-key.webp&#34; alt=&#34;Successful response for authenticated request&#34;&gt;&lt;/p&gt;
&lt;p&gt;The key can also be found in the database:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vehicle_quotes=# select id, SUBSTRING(value,1,13), user_id from user_api_keys;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; id |   substring   |               user_id                
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;----+---------------+--------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  1 | efd2133b-cc4a | aaad07b4-f109-4255-8caa-185fd7694c72
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(1 row)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;securing-an-endpoint-with-api-key-authentication&#34;&gt;Securing an endpoint with API Key authentication&lt;/h4&gt;
&lt;p&gt;Now that users have a way of getting API Keys, we can start securing endpoints with that authentication scheme. In order to acomplish that, we need to take three steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Implement a custom authentication handler that runs the authentication logic.&lt;/li&gt;
&lt;li&gt;Configure the new authentication handler, plugging it into ASP.NET Core&amp;rsquo;s auth mechanisms.&lt;/li&gt;
&lt;li&gt;Secure certain endpoints via the Authorization attribute, specifying the scheme that we want to use to do so.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We&amp;rsquo;ll do that next.&lt;/p&gt;
&lt;h5 id=&#34;implementing-the-api-key-authentication-handler&#34;&gt;Implementing the API Key authentication handler&lt;/h5&gt;
&lt;p&gt;Like we&amp;rsquo;ve touched on before, the &amp;ldquo;authentication handler&amp;rdquo; is a class that can plug into ASP.NET Core&amp;rsquo;s authentication middleware and can run the authentication logic for a given authentication scheme. In practical terms, it&amp;rsquo;s a class that inherits from &lt;code&gt;Microsoft.AspNetCore.Authentication.AuthenticationHandler&lt;/code&gt; and implements the &lt;code&gt;HandleAuthenticateAsync&lt;/code&gt; method. Ours will live in &lt;code&gt;VehicleQuotes/Authentication/ApiKey/ApiKeyAuthenticationHandler.cs&lt;/code&gt; and it looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System.Security.Claims&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System.Text.Encodings.Web&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;System.Threading.Tasks&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Authentication&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Identity&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.Extensions.Logging&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.Extensions.Options&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Authentication.ApiKey&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;ApiKeyAuthenticationHandler&lt;/span&gt; : AuthenticationHandler&amp;lt;AuthenticationSchemeOptions&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; API_KEY_HEADER = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Api-Key&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; VehicleQuotesContext _context;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; ApiKeyAuthenticationHandler(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            IOptionsMonitor&amp;lt;AuthenticationSchemeOptions&amp;gt; options,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ILoggerFactory logger,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            UrlEncoder encoder,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ISystemClock clock,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            VehicleQuotesContext context
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ) : &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;base&lt;/span&gt;(options, logger, encoder, clock)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            _context = context;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;protected&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; Task&amp;lt;AuthenticateResult&amp;gt; HandleAuthenticateAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (!Request.Headers.ContainsKey(API_KEY_HEADER))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; AuthenticateResult.Fail(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Header Not Found.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; apiKeyToValidate = Request.Headers[API_KEY_HEADER];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; apiKey = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _context.UserApiKeys
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .Include(uak =&amp;gt; uak.User)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .SingleOrDefaultAsync(uak =&amp;gt; uak.Value == apiKeyToValidate);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (apiKey == &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; AuthenticateResult.Fail(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Invalid key.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; AuthenticateResult.Success(CreateTicket(apiKey.User));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; AuthenticationTicket CreateTicket(IdentityUser user)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; claims = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt;[] {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(ClaimTypes.NameIdentifier, user.Id),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(ClaimTypes.Name, user.UserName),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Claim(ClaimTypes.Email, user.Email)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; identity = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; ClaimsIdentity(claims, Scheme.Name);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; principal = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; ClaimsPrincipal(identity);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; ticket = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; AuthenticationTicket(principal, Scheme.Name);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; ticket;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The first thing to note is how we&amp;rsquo;ve defined the base class: &lt;code&gt;AuthenticationHandler&amp;lt;AuthenticationSchemeOptions&amp;gt;&lt;/code&gt;. &lt;code&gt;AuthenticationHandler&lt;/code&gt; is a generic, and it allows us to control the type of options object that it accepts. In this case, we&amp;rsquo;ve used &lt;code&gt;AuthenticationSchemeOptions&lt;/code&gt;, which is just a default one already provided by the framework. This is because we don&amp;rsquo;t really need any custom options.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If we did, then we would define a new class that inherits from &lt;code&gt;AuthenticationSchemeOptions&lt;/code&gt;, give it the new fields that we need, and use that as a generic time parameter instead. We would then be able to set values for these new fields during configuration in the app&amp;rsquo;s &lt;code&gt;Startup&lt;/code&gt; class. Just like we&amp;rsquo;ve done with the &amp;ldquo;Jwt Bearer&amp;rdquo; auth scheme via the &lt;code&gt;AddJwtBearer&lt;/code&gt; method.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;The next point of interest in this class is its constructor. The key aspect of it is that it calls base and passes it a series of parameters that it itself gets. This is because &lt;code&gt;AuthenticationHandler&lt;/code&gt; does have some constructor logic that needs to be run for things to work properly, so we make sure to do that.&lt;/p&gt;
&lt;p&gt;Finally, there&amp;rsquo;s the &lt;code&gt;HandleAuthenticateAsync&lt;/code&gt; method which does the actual authentication. This method inspects the incoming request looking for an &amp;ldquo;Api-Key&amp;rdquo; header. If it finds it, it takes its value and makes a query into the database to try and find a record in the &lt;code&gt;user_api_keys&lt;/code&gt; table that matches the incoming value. If it finds that, it creates an &lt;code&gt;AuthenticationTicket&lt;/code&gt; which contains the identity of the user that the key belongs to, and returns it wrapped in a &lt;code&gt;AuthenticateResult.Success&lt;/code&gt;. That return value signals ASP.NET Core&amp;rsquo;s authentication middleware that the request is authentic. Otherwise, it returns &lt;code&gt;AuthenticateResult.Fail&lt;/code&gt;, which prompts ASP.NET Core to halt the request and return a 401.&lt;/p&gt;
&lt;p&gt;Something interesting to note is how the &lt;code&gt;AuthenticationTicket&lt;/code&gt;, similarly to the JWT Bearer scheme&amp;rsquo;s &lt;code&gt;JwtSecurityToken&lt;/code&gt;, also includes an array of &amp;ldquo;claims&amp;rdquo; that serve to identify the user that has been authenticated. The &amp;ldquo;claims&amp;rdquo; concept is central to how auth works in ASP.NET Core.&lt;/p&gt;
&lt;h5 id=&#34;enabling-and-configuring-the-api-key-authentication&#34;&gt;Enabling and configuring the API Key authentication&lt;/h5&gt;
&lt;p&gt;Now we need to tell the framework that we want it to use a new authentication scheme for our app, spearheaded by the custom authentication handler that we just wrote. There are two steps to make that happen.&lt;/p&gt;
&lt;p&gt;First, we configure our new scheme in the app&amp;rsquo;s &lt;code&gt;Startup&lt;/code&gt; class. For that, we need these two new &lt;code&gt;using&lt;/code&gt; statements:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Authentication&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.Authentication.ApiKey&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then we add this right after the &lt;code&gt;AddJwtBearer&lt;/code&gt; call in the &lt;code&gt;ConfigureServices&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; public void ConfigureServices(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     // ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     services
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         .AddJwtBearer(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             options.TokenValidationParameters = new TokenValidationParameters()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 ValidateIssuer = true,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 ValidateAudience = true,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 ValidateLifetime = true,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 ValidateIssuerSigningKey = true,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 ValidAudience = Configuration[&amp;#34;Jwt:Audience&amp;#34;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 ValidIssuer = Configuration[&amp;#34;Jwt:Issuer&amp;#34;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 IssuerSigningKey = new SymmetricSecurityKey(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     Encoding.UTF8.GetBytes(Configuration[&amp;#34;Jwt:Key&amp;#34;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        .AddScheme&amp;lt;AuthenticationSchemeOptions, ApiKeyAuthenticationHandler&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+            &amp;#34;ApiKey&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+            options =&amp;gt; { }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        );
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A simple configuration as far as auth schemes go. We specify both the types of the authentication handler and the options object that it accepts via the generic type parameters to the &lt;code&gt;AddScheme&lt;/code&gt; method. &lt;code&gt;&amp;quot;ApiKey&amp;quot;&lt;/code&gt; is just the name we&amp;rsquo;ve given our custom auth scheme. It can be anything as long as it&amp;rsquo;s not &amp;ldquo;Bearer&amp;rdquo;, which is the name of the Jwt Bearer scheme (which is the value returned by &lt;code&gt;JwtBearerDefaults.AuthenticationScheme&lt;/code&gt;). We&amp;rsquo;ll refer back to it later. Finally, since we have no special options to give it, we specify a lambda that does nothing with its options.&lt;/p&gt;
&lt;h5 id=&#34;updating-the-authorize-attribute-to-use-our-two-schemes&#34;&gt;Updating the Authorize attribute to use our two schemes&lt;/h5&gt;
&lt;p&gt;Now that the auth scheme is configured, we need to use it to secure an endpoint. We can use the same that we used for JWT: &lt;code&gt;POST api/BodyTypes&lt;/code&gt;, defined in &lt;code&gt;BodyTypesController&lt;/code&gt;&amp;rsquo;s &lt;code&gt;GetBodyTypes&lt;/code&gt; action method. Remember though, that we wanted to have both auth schemes (JWT and API Key) work on the endpoint. For that, the &lt;code&gt;Authorize&lt;/code&gt; attribute allows a comma separated string of scheme names as a parameter. So, we can get both our configured schemes working if we update the attribute like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;[Authorize(AuthenticationSchemes = $&amp;#34;{JwtBearerDefaults.AuthenticationScheme},ApiKey&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; Task&amp;lt;ActionResult&amp;lt;IEnumerable&amp;lt;BodyType&amp;gt;&amp;gt;&amp;gt; GetBodyTypes()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _context.BodyTypes.ToListAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;JwtBearerDefaults.AuthenticationScheme&lt;/code&gt; contains the name of the JWT Bearer auth scheme. Next to it, we just put &amp;ldquo;ApiKey&amp;rdquo;, which is the name we have given our new custom one.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&amp;rsquo;d be nice to put that &amp;ldquo;ApiKey&amp;rdquo; string somewhere safe similar to how the &amp;ldquo;Jwt Bearer&amp;rdquo; scheme has it in a constant. Take a look at the &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-5-web-api/tree/json-web-token&#34;&gt;finished project on GitHub&lt;/a&gt; to see &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-5-web-api/commit/8e9432068bfad977980c7c28702773a25ee293b8&#34;&gt;one way to organize that&lt;/a&gt; and also how to &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-5-web-api/commit/05138e927986fc191d4a042a3455ed1ee1947b8f&#34;&gt;make the &lt;code&gt;Startup&lt;/code&gt; configuration a little less verbose&lt;/a&gt; by using extension methods.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;And finally, let&amp;rsquo;s test our endpoint and marvel at the fruit of our work.&lt;/p&gt;
&lt;p&gt;Make sure to create a new API Key by POSTing to &lt;code&gt;api/Users/ApiKey&lt;/code&gt;, and copy the &amp;ldquo;value&amp;rdquo; from the response. We can use it as the value for the &lt;code&gt;Api-Key&lt;/code&gt; header a GET request to &lt;code&gt;api/BodyTypes&lt;/code&gt;. In Postman, we can do that by choosing the &amp;ldquo;Authorization&amp;rdquo; tab, selecting &amp;ldquo;API Key&amp;rdquo; in the &amp;ldquo;Type&amp;rdquo; drop-down list, writing &amp;ldquo;Api-Key&amp;rdquo; in the &amp;ldquo;Key&amp;rdquo; text box, and putting our key in the &amp;ldquo;Value&amp;rdquo; text box.&lt;/p&gt;
&lt;p&gt;With that, we can make the request and see how the endpoint now allows authentication via API Keys:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/implementing-authentication-in-asp.net-core-web-apis/authorized-response-api-key.webp&#34; alt=&#34;Successful response for request authenticated with API Key&#34;&gt;&lt;/p&gt;
&lt;p&gt;Of course, requests authenticated via JWT will also work for this endpoint.&lt;/p&gt;
&lt;h3 id=&#34;thats-all&#34;&gt;That&amp;rsquo;s all&lt;/h3&gt;
&lt;p&gt;And there you have it! We&amp;rsquo;ve updated an existing ASP.NET Core Web API application so that it supports authentication using two strategies: JWT and API Keys. We leveraged the Identity libraries to securely store and manage user accounts. We used ASP.NET Core&amp;rsquo;s built-in authentication capabilities to enable JWT generation and usage. For API Keys, the framework didn&amp;rsquo;t provide an implementation out of the box. However, it proved to be extensible enough so that we could implement our own.&lt;/p&gt;
&lt;h3 id=&#34;table-of-contents&#34;&gt;Table of contents&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#two-approaches-to-authentication-jwt-and-api-keys&#34;&gt;Two approaches to authentication: JWT and API Keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#managing-user-accounts-with-aspnet-core-identity&#34;&gt;Managing user accounts with ASP.NET Core Identity&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#install-the-necessary-nuget-packages&#34;&gt;Install the necessary NuGet packages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#update-the-dbcontext-to-include-the-identity-tables&#34;&gt;Update the DbContext to include the Identity tables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#applying-database-changes&#34;&gt;Applying database changes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#configuring-the-identity-services&#34;&gt;Configuring the Identity services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#creating-users&#34;&gt;Creating users&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#fetching-users&#34;&gt;Fetching users&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#implementing-jwt-bearer-token-authentication&#34;&gt;Implementing JWT Bearer Token authentication&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#creating-tokens&#34;&gt;Creating tokens&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#the-request-data-structure&#34;&gt;The request data structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#the-response-data-structure&#34;&gt;The response data structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#the-class-that-creates-jwts&#34;&gt;The class that creates JWTs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#the-action-method-that-puts-it-all-together&#34;&gt;The action method that puts it all together&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#securing-an-endpoint-with-jwt-authentication&#34;&gt;Securing an endpoint with JWT authentication&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#applying-the-authorize-attribute&#34;&gt;Applying the Authorize attribute&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#enabling-and-configuring-the-jwt-authentication&#34;&gt;Enabling and configuring the JWT authentication&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#implementing-api-key-authentication&#34;&gt;Implementing API Key authentication&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#creating-api-keys&#34;&gt;Creating API Keys&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#the-api-key-model-and-database-table&#34;&gt;The API Key model and database table&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#the-class-that-creates-api-keys&#34;&gt;The class that creates API Keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#the-action-method-that-puts-it-all-together-1&#34;&gt;The action method that puts it all together&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#securing-an-endpoint-with-api-key-authentication&#34;&gt;Securing an endpoint with API Key authentication&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#implementing-the-api-key-authentication-handler&#34;&gt;Implementing the API Key authentication handler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#enabling-and-configuring-the-api-key-authentication&#34;&gt;Enabling and configuring the API Key authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#updating-the-authorize-attribute-to-use-our-two-schemes&#34;&gt;Updating the Authorize attribute to use our two schemes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thats-all&#34;&gt;That&amp;rsquo;s all&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Using pgTAP to automate database testing</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/03/using-pgtap-automate-database-testing/"/>
      <id>https://www.endpointdev.com/blog/2022/03/using-pgtap-automate-database-testing/</id>
      <published>2022-03-16T00:00:00+00:00</published>
      <author>
        <name>Josh Tolley</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/03/using-pgtap-automate-database-testing/piano.webp&#34; alt=&#34;Old piano outdoors, focused on keyboard with most keytops missing and some snow on it&#34;&gt;
Photo from &lt;a href=&#34;https://pxhere.com/en/photo/1292458&#34;&gt;PxHere&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Recently I started learning to tune pianos. There are many techniques and variations, but the traditional method, and the one apparently most accepted by ardent piano tuning purists, involves tuning one note to a reference, tuning several other notes in relation to the first, and testing the results by listening closely to different combinations of notes.&lt;/p&gt;
&lt;p&gt;The tuner adjusts each new note in relation to several previously tuned notes. Physics being what it is, no piano can play all its tones perfectly, and one of the tricks of it all is adjusting each note to minimize audible imperfections. The tuner achieves this with an exacting series of musical intervals tested against each other.&lt;/p&gt;
&lt;h3 id=&#34;databases-need-tests-too&#34;&gt;Databases need tests too&lt;/h3&gt;
&lt;p&gt;One of our customers needed to add security policies to their PostgreSQL database, to limit data visibility for certain new users. This can quickly become complicated and ticklish, ensuring that the rules work properly for the affected users while leaving other users unmolested.&lt;/p&gt;
&lt;p&gt;This struck me as an excellent opportunity to create some unit tests, not that there&amp;rsquo;s any short supply of good opportunities to add unit tests! This is not just because it helps prove that these security policies really do work properly, but because (confession time) I recently did a similar project for a different database without the help of unit tests, and it wasn&amp;rsquo;t much fun.&lt;/p&gt;
&lt;p&gt;So this seemed like a good time to use &lt;a href=&#34;https://pgtap.org/&#34;&gt;pgTAP&lt;/a&gt;, a set of database functions designed to allow writing unit tests within the database itself. They produce &lt;a href=&#34;http://testanything.org/&#34;&gt;&amp;ldquo;Test Anything Protocol&amp;rdquo; (TAP)&lt;/a&gt; output, a simple protocol that displays unit test results in an easily understood report.&lt;/p&gt;
&lt;h3 id=&#34;what-to-test&#34;&gt;What to test?&lt;/h3&gt;
&lt;p&gt;A good first step in writing unit tests is deciding on something to test. In my case, I figured I should make sure row-level security policies were turned on for the tables I was interested in, which is available from the &lt;code&gt;rowsecurity&lt;/code&gt; field in the &lt;code&gt;pg_tables&lt;/code&gt; view:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;select&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;ok(rowsecurity,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;tablename&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;||&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39; has row security enabled&amp;#39;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;pg_tables&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;where&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;schemaname&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;public&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;and&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;            &lt;/span&gt;tablename&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;in&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;                &lt;/span&gt;&lt;span style=&#34;color:#888&#34;&gt;-- ... some hard coded table names
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;            &lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;or&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;tablename&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;like&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;some_other_tables_%&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;ok()&lt;/code&gt; function comes from pgTAP. It counts as one test each time it&amp;rsquo;s called; the test passes when the first argument is true, and fails when the argument is something else. The second argument is an optional comment describing what&amp;rsquo;s being tested. Following a pretty common TAP-related naming convention, I put this in a file called &lt;code&gt;00-test.sql&lt;/code&gt; in a directory under the root of my project, simply called &lt;code&gt;t&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A more complicated set of tests could include several different files, where the numeric part of the name helps sort the tests in the desired run order, and the rest of the filename describes the subject of the tests within. But this will do just to get started. I can run it with &lt;code&gt;pg_prove&lt;/code&gt;, included with the pgTAP package:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pg_prove -d mydatabase t/00-test.sql&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;iteratively-improving&#34;&gt;Iteratively improving&lt;/h3&gt;
&lt;p&gt;This fails, for several reasons.&lt;/p&gt;
&lt;p&gt;First, I haven&amp;rsquo;t yet installed the pgTAP extension in my database, with &lt;code&gt;CREATE EXTENSION pgtap&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I also haven&amp;rsquo;t actually done anything in my test to run the code I&amp;rsquo;m testing. The actual code in this project consists of some database functions, which we need to run to create the database security policies, and I haven&amp;rsquo;t run any of them yet.&lt;/p&gt;
&lt;p&gt;And finally, pgTAP requires me to &amp;ldquo;plan&amp;rdquo; my tests first, or in other words, I need to inform pgTAP how many tests I plan to run, before I run them. It&amp;rsquo;s also nice to call &lt;code&gt;finish()&lt;/code&gt; so pgTAP can clean up after itself.&lt;/p&gt;
&lt;p&gt;I installed the pgTAP extension in my database, and modified the test as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;begin&lt;/span&gt;;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;\&lt;/span&gt;i&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;create_policy.&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sql&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;select&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;plan(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;);&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#888&#34;&gt;-- plan for a single test
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;select&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;ok(rowsecurity,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;tablename&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;||&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39; has row security enabled&amp;#39;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;pg_tables&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;where&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;schemaname&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;public&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;and&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;            &lt;/span&gt;tablename&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;in&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;                &lt;/span&gt;&lt;span style=&#34;color:#888&#34;&gt;-- ... some hard coded table names
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;            &lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;or&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;tablename&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;like&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;some_other_tables_%&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;);&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;select&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;finish();&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;rollback&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This wraps my test in a transaction, so that I can roll everything back to leave the database essentially as I found it. It also calls the actual code I&amp;rsquo;m testing, in &lt;code&gt;create_policy.sql&lt;/code&gt;, and plans one test. And it gives me this new failure:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t/m.sql .. All 1 subtests passed
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Test Summary Report
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-------------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t/m.sql (Wstat: 0 Tests: 226 Failed: 225)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Failed tests:  2-226
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Parse errors: Bad plan.  You planned 1 tests but ran 226.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Files=1, Tests=226,  1 wallclock secs ( 0.04 usr  0.00 sys +  0.03 cusr  0.01 csys =  0.08 CPU)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Result: FAIL&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The problem here is that each call to &lt;code&gt;ok()&lt;/code&gt; counts as one test, and my test apparently found 226 tables to check for row-level security. I can improve the planning like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;select&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;plan(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;count&lt;/span&gt;(*)::&lt;span style=&#34;color:#038&#34;&gt;integer&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;pg_tables&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;where&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;schemaname&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;public&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;and&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;            &lt;/span&gt;tablename&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;in&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;                &lt;/span&gt;&lt;span style=&#34;color:#888&#34;&gt;-- ... some hard coded table names
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;            &lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;or&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;tablename&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;like&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;some_other_tables_%&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;count()&lt;/code&gt; returns a &lt;code&gt;bigint&lt;/code&gt;, and &lt;code&gt;plan()&lt;/code&gt; expects &lt;code&gt;integer&lt;/code&gt;, so this requires a typecast, but is otherwise pretty simple. And now my tests pass:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;josh@here:~dw$ pg_prove -d nedss t/00-test.sql
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t/00-test.sql .. ok
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;All tests successful.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Files=1, Tests=226,  1 wallclock secs ( 0.03 usr  0.01 sys +  0.03 cusr  0.01 csys =  0.08 CPU)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Result: PASS&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;looking-back-and-ahead&#34;&gt;Looking back and ahead&lt;/h3&gt;
&lt;p&gt;Suffice it to say that pgTAP includes many functions similar to &lt;code&gt;ok()&lt;/code&gt;, to test various aspects of the database, its structure, and its behavior, and I&amp;rsquo;d recommend interested users review &lt;a href=&#34;https://pgtap.org/documentation.html&#34;&gt;the documentation&lt;/a&gt; for more details. I intended this post only as an introduction.&lt;/p&gt;
&lt;p&gt;In its completed state, my test suite comprised several tests ensuring various required preliminaries were in place, a few tests like the one above that check for necessary table-specific settings, others that ensure the affected roles were created, and finally some which create some sample data and use &lt;code&gt;SET ROLE&lt;/code&gt; to test the data visibility directly for roles with various policies applied.&lt;/p&gt;
&lt;p&gt;And to be honest, I was surprised at the sense of security that came over me with this completed test suite. As I mentioned, I&amp;rsquo;d done similar work previously, and knew that although I was confident in the code when it was written, that confidence came only through fairly extensive manual testing. I know very well the struggles of bit rot, and I knew it would be at least as hard to repeat that testing regimen by hand sometime down the road after a year or two.&lt;/p&gt;
&lt;p&gt;I also recognized that if I ever needed to set up similar policies again, I could use these tests themselves as a reference, because they show exactly how to run the code in question. Though of course I included that information in the project&amp;rsquo;s associated &lt;code&gt;README&lt;/code&gt; file as well &amp;hellip; right?&lt;/p&gt;
&lt;p&gt;Let us know if you&amp;rsquo;ve used pgTAP, and what effect it has had on your database development.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Using a YubiKey as authentication for an encrypted disk</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/03/disk-decryption-yubikey/"/>
      <id>https://www.endpointdev.com/blog/2022/03/disk-decryption-yubikey/</id>
      <published>2022-03-07T00:00:00+00:00</published>
      <author>
        <name>Zed Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/03/disk-decryption-yubikey/banner.jpg&#34; alt=&#34;Keys hanging on a wall&#34;&gt;
&lt;a href=&#34;https://unsplash.com/photos/C1P4wHhQbjM&#34;&gt;Image&lt;/a&gt; by &lt;a href=&#34;https://unsplash.com/@silas_crioco&#34;&gt;Silas Köhler&lt;/a&gt; on Unsplash&lt;/p&gt;
&lt;p&gt;Recently I built a small desktop computer to run applications that were a bit much for my laptop to handle, intending to bring it with me when I work outside my apartment. However, there was an immediate issue with this plan. Because this computer was intended for use with sensitive information/​source code, I needed to encrypt the disk, which meant that I&amp;rsquo;d need to enter a passphrase before I could boot it up.&lt;/p&gt;
&lt;p&gt;I didn’t really want to haul a keyboard and monitor around with me, so I came up with an alternative solution: using a YubiKey as my method of authentication. This allowed me to avoid the need to type a password without giving up security. In this post I&amp;rsquo;ll show you how you can do the same.&lt;/p&gt;
&lt;h3 id=&#34;preparation&#34;&gt;Preparation&lt;/h3&gt;
&lt;p&gt;First off, you need a YubiKey, if you don&amp;rsquo;t have one already. I ended up getting the &lt;a href=&#34;https://www.yubico.com/product/yubikey-5c-nfc/&#34;&gt;YubiKey 5C NFC&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While I waited for my YubiKey to arrive, I installed Ubuntu 20.04 with full-disk encryption (using the default option of LUKS, or Linux Unified Key Setup) on the computer. I set a passphrase like normal—the process I describe in this post allows access with either this passphrase or the YubiKey.&lt;/p&gt;
&lt;p&gt;Next, there were two packages that I needed to configure everything:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/Yubico/yubikey-personalization&#34;&gt;yubikey-personalization&lt;/a&gt; allows you to change the settings on your YubiKey. I installed it from the Ubuntu repository and had no problems.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/cornelinux/yubikey-luks&#34;&gt;yubikey-luks&lt;/a&gt; is what lets you use the YubiKey as an authentication method for a LUKS setup. I initially installed this from Ubuntu’s repository as well, but the version they’ve got is fairly out of date and required both a YubiKey and passphrase instead of just the YubiKey. As I mentioned earlier, the main objective of setting this up was booting without a keyboard, so I installed the tool from source as detailed in its README.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;setup&#34;&gt;Setup&lt;/h3&gt;
&lt;p&gt;Once you’ve got the above libraries installed, setup is simple. Step by step:&lt;/p&gt;
&lt;h6 id=&#34;1-configure-your-yubikey-to-use-challenge-response-mode&#34;&gt;1. Configure your YubiKey to use challenge-response mode&lt;/h6&gt;
&lt;p&gt;A YubiKey has at least 2 &amp;ldquo;slots&amp;rdquo; for keys, depending on the model.&lt;/p&gt;
&lt;p&gt;We will change only the second YubiKey slot so you will still be able to use your YubiKey for two-factor auth like normal.&lt;/p&gt;
&lt;p&gt;Plug in your YubiKey and run the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ykpersonalize -2 -ochal-resp -ochal-hmac -ohmac-lt64 -oserial-api-visible&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h6 id=&#34;2-find-a-free-luks-slot-to-use-for-your-yubikey&#34;&gt;2. Find a free LUKS slot to use for your YubiKey&lt;/h6&gt;
&lt;p&gt;LUKS also allows for multiple key slots so that you can have different passphrases to unlock the encrypted data. Up to 8 key slots are available for LUKS1, and up to 32 for LUKS2.&lt;/p&gt;
&lt;p&gt;Most setups only use the first slot for the main passphrase, but we can check by following these steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First run &lt;code&gt;lsblk&lt;/code&gt; and figure out the name of your LUKS-encrypted disk partition. Mine was &lt;code&gt;nvme0n1p3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Now run &lt;code&gt;sudo cryptsetup luksDump /dev/nvme0n1p3&lt;/code&gt;. The output should look something like this:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LUKS header information
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Version:        2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Epoch:          11
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Metadata area:  [a smallish number] [bytes]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Keyslots area:  [a medium number] [bytes]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UUID:           [a UUID]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Label:          (no label)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Subsystem:      (no subsystem)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Flags:          (no flags)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Data segments:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  0: crypt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        offset: [a big number] [bytes]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        length: (whole device)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cipher: aes-xts-plain64
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        sector: 512 [bytes]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Keyslots:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  0: luks2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				[Lots of information about this slot]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Tokens:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Digests:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  0: pbkdf2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Hash:       sha256
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Iterations: 370259
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Salt:       [A bunch of bytes in hex format]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Digest:     [A bunch of bytes in hex format]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You&amp;rsquo;re looking specifically for a free keyslot, and the output here only shows anything in slot 0, so slot 1 should be free.&lt;/p&gt;
&lt;h6 id=&#34;3-assign-your-yubikey-to-a-free-slot&#34;&gt;3. Assign your YubiKey to a free slot&lt;/h6&gt;
&lt;p&gt;You can do this with the following command (substituting in your own partitition name and slot number):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo yubikey-luks-enroll -d /dev/nvme0n1p3 -s &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This command will ask you for a passphrase. It doesn&amp;rsquo;t need to be a particularly complex one, because it&amp;rsquo;ll only work with your YubiKey.&lt;/p&gt;
&lt;h6 id=&#34;4-update-crypttab-and-yklukscfg&#34;&gt;4. Update crypttab and ykluks.cfg&lt;/h6&gt;
&lt;p&gt;Now you need to add &lt;code&gt;keyscript=/usr/share/yubikey-luks/ykluks-keyscript&lt;/code&gt; to &lt;code&gt;/etc/crypttab&lt;/code&gt;. For example, mine started as:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nvme0n1p3_crypt &lt;span style=&#34;color:#369&#34;&gt;UUID&lt;/span&gt;=[uuid-here] none luks,discard&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After the change, it should look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nvme0n1p3_crypt &lt;span style=&#34;color:#369&#34;&gt;UUID&lt;/span&gt;=[uuid-here] none luks,discard,keyscript=/usr/share/yubikey-luks/ykluks-keyscript&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, you need to configure yubikey-luks to give the passphrase you just set so you don&amp;rsquo;t have to. Open &lt;code&gt;/etc/ykluks.cfg&lt;/code&gt; and add the line&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;YUBIKEY_CHALLENGE&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;[your new passphrase here]&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once you&amp;rsquo;ve added this line, run &lt;code&gt;sudo update-initramfs -u&lt;/code&gt; and you&amp;rsquo;re done!&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Now if you shut your machine off, plug in your YubiKey, and turn it on, it should boot all the way without needing a passphrase. If you forget to plug in the YubiKey before turning the computer on, you&amp;rsquo;ll probably need to hold the contact button on it for a second or two and then it should boot just the same.&lt;/p&gt;
&lt;p&gt;And there you go! A YubiKey provides neat way to securely start up a computer with an encrypted disk without needing a passphrase.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Fixing a PostgreSQL cluster that has no superuser</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/01/postgresql-no-superuser-fix/"/>
      <id>https://www.endpointdev.com/blog/2022/01/postgresql-no-superuser-fix/</id>
      <published>2022-01-07T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/01/postgresql-no-superuser-fix/20210721-081257-sm.jpg&#34; alt=&#34;Stone building with arched windows, a tower, steps leading up, and lush lawn, flowers, and trees&#34;&gt;&lt;/p&gt;
&lt;!-- photo by Jon Jensen --&gt;
&lt;p&gt;Normally in a newly-created PostgreSQL database cluster there is a single all-powerful administrative role (user) with &amp;ldquo;superuser&amp;rdquo; privileges, conventionally named &lt;code&gt;postgres&lt;/code&gt;, though it can have any name.&lt;/p&gt;
&lt;p&gt;After the initial cluster setup you can create other roles as needed. You may optionally grant one or more of your new roles the superuser privilege, but it is best to avoid granting superuser to any other roles because you and your applications should generally connect as roles with lower privilege to reduce the risk of accidental or malicious damage to your database.&lt;/p&gt;
&lt;h3 id=&#34;lets-break-something-&#34;&gt;Let&amp;rsquo;s break something 😎&lt;/h3&gt;
&lt;p&gt;Imagine you have a cluster with two or more superuser roles. If you accidentally remove superuser privilege from one role, you can simply connect as the other superuser and re-grant it.&lt;/p&gt;
&lt;p&gt;But if you have a cluster where only the single &lt;code&gt;postgres&lt;/code&gt; role is a superuser, what happens if you connect as that role and try to remove its superuser privilege?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ psql -U postgres postgres
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;psql (14.1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Type &amp;#34;help&amp;#34; for help.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postgres=# \du
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                       List of roles
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; Role name |            Attributes             | Member of
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-----------+-----------------------------------+-----------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; postgres  | Superuser, Create role, Create DB | {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; somebody  | Create DB                         | {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postgres=&amp;gt; \conninfo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;You are connected to database &amp;#34;postgres&amp;#34; as user &amp;#34;postgres&amp;#34; via socket in &amp;#34;/tmp&amp;#34; at port &amp;#34;5432&amp;#34;.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postgres=# ALTER ROLE postgres NOSUPERUSER;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ALTER ROLE
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postgres=# \du
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 List of roles
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; Role name |       Attributes       | Member of
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-----------+------------------------+-----------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; postgres  | Create role, Create DB | {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; somebody  | Create DB              | {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postgres=# ALTER ROLE postgres SUPERUSER;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ERROR:  must be superuser to alter superuser roles or change superuser attribute
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postgres=# \q&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;PostgreSQL happily lets us do that, and now we have &lt;strong&gt;no&lt;/strong&gt; superuser, and so we cannot re-grant the privilege to that role or any other!&lt;/p&gt;
&lt;h3 id=&#34;homebrew-postgresql-problem&#34;&gt;Homebrew PostgreSQL problem&lt;/h3&gt;
&lt;p&gt;Aside from such a severe operator error, there are other situations where you may find no superuser exists. One happened to me recently while experimenting with PostgreSQL installed by Homebrew on macOS.&lt;/p&gt;
&lt;p&gt;I used Homebrew to install &lt;code&gt;postgresql@14&lt;/code&gt; and later noticed that it left me with a single role named after my OS user, and it was not a superuser. It couldn&amp;rsquo;t even create other roles. I&amp;rsquo;m not sure how that happened, perhaps somehow caused by an earlier installation of &lt;code&gt;postgresql&lt;/code&gt; on the same computer, but so it was.&lt;/p&gt;
&lt;p&gt;Since there wasn&amp;rsquo;t any data in there yet, I could have simply deleted the existing PostgreSQL cluster and created a new one. But in other circumstances there could have been data in there that I needed to preserve, which wasn&amp;rsquo;t accessible to my one less-privileged user, and which caused errors in &lt;code&gt;pg_dumpall&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So how can we solve this problem the right way?&lt;/p&gt;
&lt;h3 id=&#34;first-stop-the-server&#34;&gt;First, stop the server&lt;/h3&gt;
&lt;p&gt;We need to get lower-level access to our database. To do that, first we stop the running database server.&lt;/p&gt;
&lt;p&gt;On a typical modern Linux server running systemd, that looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# systemctl stop postgresql-14&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On macOS using Homebrew services, that could be:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew services stop postgresql&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or in my experimental case with Homebrew using a temporary Postgres server which I&amp;rsquo;m showing here:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pg_ctl -D /opt/homebrew/var/postgresql@14 stop
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;waiting for server to shut down.... done
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;server stopped&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;next-start-the-postgresql-stand-alone-backend&#34;&gt;Next, start the PostgreSQL stand-alone backend&lt;/h3&gt;
&lt;p&gt;Next we start the &amp;ldquo;stand-alone backend&amp;rdquo; which a single user can interact with directly, not using a separate client.&lt;/p&gt;
&lt;p&gt;No privilege checks are done here, so we can re-grant the superuser privilege to our &lt;code&gt;postgres&lt;/code&gt; role.&lt;/p&gt;
&lt;p&gt;Interestingly, SQL statements entered here end with a newline, no &lt;code&gt;;&lt;/code&gt; needed, though adding one doesn&amp;rsquo;t hurt. And statements here can&amp;rsquo;t span multiple lines without being continued with &lt;code&gt;\&lt;/code&gt; at the end of each intermediate line.&lt;/p&gt;
&lt;p&gt;In the command below, note that the &lt;code&gt;--single&lt;/code&gt; option must come first, and the &lt;code&gt;postgres&lt;/code&gt; at the end of the command is the name of the database we want to connect to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ postgres --single -D /opt/homebrew/var/postgresql@14 postgres
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PostgreSQL stand-alone backend 14.1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;backend&amp;gt; ALTER ROLE postgres SUPERUSER
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2022-01-07 20:32:51.321 MST [27246] LOG:  statement: ALTER ROLE postgres SUPERUSER
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2022-01-07 20:32:51.322 MST [27246] LOG:  duration: 1.242 ms
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;backend&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;postgres&lt;/code&gt; server stand-alone prompt does not have all the niceties of &lt;code&gt;psql&lt;/code&gt; including line editing features, history, and backslash metacommands such as &lt;code&gt;\q&lt;/code&gt; to quit, so we type control-D there to mark &amp;ldquo;end of file&amp;rdquo; on our input stream and exit the program.&lt;/p&gt;
&lt;h3 id=&#34;back-to-normal&#34;&gt;Back to normal&lt;/h3&gt;
&lt;p&gt;Now we can again start the normal multi-user client/​server PostgreSQL service:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pg_ctl -D /opt/homebrew/var/postgresql@14 start
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;waiting for server to start.... done
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;server started&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And finally we can connect to the server and confirm our change persisted:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ psql -U postgres postgres
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;psql (14.1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Type &amp;#34;help&amp;#34; for help.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postgres=# \du
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                       List of roles
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; Role name |            Attributes             | Member of
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-----------+-----------------------------------+-----------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; postgres  | Superuser, Create role, Create DB | {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; somebody  | Create DB                         | {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postgres=#&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;reference&#34;&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.postgresql.org/docs/current/app-postgres.html#APP-POSTGRES-SINGLE-USER&#34;&gt;PostgreSQL single-user mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.postgresql.org/docs/current/sql-alterrole.html&#34;&gt;PostgreSQL ALTER ROLE documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Remote Access Control with AWS Security Groups</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2021/12/remote-access-control-aws/"/>
      <id>https://www.endpointdev.com/blog/2021/12/remote-access-control-aws/</id>
      <published>2021-12-23T00:00:00+00:00</published>
      <author>
        <name>Ardyn Majere</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2021/12/remote-access-control-aws/salt-flats.jpg&#34; alt=&#34;Salt flats with no trespassing sign in foreground, people beyond it, and mountains in the background&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Seth Jensen --&gt;
&lt;p&gt;With the onset of COVID-19, a lot of companies started remote work and had to allow their employees to access systems remotely while keeping unwanted traffic out.&lt;/p&gt;
&lt;p&gt;As a company of almost all remote workers, even pre-pandemic, we have long had a solution for this. We have a web application that allows people to register their current IP address with one or more servers to gain access to staging sites, SSH, SFTP, databases, and other services that aren’t open to the public.&lt;/p&gt;
&lt;p&gt;The application relies on host-based firewalls &lt;code&gt;iptables&lt;/code&gt; (Linux) or &lt;code&gt;pf&lt;/code&gt; (OpenBSD) to give people access to the system. For customers using cloud providers like AWS, we added support for this application to work with AWS Security Groups.&lt;/p&gt;
&lt;h3 id=&#34;the-basics&#34;&gt;The Basics&lt;/h3&gt;
&lt;p&gt;The system uses a single web application and relies on the &lt;a href=&#34;https://httpd.apache.org/docs/2.4/mod/mod_authn_file.html#authuserfile&#34;&gt;Apache &lt;code&gt;mod_authn_file&lt;/code&gt; format&lt;/a&gt; and &lt;code&gt;htpasswd&lt;/code&gt; command to create users and hashed passwords for the system. &lt;code&gt;htpasswd&lt;/code&gt; allows you to create bcrypt, MD5, SHA-1, and Unix crypt passwords. We suggest using bcrypt wherever possible, since it is the only one in that list which is not fairly easily reversible nowadays.&lt;/p&gt;
&lt;p&gt;The system also allows for grouping of users to allow them access to different sets of systems. These are handled by the simple &lt;a href=&#34;https://httpd.apache.org/docs/2.4/mod/mod_authz_groupfile.html&#34;&gt;Apache &lt;code&gt;mod_authz_groupfile&lt;/code&gt; format&lt;/a&gt; that looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;web: richard, joe, frank
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dbs: richard, frank&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The data format is the group name followed by a colon (&lt;code&gt;:&lt;/code&gt;), then a comma-separated list of users who should be allowed to access that service. In this example, the users &lt;code&gt;richard&lt;/code&gt;, &lt;code&gt;joe&lt;/code&gt;, and &lt;code&gt;frank&lt;/code&gt; would be allowed to access the &lt;code&gt;web&lt;/code&gt; group but only &lt;code&gt;richard&lt;/code&gt; and &lt;code&gt;frank&lt;/code&gt; would be allowed to access the &lt;code&gt;dbs&lt;/code&gt; group.&lt;/p&gt;
&lt;p&gt;The groups in this file then dictate what servers and services you are allowed to access. For example, the &lt;code&gt;dbs&lt;/code&gt; group might allow you ssh and database connectivity to the specific production and replication database systems in AWS. It does this by adding rules to the proper security groups in AWS via their API.&lt;/p&gt;
&lt;p&gt;We typically set up two different security groups that a set of servers can use: a static whitelist and a dynamic whitelist. The static whitelist is populated with IP addresses that should always be allowed in. This could be anything from a set of IP addresses for people still working at the office to employees’ dedicated IP addresses from their ISPs. This could also be certain other systems (remote data sources or such) that would need access to these systems. The dynamic whitelist is what the web application will add rules to after someone enters their proper username and password.&lt;/p&gt;
&lt;h3 id=&#34;the-api-layer&#34;&gt;The API layer&lt;/h3&gt;
&lt;p&gt;Now that we’ve reviewed the way to setup users and the way users can be organized into groups, we can talk about how the web application interfaces with AWS itself.&lt;/p&gt;
&lt;p&gt;After the user passes the username and password validation, the system will use the AWS API key to add the user to the appropriate security groups. We need certain information about each security group we are going to add people to: the Security Group ID, description, profile, and region. We also need to know what ports we are going to open for someone for this group.&lt;/p&gt;
&lt;p&gt;So using our previous example, if we are going to be granting access to the user &lt;code&gt;frank&lt;/code&gt; and giving him access to the web servers then we’d want to open up TCP &amp;amp; UDP (for HTTP/3!) ports 443 (https) and probably port 80 (http). The same goes with granting access to the database servers: give &lt;code&gt;frank&lt;/code&gt; access to whatever port the database allows you to connect on.&lt;/p&gt;
&lt;h3 id=&#34;the-security-group-layer&#34;&gt;The Security Group Layer&lt;/h3&gt;
&lt;p&gt;The secret sauce in this is interfacing with AWS’s security group.&lt;/p&gt;
&lt;p&gt;Create a new security group in the account—one per server or logical group (e.g. TCP port 22 for SSH on all servers).&lt;/p&gt;
&lt;p&gt;Then create a new IAM account. Go to the IAM menu and click on Groups in the side tab.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click “Create a Group”
&lt;ul&gt;
&lt;li&gt;Name the group &lt;code&gt;whitelist-user-group_id&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click “Create Policy”
&lt;ul&gt;
&lt;li&gt;Choose Service “Ec2”&lt;/li&gt;
&lt;li&gt;Set the following permissions:
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;List (4 of 123 actions)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DescribeSecurityGroupReferences
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DescribeSecurityGroups
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DescribeStaleSecurityGroups
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DescribeVpcs
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Write (4 of 285 actions)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;AuthorizeSecurityGroupEgress
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;AuthorizeSecurityGroupIngress
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;RevokeSecurityGroupEgress
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;RevokeSecurityGroupIngress&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Hint: You can find all but one of these by searching &amp;lsquo;SecurityGroup&amp;rsquo;, and the last one by searching for DescribeVpcs&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;The write functions you&amp;rsquo;ll need to attach to a specific security group or groups, which you created earlier.&lt;/p&gt;
&lt;p&gt;When you&amp;rsquo;re done, you should have:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;User&lt;/strong&gt; (&lt;code&gt;whitelist_groupid&lt;/code&gt;) → in &lt;strong&gt;group&lt;/strong&gt; (&lt;code&gt;whitelist_group_groupid&lt;/code&gt;) → with attached &lt;strong&gt;policy&lt;/strong&gt; (&lt;code&gt;pol-whitelist&lt;/code&gt;) → with the above eight permissions, the four latter ones pointed at your new security group.&lt;/p&gt;
&lt;p&gt;Attach the security group to your VM.&lt;/p&gt;
&lt;p&gt;You’ll need to make calls to the security groups over the API. You can find more info about the API call for changing firewall rules in &lt;a href=&#34;https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_UpdateSecurityGroupRuleDescriptionsIngress.html&#34;&gt;AWS’s docs&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;when-the-layers-combine&#34;&gt;When the layers combine&lt;/h3&gt;
&lt;p&gt;With these pieces in place, you can start thinking about what groups would give people access to what. How broad or granular you want to be is up to you! If you want to have a group that gives access to every system and all the ports (maybe for your operations team), go for it! If you want to have a group that just needs access to https on certain systems (like a sales and marketing team) then you can easily control that as well.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Generating TOTP QR codes as Unicode text from the command line</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2021/10/generating-qr-codes-as-unicode-text/"/>
      <id>https://www.endpointdev.com/blog/2021/10/generating-qr-codes-as-unicode-text/</id>
      <published>2021-10-28T00:00:00+00:00</published>
      <author>
        <name>Bharathi Ponnusamy</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2021/10/generating-qr-codes-as-unicode-text/banner.jpg&#34; alt=&#34;banner, qr code, Unicode, text, security, console, terminal, command line&#34;&gt;&lt;/p&gt;
&lt;!-- photo by Bharathi Ponnusamy --&gt;
&lt;p&gt;(QR = “Quick Response” — good to know!)&lt;/p&gt;
&lt;p&gt;Python’s QR code generator library &lt;a href=&#34;https://pypi.org/project/qrcode/&#34;&gt;qrcode&lt;/a&gt; generates QR codes from a secret key and outputs to a terminal using Unicode characters, not a PNG graphic as most other libraries do. We can store that in a text file. This is a neat thing to do, but how is this functionality useful?&lt;/p&gt;
&lt;h5 id=&#34;benefits-of-having-unicode-qr-code-as-a-text-file&#34;&gt;Benefits of having Unicode QR code as a text file:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;Storing the QR code as a text file takes less disk space than a PNG image.&lt;/li&gt;
&lt;li&gt;It is easy to read the QR code over ssh using the &lt;code&gt;cat&lt;/code&gt; command; you don&amp;rsquo;t even have to download the file to your own workstation.&lt;/li&gt;
&lt;li&gt;It is simpler to manage QR codes in Git as text files than as PNG images.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This can be used for any kind of QR code, but we have found it especially useful for managing shared multi-factor authentication (MFA, including 2FA for 2-factor authentication) secrets for TOTPs (Time-based One-Time Passwords).&lt;/p&gt;
&lt;h3 id=&#34;multi-factor-authentication-mfa&#34;&gt;Multi-factor authentication (MFA)&lt;/h3&gt;
&lt;p&gt;Many services provide a separate account and login for each user so that accounts do not need to be shared, and thus passwords and multi-factor authentication secrets do not need to be shared either. This is ideal, and what we insist on for our most important accounts.&lt;/p&gt;
&lt;p&gt;Unfortunately, however, some services provide only a single login per account, or only a single primary account login with the other accounts being limited in serious ways (no access to billing, account management, etc.) so that any business relying on them needs to share the access between several authorized users. A single point of failure in an account login is a serious problem when that one person is unavailable.&lt;/p&gt;
&lt;h3 id=&#34;totp-mobile-apps&#34;&gt;TOTP mobile apps&lt;/h3&gt;
&lt;p&gt;There are many good mobile apps for managing TOTP keys and codes, including Aegis, FreeOTP, Google Authenticator, and many others. Look for one that works with no connection to the outside world, so that you won’t be stuck when off internet &amp;amp; data networks.&lt;/p&gt;
&lt;p&gt;Most applications support scanning QR codes with the phone’s camera, or else typing in a secret key to import the accounts.&lt;/p&gt;
&lt;p&gt;For those shared accounts with no option for fully empowered individual user accounts, we can convert secret keys into QR codes for easy sharing and easy imports.&lt;/p&gt;
&lt;p&gt;First, note that you should never use online QR code generators for MFA secrets! You risk exposing your extra authentication factors and defeating the purpose of your extra work.&lt;/p&gt;
&lt;h3 id=&#34;pythons-qrcode-library&#34;&gt;Python’s &amp;lsquo;qrcode&amp;rsquo; library&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;qrcode&lt;/code&gt; Python library provides a &lt;code&gt;qr&lt;/code&gt; executable that can print your QR code using UTF-8 characters on the console.&lt;/p&gt;
&lt;h4 id=&#34;installation&#34;&gt;Installation&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;apt install python3-qrcode&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or visit &lt;a href=&#34;https://pypi.org/project/qrcode/&#34;&gt;https://pypi.org/project/qrcode/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The contents of the QR code are a URL in the format:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;otpauth://totp/{username}?secret={key}&amp;amp;issuer={provider_name}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;provider_name&lt;/code&gt; can contain spaces; however, they need to be URL-encoded and entered as %20 for auth to work correctly on iOS. Otherwise, an invalid barcode error will be shown when adding the code.&lt;/p&gt;
&lt;p&gt;For example, if you generate the QR code with key &lt;code&gt;JSZE5V4676DZFCUCFW4GLPAHEFDNY447&lt;/code&gt; for the account &lt;code&gt;root@example.com&lt;/code&gt;, the resulting command would be:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ qr &amp;#34;otpauth://totp/Example:root@example.com?secret=JSZE5V4676DZFCUCFW4GLPAHEFDNY447&amp;amp;issuer=Superhost&amp;#34; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here&amp;rsquo;s what its output looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/10/generating-qr-codes-as-unicode-text/qrcode.jpg&#34; alt=&#34;qrcode&#34;&gt;&lt;/p&gt;
&lt;p&gt;Providing the username and issuer will display it properly in the list of configured accounts in your authenticator application. For example: &lt;code&gt;Superhost (Example:root@example.com)&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&#34;reference&#34;&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;See a &lt;a href=&#34;http://www1.auth.iij.jp/smartkey/en/uri_v1.html&#34;&gt;simple explanation of otpauth URI format&lt;/a&gt; used by TOTP.&lt;/li&gt;
&lt;li&gt;See &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc6238&#34;&gt;RFC 6238&lt;/a&gt; for full details about TOTP.&lt;/li&gt;
&lt;li&gt;See &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc4648#section-6&#34;&gt;RFC 4648&lt;/a&gt; for the base 32 specification used to encode the secret key.&lt;/li&gt;
&lt;li&gt;A recent similar Perl implementation &lt;a href=&#34;https://github.polettix.it/ETOOBUSY/2021/09/26/text-qrcode-unicode/&#34;&gt;Terminal: QR Code with Unicode characters&lt;/a&gt; by Flavio Poletti that builds on &lt;code&gt;Text::QRCode&lt;/code&gt;, which uses &lt;code&gt;libqrencode&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Lock down your security with GPG on a YubiKey</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2021/09/gpg-keys-on-a-yubikey/"/>
      <id>https://www.endpointdev.com/blog/2021/09/gpg-keys-on-a-yubikey/</id>
      <published>2021-09-10T00:00:00+00:00</published>
      <author>
        <name>Ardyn Majere</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2021/09/gpg-keys-on-a-yubikey/banner.jpg&#34; alt=&#34;&#34;&gt;
&lt;a href=&#34;https://unsplash.com/photos/4hfpVsi-gSg&#34;&gt;Photo&lt;/a&gt; by &lt;a href=&#34;https://unsplash.com/@maurosbicego&#34;&gt;Mauro Sbicego&lt;/a&gt; on Unsplash&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://gnupg.org/&#34;&gt;Gnu Privacy Guard&lt;/a&gt; (GnuPG or GPG) is a tool we use a lot at End Point. Its ubiquity and quite decent security is a perfect fit for us — and there&amp;rsquo;s a way to make it even safer.&lt;/p&gt;
&lt;p&gt;GPG uses the OpenPGP standard to encrypt files. Normally, one creates a PGP key on their computer and just keeps the keyfile safe. A password is generally used, but as with any private key, it&amp;rsquo;s only as safe as the computer it&amp;rsquo;s on.&lt;/p&gt;
&lt;h3 id=&#34;got-a-yubikey-and-not-sure-what-to-do-with-it-want-to-get-a-little-more-secure-with-your-encryption&#34;&gt;Got a YubiKey and not sure what to do with it? Want to get a little more secure with your encryption?&lt;/h3&gt;
&lt;p&gt;In case you haven&amp;rsquo;t heard of them, &lt;a href=&#34;https://www.yubico.com/why-yubico/&#34;&gt;YubiKeys&lt;/a&gt; are hardware USB keys that can be used as a multi-factor authentication (MFA) token, or to fill in one-time password (OTP) fields (like those generated by Google Authenticator) on sites that don&amp;rsquo;t support the YubiKey directly as an MFA token.&lt;/p&gt;
&lt;p&gt;Using a smart card like a YubiKey can increase GPG&amp;rsquo;s security, especially if the key is generated on an &lt;a href=&#34;https://en.wikipedia.org/wiki/Air_gap_(networking)&#34;&gt;air-gapped&lt;/a&gt; machine. This way the keyfile is stored in the hardware security token, and is never exposed to the internet.&lt;/p&gt;
&lt;p&gt;In addition, you can even store an SSH key on the card, which will enable you to log in to remote Linux machines while keeping your private key secured.&lt;/p&gt;
&lt;p&gt;While there isn&amp;rsquo;t full password locking on hardware tokens, YubiKey and almost all OpenPGP keys have two PINs — a user PIN and an administrative PIN to reset the user PIN. If you enter either or both three times incorrectly, the card will lock and you&amp;rsquo;ll need to reload from backup (or, in some cases, throw the card away) which is why it&amp;rsquo;s critical to have a backup.&lt;/p&gt;
&lt;p&gt;There are several options for smart cards beyond the YubiKey. You can use any OpenPGP compatible card and reader, or an all-in-one solution that&amp;rsquo;s compatible with OpenPGP.&lt;/p&gt;
&lt;p&gt;The following instructions do require a basic understanding of the command line and of how to create a live CD/​USB stick, but if you need to use GPG, you&amp;rsquo;re probably already at least somewhat familiar with these requirements.&lt;/p&gt;
&lt;p&gt;An air-gapped machine isn&amp;rsquo;t required for these instructions. You could do this on any machine you trust, but using a machine with a fresh OS that hasn&amp;rsquo;t been connected to the internet affords the highest level of security. Simply booting from a live CD/​USB is fairly easy. Choosing an operating sytem that comes with the smart card daemon (&lt;a href=&#34;https://linux.die.net/man/1/scdaemon&#34;&gt;scdaemon&lt;/a&gt;) will help. If you don&amp;rsquo;t, make sure you download the scdaemon package for your operating system to use with the live CD/​USB.&lt;/p&gt;
&lt;h3 id=&#34;prepare&#34;&gt;Prepare&lt;/h3&gt;
&lt;p&gt;Before you begin, you&amp;rsquo;ll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A smart card solution as described above.&lt;/li&gt;
&lt;li&gt;A backup smart card, or external media on which to store an encrypted copy of the key.&lt;/li&gt;
&lt;li&gt;A machine on which to generate the key.&lt;/li&gt;
&lt;li&gt;A live OS. For this demonstration I used &lt;a href=&#34;https://tails.boum.org/&#34;&gt;Tails&lt;/a&gt;, The Amnesic Incognito Live System. It&amp;rsquo;s specifically designed to not store logs or keep data from one reboot to another.&lt;/li&gt;
&lt;li&gt;A way to access these instructions, like a second computer, phone, printout, or a very good memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Boot up the live machine. Note that if you&amp;rsquo;re using Tails, there are two settings you&amp;rsquo;ll need to choose on the welcome screen. Click the plus button and do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set an administrative password. Tails doesn&amp;rsquo;t set a root password by default, and thus disallows root access for better security. You can set one yourself.&lt;/li&gt;
&lt;li&gt;Disable networking in the &amp;lsquo;Network Connection&amp;rsquo; section.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;access-and-configure-the-card&#34;&gt;Access and configure the card&lt;/h3&gt;
&lt;p&gt;Once booted, run an admin terminal, or load a terminal and run &lt;code&gt;sudo -i&lt;/code&gt;. It&amp;rsquo;ll prompt you for the password you just set.&lt;/p&gt;
&lt;p&gt;Ensure you can access the card and that the smart card daemon is installed by running &lt;code&gt;gpg --edit-card&lt;/code&gt;. It should display information about your smart card.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you&amp;rsquo;re using an air-gapped machine and your live OS is missing requisite packages, don&amp;rsquo;t access the internet with the machine in order to install it, since that would break the air gap. Instead, copy the installation files across using sneakernet: Add the files to a USB drive, perhaps the one you&amp;rsquo;ll be using to back up your PGP key). The packages for a Debian-based machine are: &lt;code&gt;scdaemon&lt;/code&gt;, &lt;code&gt;libccid&lt;/code&gt;, &lt;code&gt;pcscd&lt;/code&gt;, &lt;code&gt;rng-tools&lt;/code&gt;, and &lt;code&gt;gnupg2&lt;/code&gt;. Debian has &lt;a href=&#34;https://www.debian.org/doc/manuals/apt-offline/index.en.html&#34;&gt;instructions&lt;/a&gt; on how to get packages to an air-gapped machine.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Set a PIN for your card, if you haven&amp;rsquo;t already. Start with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ gpg --edit-card&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The default PIN for a YubiKey should be 123456, and the default admin PIN should be 12345678. Check the documentation that came with your key, though!&lt;/p&gt;
&lt;p&gt;Enable admin features first:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gpg/card&amp;gt; admin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This should return &lt;code&gt;Admin commands are allowed&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Set the passwords, both for the regular PIN and the admin PIN:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gpg/card&amp;gt; passwd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1 - change PIN &amp;lt;- Default 123456
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2 - unblock PIN &amp;lt;- To reset the pin with the AdminPin / Reset Code
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;3 - change Admin PIN &amp;lt;- Default 12345678
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;4 - set the Reset Code &amp;lt;- See below
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Q - quit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Do not mix up your PIN and admin PIN! You can lock up your card, which will require a factory reset.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The reset code is set if you are setting up the card for someone else to use, and wish to give them a way to reset the PIN without having full access to the rest of the admin functions.&lt;/p&gt;
&lt;h3 id=&#34;generate-the-keys&#34;&gt;Generate the keys&lt;/h3&gt;
&lt;p&gt;Run the following to generate the key:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ gpg --expert --full-gen-key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Key type:&lt;/strong&gt; 1 (RSA &amp;amp; RSA). (You can also use &amp;ldquo;ECC &amp;amp; ECC&amp;rdquo; if you&amp;rsquo;re brave. These types of keys may not work with older systems and implementations of GPG, however, so your mileage may vary. If you do want to use ECC &amp;amp; ECC, use Curve 25519 in the next step.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key size:&lt;/strong&gt; this should be the maximum supported by your key. YubiKey 4 or 5 can support up to 4096. Use this for both main and subkey.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expiry:&lt;/strong&gt; This is your choice. I&amp;rsquo;d set it to a year or two.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real name, email, and comment:&lt;/strong&gt; I recommend leaving the comment blank, since most of the time the email address will be enough information.&lt;/li&gt;
&lt;li&gt;Next, GPG will ask you to move your mouse around — don&amp;rsquo;t sprain anything while generating entropy!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now your key is generated. If you only have one spare storage device which you want to use for backups, copy the revocation certificate and the SSH public key to storage, sneak this on to your main computer, and only then copy the keys over to the backup drive. Don&amp;rsquo;t attach the backup drive to anything but an air-gapped machine once it holds your key!&lt;/p&gt;
&lt;p&gt;You might as well generate an SSH key now. Even if you don&amp;rsquo;t use it, there&amp;rsquo;s no harm in having it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ gpg --expert --edit-key &amp;lt;your email/key id&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;gpg/card&amp;gt; addkey&lt;/code&gt; to add a key.&lt;/li&gt;
&lt;li&gt;Type 8, RSA (set your own capabilities). If this option doesn&amp;rsquo;t show up, ensure you used &lt;code&gt;--expert&lt;/code&gt; above.&lt;/li&gt;
&lt;li&gt;Enable authentication and disable signing and encrypting — type &lt;code&gt;s&lt;/code&gt;, &lt;code&gt;e&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, then &lt;code&gt;q&lt;/code&gt; to save.&lt;/li&gt;
&lt;li&gt;4096 bits is the best number to use, at least for RSA on modern YubiKeys. If you have an older key you may be limited to 3072 or 2048.&lt;/li&gt;
&lt;li&gt;You can choose to have the key expire, but ensure you have a backup method of logging in.&lt;/li&gt;
&lt;li&gt;Confirm your choices, then quit, confirming you want to save.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;For more in-depth instructions, visit &lt;a href=&#34;https://opensource.com/article/19/4/gpg-subkeys-ssh&#34;&gt;https://opensource.com/article/19/4/gpg-subkeys-ssh&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Export the public keys:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ gpg --export --armor &amp;lt;key ID&amp;gt; &amp;gt; /path/to/thumbdrive/&amp;lt;email&amp;gt;_pub.asc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ gpg --export-ssh &amp;lt;key ID&amp;gt; &amp;gt; /path/to/thumbdrive/YubiKey_id_rsa.pub&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;move-the-keys-to-your-card&#34;&gt;Move the keys to your card&lt;/h3&gt;
&lt;p&gt;Once you have the key added to your keyring, you&amp;rsquo;ll need to transfer that key to your card:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ gpg --edit-key &amp;lt;key id&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To export the public SSH key you&amp;rsquo;ll need to put on remote servers, you can run the command: &lt;code&gt;gpg --export-ssh-key 0x123456789ABCDE&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gpg&amp;gt; keytocard&lt;/code&gt; — confirm you want to move the primary key and store this in position 1 of the card.
&lt;ul&gt;
&lt;li&gt;To select the encryption key, type &lt;code&gt;key 1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;keytocard&lt;/code&gt; to store the encryption key in the encryption slot.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If you have an encryption key:
&lt;ul&gt;
&lt;li&gt;To select the authentication key, run &lt;code&gt;key 2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To deselect the key first key, run &lt;code&gt;key 1&lt;/code&gt;. You should only have one key with &lt;code&gt;*&lt;/code&gt; marking it.&lt;/li&gt;
&lt;li&gt;Store in the authentication slot: &lt;code&gt;keytocard&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Repeat this for as many subkeys as you have.&lt;/li&gt;
&lt;li&gt;Once you&amp;rsquo;re done, quit and confirm the saved changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Time to test your new key!&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s it. Publish your new public GPG key, use your new SSH key, secure in the knowledge that your private key is protected from malicious attacks by an additional hardware layer.&lt;/p&gt;
&lt;h3 id=&#34;what-do-i-do-if-it-all-went-wrong-or-i-locked-up-the-card&#34;&gt;What do I do if it all went wrong or I locked up the card?&lt;/h3&gt;
&lt;p&gt;You can start again. The following command will restore the GPG-compatible portion of your YubiKey to factory settings. You will lose any keys stored on the card. I don&amp;rsquo;t believe it&amp;rsquo;ll cause any OTP/2FA set up with the card to be lost, but I make no guarantees.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ONLY RUN THE FOLLOWING IF YOUR PIN IS LOCKED AND THE SMART CARD IS UNUSABLE:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ gpg --edit-card
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gpg/card&amp;gt; factory-reset&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Follow the confirmation steps on screen.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Media erasure in the time of SSD</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/12/drive-destruction/"/>
      <id>https://www.endpointdev.com/blog/2020/12/drive-destruction/</id>
      <published>2020-12-10T00:00:00+00:00</published>
      <author>
        <name>Ardyn Majere</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2020/12/drive-destruction/garbage.jpg&#34; alt=&#34;&#34;&gt;
&lt;a href=&#34;https://www.pexels.com/photo/garbage-lot-2967770/&#34;&gt;Photo&lt;/a&gt; by &lt;a href=&#34;https://www.pexels.com/@alexfu&#34;&gt;Alex Fu&lt;/a&gt; from Pexels&lt;/p&gt;
&lt;p&gt;How valuable is your data? Losing it to a third party is usually a business’s worst nightmare—​and can cause legal or even criminal repercussions, depending on the drive’s contents and the business’s jurisdiction.&lt;/p&gt;
&lt;p&gt;Every system adminstrator worth their salt knows that running “rm” (or equivalent delete operations) doesn’t actually remove data, it simply removes the file name from the filesystem and leaves the data in place on the disk.&lt;/p&gt;
&lt;p&gt;When dealing with traditional storage, destroying (intentionally or otherwise) your data used to be relatively easy. A wise system admin could simply run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;shred /dev/sda&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And be fairly certain of the result. A cautious one might run a demagnetizing wand over the drive. Only the most paranoid might destroy it physically.&lt;/p&gt;
&lt;h3 id=&#34;the-age-of-ssds&#34;&gt;The Age of SSDs&lt;/h3&gt;
&lt;p&gt;Nowadays, most servers have switched away from storing data on rotating metal or glass platters. Solid state drives, or SSDs, are faster, less prone to errors from physical impact, and generally more sought after.&lt;/p&gt;
&lt;p&gt;SSDs have issues with speed if the drives are too full, and have a limited lifespan—​only a certain number of write operations can be achieved. This is less of an issue with modern drives thanks to wear leveling built into the firmware of the drives. However, this leads to some issues as well.&lt;/p&gt;
&lt;h3 id=&#34;complicated-systems-introduce-issues&#34;&gt;Complicated systems introduce issues&lt;/h3&gt;
&lt;p&gt;Because SSDs manage which blocks of storage they write to, a simple shred won’t do. There could be hundreds of bytes, or even kilobytes or megabytes, of data that the shred doesn’t reach.&lt;/p&gt;
&lt;p&gt;Even some “traditional” storage can run into such issues these days. Hybrid drives offer some speed advantages: By leveraging a small amount of SSD storage, these drives save data to SSD first, then write it at slower speeds to the actual magnetic platters. The same issues with SSD storage can affect this cache of data.&lt;/p&gt;
&lt;p&gt;So how to be sure?&lt;/p&gt;
&lt;p&gt;Ideally, I would recommend using a combination of methods for security. Here are the main methods that are used at present:&lt;/p&gt;
&lt;h3 id=&#34;run-shred-and-hope-for-the-best&#34;&gt;Run shred and hope for the best&lt;/h3&gt;
&lt;p&gt;This is an option, for sure. Writing to every block of the disk will generally wipe the data securely enough. However, if there are sectors that have been marked bad by the drive firmware, these won’t be covered.&lt;/p&gt;
&lt;h3 id=&#34;nwipe-dban-and-other-free-options&#34;&gt;nwipe, DBAN, and other free options&lt;/h3&gt;
&lt;p&gt;Free software exists to securely run shred over operating systems. The old gold standard for this was Darik’s Boot and Nuke (DBAN), written by Darik Horn, but this software was acquired by Blancco, a for-profit data erasure company (more on them later). DBAN is &lt;a href=&#34;https://dban.org/&#34;&gt;still available&lt;/a&gt;, but lacks features necessary for a 100% wipe.&lt;/p&gt;
&lt;p&gt;A fork called &lt;a href=&#34;https://github.com/martijnvanbrummelen/nwipe&#34;&gt;nwipe&lt;/a&gt; exists, and is available on many live operating systems. nwipe does a more thorough job than shred, but it still can’t get to sectors the firmware hides from the operating system.&lt;/p&gt;
&lt;p&gt;Wikipedia has a &lt;a href=&#34;https://en.wikipedia.org/wiki/List_of_data-erasing_software&#34;&gt;list of data-erasing software&lt;/a&gt;. Most of these are open source or freeware, but it includes a few paid options.&lt;/p&gt;
&lt;h3 id=&#34;sata-secure-erase-with-hdparm&#34;&gt;SATA secure erase with hdparm&lt;/h3&gt;
&lt;p&gt;Drive manufacturers have thought of this issue as well. Most drive manufacturers offer a secure erase program that works with their drives, and the &lt;a href=&#34;https://en.wikipedia.org/wiki/Serial_ATA&#34;&gt;SATA&lt;/a&gt; standard has a procedure in place for &lt;a href=&#34;https://ata.wiki.kernel.org/index.php/ATA_Secure_Erase&#34;&gt;securely erasing drive contents&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hdparm --user-master u --security-set-pass TheEnd /dev/X
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hdparm -I /dev/X  &lt;span style=&#34;color:#888&#34;&gt;# Check that the master password is enabled&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;time&lt;/span&gt; hdparm --user-master u --security-erase TheEnd /dev/X&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will show some output indicating success or failure. Once this is done…&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hdparm -I /dev/X  &lt;span style=&#34;color:#888&#34;&gt;# Check that the master password is not enabled, which indicates the wipe was successful&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is likely the best option for a savvy home user, combined with shred/​nwipe. However, if you are going to attempt this, I highly recommend &lt;a href=&#34;https://ata.wiki.kernel.org/index.php/ATA_Secure_Erase&#34;&gt;reading the full instructions&lt;/a&gt;!&lt;/p&gt;
&lt;h3 id=&#34;commercial-software&#34;&gt;Commercial software&lt;/h3&gt;
&lt;p&gt;Blancco, aforementioned as the purchasers of DBAN, offer an enterprise level product for destroying data called &lt;a href=&#34;https://www.blancco.com/products/drive-eraser/&#34;&gt;Drive Eraser&lt;/a&gt;. Importantly, for business users, it provides certification that the data are gone for good.&lt;/p&gt;
&lt;p&gt;There are also many other options. Ask your favourite security vendor and they will be happy to sell you a product for this, though you usually have to take them at their word.&lt;/p&gt;
&lt;h3 id=&#34;be-preparedencrypt-your-disk&#34;&gt;Be prepared—​encrypt your disk!&lt;/h3&gt;
&lt;p&gt;Encrypt your disk. This can be achieved by encrypting either your home folder or by encrypting your whole disk. One downside is that your password is required after every reboot—​a problem especially for servers, but even cloud providers offer virtual terminals these days, so for secure operations, this is the best option.&lt;/p&gt;
&lt;p&gt;A secure, long password is usually enough to ensure the disk can’t be cracked, though be aware that cybersecurity changes quickly—​what’s impossible to brute-force today might not be in five years.&lt;/p&gt;
&lt;p&gt;So how can we be really, really sure?&lt;/p&gt;
&lt;h3 id=&#34;thermite&#34;&gt;Thermite&lt;/h3&gt;
&lt;p&gt;Physical destruction of the media is always the most secure way to destroy a disk—​crushing, drilling, or in the fanciful, dangerous dreams of some systems administrators, covering the disk in thermite and lighting it with a magnesium flare. &lt;strong&gt;(Do not actually use thermite.)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At home, &lt;a href=&#34;https://www.myfixguide.com/samsung-860-pro-ssd-teardown/&#34;&gt;disassembling the drive&lt;/a&gt; and taking a hammer to the data bearing chips will do the trick, though again, combine this with the above options to be sure.&lt;/p&gt;
&lt;p&gt;For spinning disks, a similar procedure is advised, though there are other options, of course…&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/drive-destruction/drive_destruction.jpg&#34; alt=&#34;a drive with several slugs embedded in it&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Check your local laws and follow all safety procedures before engaging in creative drive destruction techniques!&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;final-thoughts&#34;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;Use any or all of the above options and you’ll be ahead of the game. Taking the time to sanitize your data, or better, encrypting it from the beginning, is always a good investment.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>A great gift for the holidays: No ads!</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/12/pihole-great-holiday-gift/"/>
      <id>https://www.endpointdev.com/blog/2020/12/pihole-great-holiday-gift/</id>
      <published>2020-12-03T00:00:00+00:00</published>
      <author>
        <name>Ardyn Majere</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2020/12/pihole-great-holiday-gift/pihole-logo.png&#34; alt=&#34;Pi-hole logo&#34;&gt;&lt;/p&gt;
&lt;p&gt;Many people will bring home a pie during the holiday season, but perhaps you’ll find a place in your home network for a &lt;a href=&#34;https://www.raspberrypi.org/&#34;&gt;Raspberry Pi&lt;/a&gt; instead?&lt;/p&gt;
&lt;p&gt;With more people than ever working from home, many more people are using their personal infrastructure to conduct business, and aren’t able to rely on a crack team of network engineers to make sure their system is secure. While there are many things one can do to improve network security, from using a VPN to ensuring you update your system, a Pi-hole is one quick, inexpensive way to help keep your network a little safer not just on your phone or laptop, but on every device that connects to your router.&lt;/p&gt;
&lt;p&gt;It’s great not only for technical types, but for everyone who connects to your network. You can even set it up with remote access and gift it to a relative, as long as you’re willing to fix it if it breaks. With the holiday season coming up, it’s surely something to consider.&lt;/p&gt;
&lt;h3 id=&#34;shut-the-door-with-pi-hole&#34;&gt;Shut the door with Pi-hole&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://pi-hole.net/&#34;&gt;Pi-hole&lt;/a&gt; is an open source DNS server for your local network which blocks advertising and, after adding some extra block lists, some malicious websites.&lt;/p&gt;
&lt;p&gt;This is done before the data even gets downloaded, by redirecting requests for ads and garbage to a blank page, which means your internet will be faster as well as safer—this is especially important if you’re sharing networks with spouses and kids. It doesn’t rely on any client-side software, either, so it works regardless of platform, even on some smart TVs and apps. (Remember to support the websites and apps you use by other means if possible, though!)&lt;/p&gt;
&lt;p&gt;In addition to securing your network by allowing you to block malware, there’s an optional step that you can take to secure your DNS entirely: &lt;a href=&#34;https://docs.pi-hole.net/guides/dns-over-https/&#34;&gt;Encrypted DNS&lt;/a&gt;. With this, you can stop your ISP or any other entity from not only tampering with your DNS results, but seeing them at all. It won’t stop state actors, but it should help keep your browsing data from being sold.&lt;/p&gt;
&lt;p&gt;Here’s some example stats from the author’s home. This is one (rather heavy) day’s worth of traffic!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/pihole-great-holiday-gift/example-display.png&#34; alt=&#34;Example stats&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;installing-pi-hole&#34;&gt;Installing Pi-hole&lt;/h3&gt;
&lt;p&gt;What sort of knowledge do you need in order to be able to install Pi-hole? Well, it does require some minimal command line knowledge. However, if you don’t already have this, then this is a great first project to learn with.&lt;/p&gt;
&lt;p&gt;The Pi-hole website offers &lt;a href=&#34;https://docs.pi-hole.net/main/prerequisites/&#34;&gt;great instructions&lt;/a&gt; for installing the software. While you may want to use it as initially intended—on a $35 Raspberry Pi board—you don’t have to purchase new hardware. You can run Pi-hole on any Linux server, or even on a Docker instance or using a virtual machine on many different services.&lt;/p&gt;
&lt;p&gt;Just make sure that whatever system you install Pi-hole on is always turned on and stable (unplug the Pi-hole and your internet may stop working for the house, something I know my own family hates to have happen).&lt;/p&gt;
&lt;h3 id=&#34;adding-blocklists&#34;&gt;Adding blocklists&lt;/h3&gt;
&lt;p&gt;Adding blocklists is as simple as putting in a link to a text file with the offending hostnames. There are quite a few blocklists curated on the page. The trick is to choose blocklists that will meet your requirements—Some of the block lists can generate false positives. If you’re using Pi-hole for yourself, that’s probably not an issue since you can manually whitelist things as you go, but if you have a larger household, you may want to stick with the more conservative whitelists. Same if you’re setting this up for a relative or family member where you won’t be on site to fix things.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://firebog.net/&#34;&gt;Firebog.net&lt;/a&gt; has a good list of block lists, sorted by category. Green checkmarks are best for minimal maintenance situations; the other categories may incur false positives that require whitelisting.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;encrypted-dns&#34;&gt;Encrypted DNS&lt;/h3&gt;
&lt;p&gt;While blocking ads is a great start, to add further security to your DNS, you have several options.&lt;/p&gt;
&lt;p&gt;You can enable DNSSEC, which doesn’t encrypt your DNS but does validate it, assuming the domain owner actually enabled it. This is a good thing to enable, but it doesn’t replace true encrypted DNS.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.cloudflare.com/&#34;&gt;Cloudflare&lt;/a&gt; is the service that Pi-hole officially recommends using, via the tool &lt;a href=&#34;https://docs.pi-hole.net/guides/dns-over-https/#configuring-dns-over-https&#34;&gt;cloudflared&lt;/a&gt;. This routes all DNS traffic over https to Cloudflare’s DNS servers by default. There are also several other options, such as &lt;a href=&#34;https://www.dnscrypt.org/&#34;&gt;DNSCrypt&lt;/a&gt;. There’s a debate going on about DNS over HTTPS vs DNS over TLS, which is summed up nicely by this &lt;a href=&#34;https://spectrum.ieee.org/tech-talk/telecom/security/the-fight-over-encrypted-dns-boils-over&#34;&gt;IEEE spectrum article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Do you need to do this? No. But taking this step is relatively easy and protects you and your family from slippery man-in-the-middle attacks as well as your ISP snooping on what websites you go to and selling this to advertisers.&lt;/p&gt;
&lt;h3 id=&#34;what-cant-it-do&#34;&gt;What can’t it do?&lt;/h3&gt;
&lt;p&gt;For now, it won’t block &lt;strong&gt;all&lt;/strong&gt; advertising. YouTube ads aren’t blocked, nor are most streaming services that display ads from their internal servers. It won’t block ads hosted directly on the website you’re visiting.&lt;/p&gt;
&lt;p&gt;To keep things running smoothly, there is some maintenance required: the OS and Pi-hole need to be updated periodically, and the blocklists as well, though these tasks are easily automated.&lt;/p&gt;
&lt;p&gt;The Pi-hole also won’t protect you from concerted attempts to get into your system. This isn’t a replacement for a good firewall or antivirus/​malware protection software, and you should of course remember to practice good browsing habits. But as a relatively simple project to set up, it’s a good way to make your browsing experience at least a little less annoying.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Testing to defend against nginx add_header surprises</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/05/nginx-add_header-surprises-testing/"/>
      <id>https://www.endpointdev.com/blog/2020/05/nginx-add_header-surprises-testing/</id>
      <published>2020-05-29T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;img src=&#34;/blog/2020/05/nginx-add_header-surprises-testing/20200408-104315-mod.jpg&#34; alt=&#34;Cute calico cat perched securely upon a trepidatious shoe&#34; /&gt;
&lt;!-- Photo by Jon Jensen --&gt;
&lt;p&gt;These days when hosting websites it is common to configure the web server to send several HTTP response headers with every single request for security purposes.&lt;/p&gt;
&lt;p&gt;For example, using the nginx web server we may add these directives to our &lt;code&gt;http&lt;/code&gt; configuration scope to apply to everything served, or to specific &lt;code&gt;server&lt;/code&gt; configuration scopes to apply only to particular websites we serve:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;add_header Strict-Transport-Security max-age=2592000 always;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;add_header X-Content-Type-Options    nosniff         always;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;(See &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security&#34;&gt;HTTP Strict Transport Security&lt;/a&gt; and &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options&#34;&gt;X-Content-Type-Options&lt;/a&gt; at MDN for details about these two particular headers.)&lt;/p&gt;
&lt;h3 id=&#34;the-surprise-problem&#34;&gt;The surprise (problem)&lt;/h3&gt;
&lt;p&gt;Once upon a time I ran into a case where nginx usually added the expected HTTP response headers, but later appeared to be inconsistent and sometimes did not. This is distressing!&lt;/p&gt;
&lt;p&gt;Troubleshooting leads to the (re-)discovery that &lt;code&gt;add_header&lt;/code&gt; directives are not always additive throughout the configuration as one would expect, and as every other server I can think of typically does.&lt;/p&gt;
&lt;p&gt;If you define your &lt;code&gt;add_header&lt;/code&gt; directives in the &lt;code&gt;http&lt;/code&gt; block and then use an &lt;code&gt;add_header&lt;/code&gt; directive in a &lt;code&gt;server&lt;/code&gt; block, those from the &lt;code&gt;http&lt;/code&gt; block will disappear.&lt;/p&gt;
&lt;p&gt;If you define some &lt;code&gt;add_header&lt;/code&gt; directives in the &lt;code&gt;server&lt;/code&gt; block and then add another &lt;code&gt;add_header&lt;/code&gt; directive in a &lt;code&gt;location&lt;/code&gt; block, those from the &lt;code&gt;http&lt;/code&gt; and/or &lt;code&gt;server&lt;/code&gt; blocks will disappear.&lt;/p&gt;
&lt;p&gt;This is even the case in an &lt;code&gt;if&lt;/code&gt; block.&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&#34;https://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header&#34;&gt;nginx &lt;code&gt;add_header&lt;/code&gt; documentation&lt;/a&gt; we find the reason for the behavior explained:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;This nginx directive has always behaved this way. Various people have warned about it in blog posts and online discussions for many years. But the situation remains the same, a trap for the unwary.&lt;/p&gt;
&lt;p&gt;I have tried to imagine the rationale behind this behavior. Response headers often are set in groups, so the programmer who created this feature may have decided that any new scope’s &lt;code&gt;add_header&lt;/code&gt; directives should start with a clean slate, unaffected by those set elsewhere. Hmm. The need for exclusive grouping of response headers is rare in my experience, and adding headers to the existing stack of tentative response headers is far more commonly what I want.&lt;/p&gt;
&lt;p&gt;So while this behavior may make sense somewhere, it has not ever done so for me or anyone I have talked to about it. For us it is simply misbehavior, silent and easy to overlook when making later seemingly unrelated configuration adjustments.&lt;/p&gt;
&lt;h3 id=&#34;dangers&#34;&gt;Dangers&lt;/h3&gt;
&lt;p&gt;It often has security implications when headers you thought were being added to every response are not. Consider more fine-tuned and consequential security-related headers such as &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy&#34;&gt;&lt;code&gt;Content-Security-Policy&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary&#34;&gt;&lt;code&gt;Vary&lt;/code&gt;&lt;/a&gt; for cache object separation, &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS&#34;&gt;CORS&lt;/a&gt; headers &lt;code&gt;Access-Control-*&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;Headers such as these are especially important when they need to be added based on logic spread across various configuration blocks, and that is exactly when nginx &lt;code&gt;add_headers&lt;/code&gt; doesn’t work as expected.&lt;/p&gt;
&lt;p&gt;Another pitfall is omitting the &lt;code&gt;always&lt;/code&gt; option to &lt;code&gt;add_header&lt;/code&gt;. Without that, the header will only be added to success responses (2XX and 3XX, but see the docs for specifics). We usually want security-related headers to be added even to 4XX and 5XX error responses.&lt;/p&gt;
&lt;h3 id=&#34;workaround-using-include&#34;&gt;Workaround using include&lt;/h3&gt;
&lt;p&gt;My first instinct was to work around the problems caused by this behavior by putting the standard add_header list in a file that I include everywhere. In some cases that works.&lt;/p&gt;
&lt;p&gt;But despite the &lt;a href=&#34;https://nginx.org/en/docs/ngx_core_module.html#include&#34;&gt;nginx include documentation&lt;/a&gt; saying that directive is allowed in “Context: any”, &lt;code&gt;include&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; allowed in an &lt;code&gt;if&lt;/code&gt; block and will result in the fatal startup error:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;include&amp;rdquo; directive is not allowed here&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;So the only recourse in those cases is to repeat all needed &lt;code&gt;add_header&lt;/code&gt; directives in every &lt;code&gt;if&lt;/code&gt; block that uses &lt;code&gt;add_header&lt;/code&gt;. Gross.&lt;/p&gt;
&lt;p&gt;Repeating configuration manually means almost surely having the &lt;code&gt;add_header&lt;/code&gt; directives in different configuration areas drift over time. So if we have to repeat ourselves, at least let’s do it with automation, such as by using configuration templating and preprocessing.&lt;/p&gt;
&lt;p&gt;That is what I have most recently done. And we can still use native nginx &lt;code&gt;include&lt;/code&gt; directives everywhere those are allowed.&lt;/p&gt;
&lt;h3 id=&#34;nginx-headers-more-module&#34;&gt;nginx Headers More module&lt;/h3&gt;
&lt;p&gt;Many people have run into exactly this problem, and some of them developed a separate nginx module &lt;a href=&#34;https://github.com/openresty/headers-more-nginx-module#readme&#34;&gt;ngx_headers_more&lt;/a&gt; to solve most of these problems.&lt;/p&gt;
&lt;p&gt;By using its &lt;code&gt;more_set_headers&lt;/code&gt; directive, you get the expected additive behavior with previously-declared headers, regardless of the block scope:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Directives inherited from an upper level scope (say, http block or server blocks) are executed before the directives in the location block.&lt;/p&gt;
&lt;p&gt;Note that although more_set_headers is allowed in location if blocks, it is not allowed in the server if blocks …&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Fortunately I have not needed to use this in an &lt;code&gt;if&lt;/code&gt; block in the &lt;code&gt;server&lt;/code&gt; scope, so that one remaining limitation doesn’t pose a problem for me.&lt;/p&gt;
&lt;p&gt;It also has options to set a header only for responses of a certain HTTP content type or status code.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;more_clear_headers&lt;/code&gt; directive allows the &lt;code&gt;*&lt;/code&gt; wildcard for clearing all headers with the same prefix at once, such as &lt;code&gt;Access-Control-*&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&#34;installing-ngx_headers_more&#34;&gt;Installing ngx_headers_more&lt;/h4&gt;
&lt;p&gt;Because “Headers More” is a separate module, not part of standard nginx, it is not usually available without some extra work.&lt;/p&gt;
&lt;p&gt;You can build it from source and install it manually, but of course that isn’t good to do on a production machine since it won’t get updated on its own.&lt;/p&gt;
&lt;p&gt;You can use the &lt;a href=&#34;https://openresty.org/en/&#34;&gt;OpenResty&lt;/a&gt; server built around nginx, which “Headers More” is part of. But you may not want all of that if you’re not writing a Lua web application.&lt;/p&gt;
&lt;p&gt;Many Linux distributions and 3rd-party package repositories have prebuilt packages for “Headers More” which you can use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alpine
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nginx-mod-http-headers-more&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Debian &amp;amp; Ubuntu
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nginx-extras&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libnginx-mod-http-headers-more-filter&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RHEL/CentOS
&lt;ul&gt;
&lt;li&gt;GetPageSpeed &amp;amp; Webtatic repos &lt;code&gt;nginx-module-headers-more&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Aeris repo &lt;code&gt;nginx-more&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Search the excellent &lt;a href=&#34;https://pkgs.org/&#34;&gt;pkgs.org&lt;/a&gt; to find what you need if it isn’t already available through your package manager.&lt;/p&gt;
&lt;h3 id=&#34;apache&#34;&gt;Apache&lt;/h3&gt;
&lt;p&gt;Apache httpd is still alive and well — actually better than ever. So depending on your situation, you may want to use that instead.&lt;/p&gt;
&lt;p&gt;Apache’s &lt;a href=&#34;https://httpd.apache.org/docs/2.4/mod/mod_headers.html#header&#34;&gt;Header directive&lt;/a&gt; has intuitive (to me) default behavior for setting response headers across the whole configuration, and many ways to deal with a possibly already-existing header:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;add another header, or set exclusively (replace), or set only if this header doesn’t already exist&lt;/li&gt;
&lt;li&gt;append to or merge into an existing header (for headers that accept multiple values)&lt;/li&gt;
&lt;li&gt;edit an existing header with a regular expression search-and-replace&lt;/li&gt;
&lt;li&gt;unset a header if one was previously set&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don’t know a way to have Apache clear a group of headers with a wildcard, or all headers at once, so they need to be individually cleared by name if that’s what you want.&lt;/p&gt;
&lt;h3 id=&#34;доверяй-но-проверяй-trust-but-verify&#34;&gt;Доверяй, но проверяй (Trust, but verify)&lt;/h3&gt;
&lt;p&gt;nginx was written by Igor Sysoev. Despite my disagreement with this one feature’s behavior, overall I find that nginx is excellent. Because of its open source release, excellent performance, and wide use, it has provided much-needed competition to Apache and Microsoft IIS. Thank you, Igor and all other contributors!&lt;/p&gt;
&lt;p&gt;In the relevant spirit, since Igor is Russian, I close with the Russian proverb Доверяй, но проверяй: Trust, but verify.&lt;/p&gt;
&lt;p&gt;Let us code (and configure) defensively, yet also test to avoid being surprised by missing headers.&lt;/p&gt;
&lt;p&gt;We can manually test various HTTP responses are as we expect using &lt;code&gt;curl -v&lt;/code&gt; or other HTTP clients to exercise various requests.&lt;/p&gt;
&lt;p&gt;Even better, we can add to our automated test suite to confirm these HTTP response headers appear everywhere we expect, for static files and API endpoints backed by different application servers, and for various success and error responses.&lt;/p&gt;
&lt;p&gt;Here is a test adapted from one I put together for one of our clients. It uses JavaScript in &lt;a href=&#34;https://nodejs.org/en/&#34;&gt;Node.js&lt;/a&gt;, the &lt;a href=&#34;https://jestjs.io/&#34;&gt;Jest&lt;/a&gt; test framework, and the &lt;a href=&#34;https://github.com/axios/axios&#34;&gt;Axios&lt;/a&gt; HTTP client. It ensures the security headers example I showed at the beginning of this article keeps working, even as we make nginx configuration changes over time:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; axios = require(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;axios&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; http = axios.create({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  baseURL: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;https://your.dom.ain&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;describe(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Check security headers&amp;#39;&lt;/span&gt;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; verifs = [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { header: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;strict-transport-security&amp;#39;&lt;/span&gt;, expect: (x) =&amp;gt; x.toMatch(&lt;span style=&#34;color:#080;background-color:#fff0ff&#34;&gt;/max-age=\d{3,}/&lt;/span&gt;) },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { header: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;x-content-type-options&amp;#39;&lt;/span&gt;,    expect: (x) =&amp;gt; x.toEqual(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;nosniff&amp;#39;&lt;/span&gt;)        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; locs = [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { path: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/robots.txt&amp;#39;&lt;/span&gt;,                status: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;200&lt;/span&gt; },  &lt;span style=&#34;color:#888&#34;&gt;// static
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    { path: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/feed/endpoint/of/interest&amp;#39;&lt;/span&gt;, status: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;200&lt;/span&gt; },  &lt;span style=&#34;color:#888&#34;&gt;// API backend in PHP
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    { path: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/api/other/auth/endpoint&amp;#39;&lt;/span&gt;,   status: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;403&lt;/span&gt; },  &lt;span style=&#34;color:#888&#34;&gt;// API backend in Perl
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    { path: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/never/gonna/give/you/up!&amp;#39;&lt;/span&gt;,  status: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;404&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { path: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/api/dies/for/testing&amp;#39;&lt;/span&gt;,      status: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;500&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;// throw no exceptions for non-success HTTP response status
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; conf = { validateStatus: () =&amp;gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; l &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;of&lt;/span&gt; locs) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;${&lt;/span&gt;l.status&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;${&lt;/span&gt;l.path&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;`&lt;/span&gt;, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; res = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; http.get(l.path, conf);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      expect(res.status).toBe(l.status);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; v &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;of&lt;/span&gt; verifs) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        v.expect(expect(res.headers[v.header]));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here I run just this one test rather than the whole suite:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;% jest -w 6 ./__tests__/webserver/security-headers.test.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Determining test suites to run...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;testing on https://https://your.dom.ain
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; PASS  webserver/security-headers.test.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Check security headers
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ✓ 200 /robots.txt (55ms)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ✓ 200 /feed/endpoint/of/interest (408ms)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ✓ 403 /api/other/auth/endpoint (18ms)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ✓ 404 /never/gonna/give/you/up! (6ms)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ✓ 500 /api/dies/for/testing (12ms)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Test Suites: 1 passed, 1 total
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Tests:       5 passed, 5 total
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Snapshots:   0 total
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Time:        2.721s, estimated 3s
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Ran all test suites matching /.\/__tests__\/webserver\/security-headers.test.js/i.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This can also be extended to ensure that certain headers do not exist, or do not contain details that you do not want exposed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;Server&lt;/code&gt; header should not reveal the nginx (see &lt;a href=&#34;https://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens&#34;&gt;server_tokens&lt;/a&gt;) or Apache (see &lt;a href=&#34;https://httpd.apache.org/docs/trunk/mod/core.html#servertokens&#34;&gt;ServerTokens&lt;/a&gt;) version numbers&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;X-Powered-By&lt;/code&gt; header should be absent, not exposing the fact that you are using PHP, and the version number — see the &lt;a href=&#34;https://www.php.net/manual/en/ini.core.php#ini.expose-php&#34;&gt;expose_php&lt;/a&gt; directive for &lt;code&gt;php.ini&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;or with the Java Wildfly server, both of those headers are sent by default! — see instructions on how to omit them &lt;a href=&#34;https://zenidas.wordpress.com/recipes/hideexpose-http-headers-in-wildfly-10-1/&#34;&gt;by editing XML&lt;/a&gt; or &lt;a href=&#34;https://mariusz.wyszomierski.pl/en/turn-off-x-powered-by-i-server-headers-in-wildfly-10/&#34;&gt;using jboss-cli&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add to the &lt;code&gt;verifs&lt;/code&gt; array in the code above:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { header: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;server&amp;#39;&lt;/span&gt;,                    expect: (x) =&amp;gt; x.not.toMatch(&lt;span style=&#34;color:#080;background-color:#fff0ff&#34;&gt;/\d/&lt;/span&gt;)         },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { header: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;x-powered-by&amp;#39;&lt;/span&gt;,              expect: (x) =&amp;gt; x.toBeUndefined()           },&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now if (when) I forget about the nginx &lt;code&gt;add_headers&lt;/code&gt; behavior, make changes, and inadvertently break things? Instead of it being unnoticed, my test suite will alert me so I can fix it before it goes into production!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Consolidating Multiple SFTP Accounts Into One Master Account</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/03/consolidating-multiple-sftp-accounts/"/>
      <id>https://www.endpointdev.com/blog/2020/03/consolidating-multiple-sftp-accounts/</id>
      <published>2020-03-16T00:00:00+00:00</published>
      <author>
        <name>Selvakumar Arumugam</name>
      </author>
      <content type="html">
        &lt;img src=&#34;/blog/2020/03/consolidating-multiple-sftp-accounts/image-0.jpg&#34; alt=&#34;merging roads&#34; /&gt;
&lt;p&gt;&lt;a href=&#34;https://unsplash.com/photos/kzSNNqqS3Qs&#34;&gt;Photo&lt;/a&gt; by &lt;a href=&#34;https://unsplash.com/@dmey503&#34;&gt;Dan Meyers&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Recently, a client implemented a data-intensive workflow to generate various reports and insights from a list of facilities as part of an EpiTrax installation. Because a significant portion of these files contain sensitive healthcare data, they needed to strictly comply with HIPAA. Optimally, facilities should be able to transfer files securely and exclusively to our server. One of the best methods of achieving this is to create individual SSH File Transfer Protocol (SFTP) accounts for each source.&lt;/p&gt;
&lt;h3 id=&#34;sftp-account&#34;&gt;SFTP account&lt;/h3&gt;
&lt;p&gt;Private SFTP accounts were established for each facility and the data was received at a designated path. At these individual points of contact, a third-party application picks up the data and processes further into the pipeline. The following demonstrates how SFTP accounts are developed and configured:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a user group for SFTP accounts:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ addgroup sftpusers&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Configure the following settings in sshd_config (this enables an SFTP account and sets the default location as the home path):&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ vi /etc/ssh/sshd_config
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# override default of no subsystems&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Subsystem       sftp    internal-sftp...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Match Group sftpusers
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ChrootDirectory /home/%u
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    AllowTCPForwarding no
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    X11Forwarding no
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ForceCommand internal-sftp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Restart SSH server to apply changes:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ systemctl restart ssh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Create an SFTP user account for a facility and place in a folder on the home path to receive data:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# set new user name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;sftpuser&lt;/span&gt;=the-new-username
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;useradd &lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;usermod -g sftpusers -s /usr/sbin/nologin &lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir -p /home/&lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;/INPUT_PATH/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chown -R root:root /home/&lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;mount-multiple-accounts-to-one-account&#34;&gt;Mount multiple accounts to one account&lt;/h3&gt;
&lt;p&gt;The goal here is to point the data from many facilities to one location, but using a single account and path for multiple sites’ data could result in a breach of security and/​or privacy. Mounting the receiving path of a facility’s data onto a single master account and then to a “mount point” with a unique facility name takes care of this issue. The process next consolidates files from individual paths on a master account in one place where the application picks up messages for further processing.&lt;/p&gt;
&lt;p&gt;The SFTP accounts and the master account should be attached to the same group. This will permit individual accounts to write on the master account-mounted path. In turn, the master account can read files from the same location. This location now has administrative rights for both the SFTP user the group. Group permission of the mounted folder is set to sftpgroup and user permission is set to the facility account.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# create master user account&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ adduser master
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ passwd master
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Add master user to sftpgroup group&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;usermod -a -G sftpgroup master
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;getent group sftpgroup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# MOUNT_PATH and sub folders&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir -p /home/master/MOUNT_PATH/{Input,Pickup,Backup,Archive}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chown -R master:master /home/master/MOUNT_PATH&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We wrote a script to automate the process of creating an SFTP account, mounting it at the master account path, and adding fstab entries to save the mount in the case of a reboot. The script not only saves time, but also avoids human error when creating accounts for all facilities.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c00;font-weight:bold&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c00;font-weight:bold&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# ./create_sftp_account.sh sftpfacilityone FACILITY_ONE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;sftpuser&lt;/span&gt;=&lt;span style=&#34;color:#369&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;facility_name&lt;/span&gt;=&lt;span style=&#34;color:#369&#34;&gt;$2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Create SFTP account and add to sftpgroup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;useradd &lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;usermod -g sftpusers -s /usr/sbin/nologin &lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;usermod -a -G sftpgroup &lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir -p /home/&lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;/INPUT_PATH
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chown root:root /home/&lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Create path specific to facility in master account Input folder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir -p /home/master/MOUNT_PATH/Input/&lt;span style=&#34;color:#369&#34;&gt;$facility_name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chown -R &lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;:sftpgroup /home/master/MOUNT_PATH/Input/&lt;span style=&#34;color:#369&#34;&gt;$facility_name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Mount sftp account into master account and set permissions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mount --bind /home/master/MOUNT_PATH/Input/&lt;span style=&#34;color:#369&#34;&gt;$facility_name&lt;/span&gt; /home/&lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;/INPUT_PATH
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chown -R &lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;:sftpgroup /home/&lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;/INPUT_PATH
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chmod &lt;span style=&#34;color:#369&#34;&gt;g&lt;/span&gt;=rwxs /home/&lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;/INPUT_PATH
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Add fstab entry to persist and mount on reboot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/home/master/MOUNT_PATH/Input/&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$facility_name&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; /home/&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;/INPUT_PATH none bind 0 0&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; /etc/fstab
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Created user &lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$sftpuser&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; at &lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$facility_name&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; mount point successfully&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;files-at-one-location&#34;&gt;Files at one location&lt;/h3&gt;
&lt;p&gt;Now, data files from facilities are available at individual folders under MOUNT_PATH/Input on the master account. This enables third-party applications to pick up files in a straightforward way to proceed with further processing. It also helps our client access the files for review from the master account easily without navigating into each separate account.&lt;/p&gt;
&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Mounting multiple SFTP accounts onto one master account turns out to be an efficient and beneficial method of consolidating data. Both safe and secure, running separate SFTP accounts establishes an exclusive private link between facilities and servers. The master account has the unique ability to access files belonging to each facility in order to process the data further.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: In order to avoid broken mounts, check the status by using the command &lt;code&gt;mount -fav&lt;/code&gt;. Problems with mount configurations can cause broken mounts after rebooting the server.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Cooking with CAS</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/03/cooking-with-cas/"/>
      <id>https://www.endpointdev.com/blog/2020/03/cooking-with-cas/</id>
      <published>2020-03-10T00:00:00+00:00</published>
      <author>
        <name>Josh Tolley</name>
      </author>
      <content type="html">
        &lt;img src=&#34;/blog/2020/03/cooking-with-cas/4696900602_77582d1d5d_c.jpg&#34; alt=&#34;Laptop on a desk showing a web browser open to a login form, with a Post-It note saying &#39;ADMIN / ADMIN&#39; attached to the screen&#34; /&gt;
&lt;p&gt;Photo by Flickr user &lt;a href=&#34;http://web.archive.org/web/20190305093832/https://www.flickr.com/photos/reidrac/&#34;&gt;reidrac&lt;/a&gt;, licensed
under &lt;a href=&#34;https://creativecommons.org/licenses/by-sa/2.0/&#34;&gt;CC BY-SA 2.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;One of our customers asked us to host a new suite of web-based applications for them and to protect them with a single sign-on (SSO) solution. Ok, easy enough;
these applications were in fact designed with a particular SSO system in mind already, but our situation required a different one, and we eventually chose
Apereo’s open source &lt;a href=&#34;https://www.apereo.org/projects/cas&#34;&gt;Central Authentication Server project&lt;/a&gt;, or CAS. I’d like to describe the conversion process we went
through.&lt;/p&gt;
&lt;h3 id=&#34;the-ingredients&#34;&gt;The ingredients&lt;/h3&gt;
&lt;p&gt;Our customer’s application suite included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The principal Java application using &lt;a href=&#34;https://docs.oracle.com/javase/7/docs/technotes/guides/security/jaas/JAASRefGuide.html&#34;&gt;JAAS authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Another Java application based on &lt;a href=&#34;https://spring.io/projects/spring-security&#34;&gt;Spring Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A pair of PHP applications&lt;/li&gt;
&lt;li&gt;A few automated tasks that needed to authenticate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The original SSO system sets a header on each request, identifying an authenticated user. This requires a gateway system to sanitize request headers to ensure
malicious users cannot forge a header themselves. It also requires each application inspect request headers and respond appropriately.&lt;/p&gt;
&lt;p&gt;CAS is a bit more complex: applications redirect unauthenticated requests to a CAS server, which authenticates the user through any of various configurable
methods. The CAS server then redirects the user back to the original application with a parameter called a “Service Ticket”, a seemingly random number
identifying an individual authentication request. The original application contacts the CAS server directly to validate the service ticket and to collect
information to identify the user. It can then establish a session for that user, and proceed normally.&lt;/p&gt;
&lt;p&gt;To CAS-enable an application, we incorporate one of the CAS &lt;a href=&#34;https://apereo.github.io/cas/6.1.x/integration/CAS-Clients.html#build-your-own-cas-client&#34;&gt;client
libraries&lt;/a&gt;, which exist for various languages. In fact we won’t use
the Java client directly, but rather we’ll incorporate components that extend it. When evaluating CAS, I was a bit concerned by what appeared to be a
surprisingly limited selection of actively supported client libraries, and of course your results may vary, but we found software to meet our own needs without
too much difficulty.&lt;/p&gt;
&lt;h3 id=&#34;configuring-wildfly-authentication&#34;&gt;Configuring Wildfly Authentication&lt;/h3&gt;
&lt;p&gt;The most important application in the suite depends on the JAAS-based security subsystem of the &lt;a href=&#34;https://www.wildfly.org/&#34;&gt;Wildfly application server&lt;/a&gt; it’s
deployed to. Originally it used a custom &lt;a href=&#34;https://docs.oracle.com/javase/7/docs/api/javax/security/auth/spi/LoginModule.html&#34;&gt;LoginModule&lt;/a&gt; that inspected
request headers to find the ID of the authenticated user. Our first task was to configure our proxy server to remove this header from every request. We planned
to disable the old authentication system entirely, of course, but this change ensured that even if we mistakenly left it enabled somewhere, malicious users
couldn’t exploit it for access.&lt;/p&gt;
&lt;p&gt;This application uses a declarative security policy: the deployment descriptor identifies a set of user roles, and another set of “security constraints”. Each
security constraint describes one or more URL patterns used in the application, and the set of user roles allowed to access URLs matching those patterns. Here
are two examples:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&amp;lt;!-- clients --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;security-constraint&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;web-resource-collection&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;web-resource-name&amp;gt;&lt;/span&gt;Secure Area&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/web-resource-name&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;url-pattern&amp;gt;&lt;/span&gt;/views/clients/*&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/url-pattern&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;url-pattern&amp;gt;&lt;/span&gt;/client/edit/*&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/url-pattern&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;url-pattern&amp;gt;&lt;/span&gt;/client/edit_tree/*&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/url-pattern&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;url-pattern&amp;gt;&lt;/span&gt;/client/view/*&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/url-pattern&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;url-pattern&amp;gt;&lt;/span&gt;/client/view/id/*&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/url-pattern&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/web-resource-collection&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;auth-constraint&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;role-name&amp;gt;&lt;/span&gt;client_role&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/role-name&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/auth-constraint&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/security-constraint&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&amp;lt;!-- places --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;security-constraint&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;web-resource-collection&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;web-resource-name&amp;gt;&lt;/span&gt;Secure Area&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/web-resource-name&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;url-pattern&amp;gt;&lt;/span&gt;/views/places/*&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/url-pattern&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/web-resource-collection&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;auth-constraint&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;role-name&amp;gt;&lt;/span&gt;manage_places&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/role-name&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/auth-constraint&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/security-constraint&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We installed the &lt;a href=&#34;https://github.com/soulwing/cas-extension&#34;&gt;cas-extension library&lt;/a&gt; in our Wildfly server to handle the CAS protocol. When an unauthenticated
user attempts to access a URL matching a pattern in one of the application’s security constraints, the CAS extension automatically intercepts control and
redirects the user to the CAS server. Assuming the user authenticates successfully, our application will receive another request with a service ticket
parameter. The cas-extension intercepts this request as well, validates the service ticket, and creates an “identity assertion”, which it sends to the Wildfly
security system. Wildfly’s role mapper queries a database to find the user’s roles, after which the authentication process is complete.&lt;/p&gt;
&lt;p&gt;Configuration of the cas-extension begins with a CAS profile, a combination of the URL of the CAS server and the URL of the service the extension should
protect.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;subsystem&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;xmlns=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;urn:soulwing.org:cas:1.0&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;cas-profile&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;default&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;service-url=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;https://our.application.server/application&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;server-url=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;https://our.cas.server&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/subsystem&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This tells the extension where to send authentication requests, where to listen for requests returning from the CAS server, and where to validate service
tickets. Next we need to validate that identity assertion, and figure out what roles belong to the user. This happens in a &lt;a href=&#34;https://docs.wildfly.org/14/Admin_Guide.html#security-domains&#34;&gt;Wildfly security
domain&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;security-domain&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;MySecurityDomain&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;authentication&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;login-module&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;IdentityAssertion&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;code=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;org.soulwing.cas.jaas.IdentityAssertionLoginModule&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;flag=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;required&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;module=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;org.soulwing.cas&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/authentication&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;mapping&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;mapping-module&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;code=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;DatabaseRoles&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;type=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;role&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;module-option&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;dsJndiName&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;value=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;java:/comp/env/jdbc/databaseConnection&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;module-option&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;name=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;rolesQuery&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;value=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;select role from user_roles where user_id = ?&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/mapping-module&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/mapping&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/security-domain&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;authentication&lt;/code&gt; portion of the security domain refers to a JAAS LoginModule shipped with the CAS extension, which simply verifies that the identity
assertion comes from the CAS extension and not somewhere else. Then the &lt;code&gt;mapping&lt;/code&gt; portion (&lt;a href=&#34;https://docs.wildfly.org/14/Admin_Guide.html#mapping&#34;&gt;documented here&lt;/a&gt;)
looks up the given user in a database to find what roles it should be assigned.&lt;/p&gt;
&lt;p&gt;The last piece of the puzzle is a CAS deployment descriptor for our application, which activates cas-extension for that application. In its simplest form, this
is an empty file in the right place, but ours ended up a little more complex. It identifies both the CAS profile we want to use (unnecessary in this case, as
there’s only one CAS profile on the system, but it helped keep things more clear in our minds), and instructs the extension to load some other libraries into
our application.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c00;font-weight:bold&#34;&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;cas&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;xmlns=&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;urn:soulwing.org:cas:1.0&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;profile&amp;gt;&lt;/span&gt;default&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/profile&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;add-api-dependencies/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;lt;/cas&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This configuration proved sufficient to let users log in and use the application, but as is sometimes the case with single sign-on, we needed a little more work
to let them log out properly. Each application in the suite sets a cookie in the user’s browser to identify its session. The CAS server likewise sets a cookie.
When a user logs out of the application, that application’s session cookie is destroyed, but we also need to destroy the CAS server’s session cookie as well as
the other applications’ cookies. Single log-out can be complicated, and I won’t go into the full setup here. One reason the CAS deployment descriptor loads
cas-extension libraries was so we could use cas-extension to generate the proper logout URL, and redirect our users to that URL once the application has
destroyed its own session.&lt;/p&gt;
&lt;h3 id=&#34;configuring-spring-authentication&#34;&gt;Configuring Spring Authentication&lt;/h3&gt;
&lt;p&gt;Another Java-based application in our suite uses Spring Security. &lt;a href=&#34;https://docs.spring.io/spring-security/site/docs/3.0.x/reference/cas.html&#34;&gt;This document&lt;/a&gt;
describes the bulk of the configuration, which seemed less straightforward than for cas-extension, but follows essentially the same mechanism. Here we
configured a Spring
&lt;a href=&#34;https://docs.spring.io/spring-security/site/docs/3.2.3.RELEASE/apidocs/org/springframework/security/core/userdetails/UserDetailsService.html&#34;&gt;UserDetailsService&lt;/a&gt;
to execute the same database query we used above to find the user’s roles. I’m not fully conversant in the large stack of beans Spring uses to manage the
process, and it took some time to get this configuration sorted out.&lt;/p&gt;
&lt;h3 id=&#34;enter-php&#34;&gt;Enter PHP&lt;/h3&gt;
&lt;p&gt;Two of these applications use PHP, which meant yet another configuration. Apereo maintains a &lt;a href=&#34;https://github.com/apereo/phpCAS&#34;&gt;PHP client&lt;/a&gt;, which includes
several helpful examples. I tracked down the part of the application that authenticates users, and replaced the existing code with calls to phpCAS:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;phpCAS::&lt;span style=&#34;color:#369&#34;&gt;client&lt;/span&gt;(CAS_VERSION_2_0, &lt;span style=&#34;color:#369&#34;&gt;$phpCASHost&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$phpCASPort&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$phpCASContext&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;phpCAS::&lt;span style=&#34;color:#369&#34;&gt;forceAuthentication&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;$_SESSION&lt;/span&gt;[EXPORT_SERVERNAME][&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;umdid&amp;#39;&lt;/span&gt;] = phpCAS::&lt;span style=&#34;color:#369&#34;&gt;getUser&lt;/span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;forceAuthentication&lt;/code&gt; call determines the current phase of the authentication process this request is in, whether it’s unauthenticated, fully authenticated, or
requires service ticket validation, and responds appropriately. We then set a session variable to the ID of the authenticated user, which replicates what
the original authentication code would have done.&lt;/p&gt;
&lt;p&gt;One of these two applications requires authenticated access to a REST API exposed by the Wildfly application. CAS calls this “proxy” authentication, when one
application requests access to another. Here, CAS issues not only its usual service ticket, but also a “proxy granting ticket”. When the application wants to
use the API, it asks the CAS server for a service ticket, using the proxy granting ticket. The CAS server itself requires some new configuration in this case,
but for the PHP code, the only difference in the login phase from the simpler, non-proxy case is that we call &lt;code&gt;phpCAS::proxy&lt;/code&gt; instead of &lt;code&gt;phpCAS::client&lt;/code&gt; to
configure CAS. Later, when calling the service itself, we used more phpCAS services in place of the cURL library the original used.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;$service&lt;/span&gt; = \phpCAS::&lt;span style=&#34;color:#369&#34;&gt;getProxiedService&lt;/span&gt;(PHPCAS_PROXIED_SERVICE_HTTP_GET);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;$service&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#369&#34;&gt;setUrl&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$serviceURL&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;$service&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#369&#34;&gt;send&lt;/span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;the-return-of-header-authentication&#34;&gt;The return of header authentication&lt;/h3&gt;
&lt;p&gt;Finally, we have a few automated tasks which use various APIs, and need to authenticate. We can’t redirect requests to a CAS server and expect a user to provide
credentials, so we’ve taken our cue from the applications’ original form, and configured CAS to recognize a “trusted header”. We add this header to any requests
issued by these automated jobs. Of course, we’ve also configured the proxy to disallow this header from any systems outside our internal network.&lt;/p&gt;
&lt;h3 id=&#34;a-few-loose-ends&#34;&gt;A few loose ends&lt;/h3&gt;
&lt;p&gt;Of course, there were other considerations in this project that I’ve not covered here. Configuring CAS itself wasn’t necessarily straightforward, and included a
custom authentication module I hope to describe in a later article. Selecting among CAS server’s available
&lt;a href=&#34;https://apereo.github.io/cas/6.1.x/installation/Configuring-Servlet-Container.html&#34;&gt;deployment&lt;/a&gt;
&lt;a href=&#34;https://apereo.github.io/cas/6.1.x/installation/Docker-Installation.html&#34;&gt;options&lt;/a&gt; and fitting the winner into our existing infrastructure in a way that makes
it easy to manage and monitor was another task entirely. We needed to customize the server’s default user interface to prevent it from offering users an
imaginary method to recover forgotten passwords. The series of redirected browser requests involved in the CAS protocol presents a notable performance impact
under some circumstances. And it has taken me no small effort to learn to appreciate the CAS server’s sometimes distressingly circular
&lt;a href=&#34;https://apereo.github.io/cas/6.1.x/index.html&#34;&gt;documentation&lt;/a&gt;. But several months into the project, CAS seems to be working well enough for our purposes that
other users of the same application suite have begun to express interest.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>End Point Security Tips: Securing your Infrastructure</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/02/end-point-security-tips/"/>
      <id>https://www.endpointdev.com/blog/2020/02/end-point-security-tips/</id>
      <published>2020-02-05T00:00:00+00:00</published>
      <author>
        <name>Charles Chang</name>
      </author>
      <content type="html">
        &lt;img src=&#34;/blog/2020/02/end-point-security-tips/image-4.jpg&#34; alt=&#34;phishing key on keyboard&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://flic.kr/p/24YXTiY&#34;&gt;Photo&lt;/a&gt; from &lt;a href=&#34;https://www.comparitech.com/blog/information-security/common-phishing-scams-how-to-avoid/&#34;&gt;comparitech.com&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;implement-security-measures-to-protect-your-organization--employees&#34;&gt;Implement Security Measures to Protect Your Organization &amp;amp; Employees&lt;/h3&gt;
&lt;p&gt;In this post, I’ll address what I believe are the three important initiatives every organization should implement to protect your organization and employees:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Train employees on security culture.&lt;/li&gt;
&lt;li&gt;Implement the best technical tools to aid with organizational security.&lt;/li&gt;
&lt;li&gt;Implement recovery tools in case you need to recover from a security breach.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;habits-of-a-security-culture&#34;&gt;Habits of a Security Culture&lt;/h3&gt;
&lt;p&gt;Train everyone in your organization on these fundamentals:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The only time you should be requested to reset your password by email is when you initiate it. There are rare exceptions to this rule, such as when accounts are compromised and providers request all users reset their passwords, but those events should be publicly announced. Staff can confirm with security personnel before acting on such requests.&lt;/li&gt;
&lt;li&gt;If you are asked to reset your password, it will typically be after you successfully logged into a website and the old one has expired.&lt;/li&gt;
&lt;li&gt;If you receive an email and do not know the sender, do not trust the contents or open attachments. Get advice from security personnel if needed.&lt;/li&gt;
&lt;li&gt;If you think the email is from your bank, keep in mind that banks do not ask their clients for private information via email.&lt;/li&gt;
&lt;li&gt;If you think the social security office emailed you to obtain your personal information, keep in mind that they do not initiate or solicit private information via email.&lt;/li&gt;
&lt;li&gt;Companies should not solicit private information unless you initiate first.&lt;/li&gt;
&lt;li&gt;Online retailers should not ask for your private information unless you initiate first.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;a-security-concern-going-phishing&#34;&gt;A Security Concern: Going Phishing!&lt;/h3&gt;
&lt;div style=&#34;float: right; padding: 20px;&#34;&gt;&lt;img src=&#34;/blog/2020/02/end-point-security-tips/image-1.jpg&#34; alt=&#34;phishing fraud&#34; align=&#34;right&#34; hspace=&#34;10&#34;&gt;&lt;p&gt;&lt;a href=&#34;https://flic.kr/p/2gLaNqk&#34;&gt;Photo&lt;/a&gt; by &lt;a href=&#34;https://www.epictop10.com/&#34;&gt;Epic Top 10 Site&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;One of the more common ways to steal someone’s private information is through phishing. Phishing is like fishing: you try to catch something. In this case, the ‘fish’ is your data. Someone with malicious intent sends you email to attempt to get you to click on the link, picture, content, etc. within the email. Once you click the link or content within the email, it might take you to a website to enter or reset your password, even ask for your social security number or other personal information. The person with malicious intent would use the information collect to open accounts, purchase items online or resell your personal data. The links within the phishing email might even redirect you to a fake website that mimics a real website to collect your personal data. The goal is to confuse the email recipient into believing that the email message is legit in its attempt to collect personal information from the user.&lt;/p&gt;
&lt;h4 id=&#34;phishing-exercises&#34;&gt;Phishing Exercises&lt;/h4&gt;
&lt;div style=&#34;float: left; padding: 20px;&#34;&gt;&lt;img src=&#34;/blog/2020/02/end-point-security-tips/image-2.jpg&#34; alt=&#34;phishing attack&#34; hspace=&#34;10&#34;&gt;&lt;p&gt;&lt;a href=&#34;https://flic.kr/p/x9zZ4A&#34;&gt;Photo&lt;/a&gt; by &lt;a href=&#34;https://www.flickr.com/photos/christiaancolen/&#34;&gt;Christiaan Colen&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;One way to help the staff better their understanding of a phishing email is to practice phishing exercises by setting up an experiment to see which users would click on your test phishing email. If the user clicks on the experiment phishing email, the email administrator would notify the compliance officer and he or she would re-train the staff on how to properly differentiate real emails from phishing emails. Phishing exercises make a great activity for the employees to avoid the email scams and exert more caution in the future.&lt;/p&gt;
&lt;h3 id=&#34;essential-security-tools&#34;&gt;Essential Security Tools&lt;/h3&gt;
&lt;h4 id=&#34;firewall&#34;&gt;Firewall&lt;/h4&gt;
&lt;p&gt;A firewall should be a mandatory technology for all consumers or businesses. This is basically your main door. The office or organization’s technology is what the firewall is protecting. When you sit in your office working on the computer, just imagine that you are in a fort surrounded by walls. If someone needs to come in, they must be given permission by a gatekeeper to enter the fort. The firewall is very similar. The firewall allows network traffic to go in and out of the office based on the configuration set by a network engineer. The engineer determines what network traffic comes into the office and goes out.&lt;/p&gt;
&lt;p&gt;Companies typically test their firewall with penetration tests and network scans to determine if there are any security concerns after implementing the firewall. The testing of the firewall verifies that good security practices are implemented and the firewall is setup properly and securely.&lt;/p&gt;
&lt;p&gt;Some hardware firewall devices to consider include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.watchguard.com/wgrd-products/rack-mount/firebox-m270-m370&#34; target=&#34;_blank&#34;&gt;WatchGuard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cisco.com/c/en/us/products/security/firewalls/index.html&#34; target=&#34;_blank&#34;&gt;Cisco Firepower&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.ui.com/unifi-routing/unifi-security-gateway-pro-4/&#34; target=&#34;_blank&#34;&gt;UniFi Security Gateway Pro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;network-assessment--internal-threat-protection&#34;&gt;Network Assessment &amp;amp; Internal Threat Protection&lt;/h4&gt;
&lt;p&gt;Why is security vulnerability testing necessary? Many organizations have legacy servers, or desktops/​laptops with operating systems that are no longer supported. For example, outdated Microsoft Windows XP and 7 can be compromised by malware while browsing the Internet due to &lt;a href=&#34;https://www.pcworld.com/article/3400698/nsa-warns-that-bluekeep-vulnerability-in-windows-xp-and-windows-7-is-especially-dangerous.html&#34;&gt;the BlueKeep vulnerability&lt;/a&gt;. Systems that are not patched could expose your organization to be infected with malware such as ransomware which would hold your data ransom until you pay to unlock the data.&lt;/p&gt;
&lt;p&gt;Security vulnerability testing typically results in a report outlining problematic areas such as outdated operating systems, private data that does not belong on a file server, such as social security or credit card number (personally identifying information, or PII). If PII is needed for the organization to operate, then higher security standards and an encryption system to store the private data is needed. The security vulnerabilities testing could also check if your environment is HIPAA or PCI DSS compliant if those security standards apply to you.&lt;/p&gt;
&lt;p&gt;Some security vulnerability testing technology includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.rapidfiretools.com/products/network-detective/&#34; target=&#34;_blank&#34;&gt;Rapid Fire Tools Network Detective&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.rapidfiretools.com/products/cyber-hawk/&#34; target=&#34;_blank&#34;&gt;Rapid Fire Tools Cyberhawk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;enterprise-antivirus-systems&#34;&gt;Enterprise Antivirus Systems&lt;/h4&gt;
&lt;p&gt;Antivirus software is the last line of defense if malware enters your computer. The antivirus itself would not protect you 100% from being infected by malware or virus but if you have multiple layers of security in place, then the chances are much lower that your organization’s systems would not be compromised. A company called &lt;a href=&#34;https://www.av-comparatives.org/tests/business-security-test-2019-march-june/&#34;&gt;AV-Comparatives assessed some of the popular antivirus software&lt;/a&gt; in the market.&lt;/p&gt;
&lt;p&gt;The battle with malware is endless. Case in point: The &lt;a href=&#34;https://techcrunch.com/2019/05/12/wannacry-two-years-on/&#34;&gt;WannaCry&lt;/a&gt; malware affected over 200,000 machines across the world and spread quickly. Security researchers quickly realized the malware was spreading like a computer worm, across computers and over the network, using the Windows SMB protocol.&lt;/p&gt;
&lt;p&gt;New variants of viruses and malware are developed every day, so the antivirus companies are also daily hard at work developing a way to block and remove malware.&lt;/p&gt;
&lt;p&gt;Some antivirus software includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.webroot.com/us/en/business/smb/endpoint-protection&#34; target=&#34;_blank&#34;&gt;Webroot Antivirus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.broadcom.com/products/cyber-security/endpoint/end-user&#34; target=&#34;_blank&#34;&gt;Symantec Endpoint Security&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;web-filtering-technology&#34;&gt;Web Filtering Technology&lt;/h4&gt;
&lt;p&gt;Web filtering technology blocks websites that are malicious or deemed not appropriate to visit from an organization’s network. For example, websites for gambling could be blocked to reduce employee distractions, but also to reduce access to sites popularly infected with malware, reducing the possibility of malware coming into your network.&lt;/p&gt;
&lt;p&gt;There are many competitors out there in this competitive market, and some vendors offer free proof-of-concept testing with their product before you make a big investment. Take a look at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.forcepoint.com/product/url-filtering&#34; target=&#34;_blank&#34;&gt;Forcepoint Web Filtering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.webtitan.com/webtitan-gateway/&#34; target=&#34;_blank&#34;&gt;Web Titan Gateway&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;data-loss-prevention&#34;&gt;Data Loss Prevention&lt;/h4&gt;
&lt;p&gt;Employees’ and businesses’ private information is sensitive and should be protected. Businesses, whether audited or not, should always protect their employee private information. 20 years ago paper was used to store private information and locked in a file cabinet, but in 2020 most private information is stored digitally. How do companies keep private information from leaving their office?&lt;/p&gt;
&lt;p&gt;Physically you probably can’t stop someone from walking out with private information, but digitally there is technology called digital loss prevention (DLP) that can help keep confidential information from leaving the office. For example, if someone in the office decides to copy private information onto USB storage, or tries to send a social security number or credit card information via email, this can often be prevented. Prepare your business with solutions tailored for regulations involving personal information using DLP software.&lt;/p&gt;
&lt;p&gt;Some DLP solutions to consider include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.forcepoint.com/product/dlp-data-loss-prevention&#34; target=&#34;_blank&#34;&gt;Forcepoint DLP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.broadcom.com/products/cyber-security/information-protection/data-loss-prevention&#34; target=&#34;_blank&#34;&gt;Symantec Data Loss Prevention&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;email-filtering&#34;&gt;Email Filtering&lt;/h4&gt;
&lt;p&gt;Probably one of the easiest and oldest methods to infect or phish someone is via email. There are multiple mechanisms email filtering uses to stop malicious emails: SPF and DKIM, blacklists, etc. On top of these configurable items, email filtering software vendors often release updates throughout the day to block the latest known malware or spam based on heuristics.&lt;/p&gt;
&lt;p&gt;Cloud email services such as Microsoft Office 365 are for many businesses superior to on-premise email servers due to having all the bells and whistles to proactively protect your email environment, such as spam &amp;amp; virus filtering, and email archiving for retention purposes. If you need email encryption to protect sensitive emails, this feature is also available.&lt;/p&gt;
&lt;p&gt;The distinct advantage of using hosted email service is that the cost is predictable and includes maintenance, so your email administrators do not have to worry about updating the email server software or hardware.&lt;/p&gt;
&lt;p&gt;Email filtering technology available includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.microsoft.com/en-us/security/business/threat-protection/office-365-defender&#34; target=&#34;_blank&#34;&gt;Microsoft Defender for Office 365&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.forcepoint.com/product/email-security&#34; target=&#34;_blank&#34;&gt;Forcepoint Email Filtering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.spamtitan.com/email-filtering-service/&#34; target=&#34;_blank&#34;&gt;SpamTitan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;two-factor-authentication-2fa&#34;&gt;Two Factor Authentication (2FA)&lt;/h4&gt;
&lt;p&gt;Companies like Duo (owned by Cisco) and Google’s two-factor authentication technology are great tools to implement to improve your overall security. Beyond the usual user name and password, your smartphone or a hardware token are used to authorize the access to a website, an application, or a network. This technology is now well-established and available in most online services, and can be added to your own custom business applications.&lt;/p&gt;
&lt;p&gt;Some starting points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://duo.com/product/multi-factor-authentication-mfa&#34; target=&#34;_blank&#34;&gt;Duo Multi-Factor Authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.google.com/landing/2step/&#34; target=&#34;_blank&#34;&gt;Google Authenticator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;vpn-virtual-private-network&#34;&gt;VPN (Virtual Private Network)&lt;/h4&gt;
&lt;p&gt;Virtual private network technology allows businesses to provide secure access to office systems or applications to employees who travel or work from a remote location. That is important so that data traveling to and from the source, and many details of traffic patterns, are encrypted, so that the data cannot be captured and decrypted. VPN solutions have been around for years and are an important tool to securely and safely protect data in transit.&lt;/p&gt;
&lt;h4 id=&#34;password-reset-systems&#34;&gt;Password Reset Systems&lt;/h4&gt;
&lt;p&gt;Why are self-service password reset systems necessary, and are they secure? They eliminate many manual password assignment mistakes, keep passwords private to the user alone, and allow an organization to integrate two-factor authentication to securely reset user passwords, such as Active Directory for Windows, or other single sign-on password. Onboarding is needed for each user, which is done by sending them an email with a link to register.&lt;/p&gt;
&lt;p&gt;The password reset system could be an internal system only available to your organization. Another possibility is to access the system via VPN or even through a proxy server in the DMZ to allow password reset from anywhere.&lt;/p&gt;
&lt;p&gt;Some password reset systems include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.manageengine.com/products/self-service-password/&#34; target=&#34;_blank&#34;&gt;ManageEngine ADSelfService Plus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.microsoft.com/en-us/azure/active-directory/authentication/concept-sspr-howitworks&#34; target=&#34;_blank&#34;&gt;Azure AD Self-Service Password Reset&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;recovery-tools&#34;&gt;Recovery Tools&lt;/h3&gt;
&lt;h4 id=&#34;data-and-system-backupsdisaster-recovery&#34;&gt;Data and System Backups/​Disaster Recovery&lt;/h4&gt;
&lt;p&gt;Data backup and disaster recovery practice is a critical component for a business to speed up the process of recovery if malware attacks your systems or if a system becomes inoperable.&lt;/p&gt;
&lt;p&gt;If you are attacked by ransomware and all the critical systems and desktops are compromised and rendered useless, then the only way to recover is from backups. At that point, the disaster recovery preparation you made will be well worth what you invested into it. If you did not have an adequate backup or disaster recovery plan, your organization or company would have to start from scratch and rebuild all systems and desktops which could take weeks if not months to recover from. The lost wages, and possibly clients, due to unavailable systems and desktop to operate, probably would cost you far more than preparation does. The more options you have, the better the chances that you will get out of your bind and not prolong the situation.&lt;/p&gt;
&lt;p&gt;Some backup solutions I have worked with have the flexibility to mix and match to fit your configuration needs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.acronis.com/en-us/products/cloud/cyber-protect/backup/&#34; target=&#34;_blank&#34;&gt;Acronis Backup Solution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.rubrik.com/solutions/backup-recovery&#34; target=&#34;_blank&#34;&gt;Rubrik&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://azure.microsoft.com/en-us/services/backup/&#34; target=&#34;_blank&#34;&gt;Azure Backup&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Acronis Cloud Backup allows you to back up to local storage, the cloud, and off-premise, recover a system using a USB drive, back up Office 365 emails, or backup VMware or Hyper-V environments. It also allows you to recover a single file if needed.&lt;/p&gt;
&lt;h3 id=&#34;additional-security-recommendations&#34;&gt;Additional Security Recommendations&lt;/h3&gt;
&lt;h4 id=&#34;ssltls-certificates&#34;&gt;SSL/TLS Certificates&lt;/h4&gt;
&lt;p&gt;What are SSL and TLS? SSL is outdated and replaced by TLS, but people often still use the familiar SSL name. They are an encryption system to keep sensitive information sent across the Internet encrypted so that only the intended recipient can access it.&lt;/p&gt;
&lt;p&gt;When an SSL certificate is validated, the transmitted data is not only unreadable by anyone except for the intended server, but you are relatively well assured you are communicating with the organization you expected and not with a malicious intermediary. Read more details in &lt;a href=&#34;https://www.sslshopper.com/why-ssl-the-purpose-of-using-ssl-certificates.html&#34;&gt;SSL Shopper’s Why SSL?&lt;/a&gt; article.&lt;/p&gt;
&lt;p&gt;SSL certificates were historically used primarily for higher-security web servers, but now are commonly used on almost all web servers. They are also used for other remote server access, B2B server-to-server communication, VPN access, email systems, etc.&lt;/p&gt;
&lt;h3 id=&#34;recent-destructive-incidents&#34;&gt;Recent Destructive Incidents&lt;/h3&gt;
&lt;p&gt;These incidents from just a few weeks in fall 2019 show that we have a long way to go in protecting our technology usage:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://www.nbcnewyork.com/news/local/long-island-schools-hacked-district-forced-to-pay-88000-in-ransom/1491924/&#34; target=&#34;_blank&#34;&gt;Long Island Schools Hacked; District Forced to Pay $88,000 in Ransom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.nbcnewyork.com/news/local/ny-school-delays-start-of-year-after-ransomware-attack/1990459/&#34; target=&#34;_blank&#34;&gt;NY School Delays Start of Year After Ransomware Attack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.newsweek.com/tortoiseshell-hacker-hire-military-heroes-fake-website-1461320&#34; target=&#34;_blank&#34;&gt;Veterans Targeted by Hackers Through Fake Military Heroes Hiring Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.zdnet.com/article/hackers-target-transportation-and-shipping-industries-in-new-trojan-malware-campaign/&#34; target=&#34;_blank&#34;&gt;Hackers Target Transportation and Shipping Companies in New Trojan Malware Campaign&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://inc42.com/buzz/hacking-routers-webcams-printers-are-the-most-searched-keywords-on-the-dark-web/&#34; target=&#34;_blank&#34;&gt;Hacking of IoT – Internet of Things (webcams, security cams, printers, home routers)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;speak-with-us&#34;&gt;Speak With Us!&lt;/h3&gt;
&lt;p&gt;Keeping up with security can be a full-time job. If you need professional consulting on security tools and implementing them, &lt;a href=&#34;/contact/&#34;&gt;contact&lt;/a&gt; our team today.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>SCAM ALERT: EndPoint Petroleum Corporation, P&amp;Z Petroleum</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2019/06/endpoint-petroleum-scam/"/>
      <id>https://www.endpointdev.com/blog/2019/06/endpoint-petroleum-scam/</id>
      <published>2019-06-22T00:00:00+00:00</published>
      <author>
        <name>Benjamin Goldstein</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2019/06/endpoint-petroleum-scam/image-0.jpg&#34; alt=&#34;Person using laptop computers&#34; /&gt;&lt;br&gt;Photo by &lt;a href=&#34;https://unsplash.com/photos/9SoCnyQmkzI&#34;&gt;Jefferson Santos&lt;/a&gt; on Unsplash&lt;/p&gt;
&lt;p&gt;This blog post is to alert the public, targeted individuals, and relevant authorities about a scam being perpetrated under the guise of a fictitious company called “EndPoint Petroleum Corporation”. The scammers have set up a website for this fake company, copying much of the content from &lt;a href=&#34;https://www.nblenergy.com&#34;&gt;Noble Energy&lt;/a&gt;. The scammers have adapted an old version of our logo for their nefarious purposes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/06/endpoint-petroleum-scam/image-1.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;We will refrain from posting the URL for the scammers’ website since we don’t want to increase its SEO ranking, but it is shown in the image capture above. At first glance this website looks legitimate, but closer observation provides abundant evidence to the contrary. Rather than posting a list of its defects which would assist the scammers, I urge the interested reader to spend five minutes on the website.&lt;/p&gt;
&lt;p&gt;An email forwarded to us from one of their targets provides an example of how the scammers are trying to use their website. They are targeting people outside the US with experience in the Oil and Gas Industry, telling them that they have been “shortlisted” for an Operations Manager position with the bogus company. The scammers then tell their targets to contact their “required travel agency” to make arrangements for a trip to the United States for an in-person interview.&lt;/p&gt;
&lt;p&gt;We know that the scammers are using LinkedIn to contact their targets. They may also be contacting potential targets via other means as well. And it stands to reason that they may have posted their fake job offer on one or more job boards.&lt;/p&gt;
&lt;h3 id=&#34;trying-to-take-this-down&#34;&gt;Trying to Take This Down&lt;/h3&gt;
&lt;p&gt;We have contacted and followed up with various authorities and organizations about this scam. We are going to continue our efforts in this regard. We welcome any assistance or advice that anyone can offer that would help with this. Please use our contact form to contact us.&lt;/p&gt;
&lt;h3 id=&#34;sample-scam-letters-and-job-description&#34;&gt;Sample Scam Letters and Job Description&lt;/h3&gt;
&lt;p&gt;Below are several attachments that have been forwarded to us by one of their intended victims:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/06/endpoint-petroleum-scam/image-2.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/06/endpoint-petroleum-scam/image-3.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;table&gt;
  &lt;tr&gt;
    &lt;td style=&#34;background-color: white; padding: 0&#34;&gt;
      &lt;img src=&#34;/blog/2019/06/endpoint-petroleum-scam/image-4.jpg&#34; /&gt;
    &lt;/td&gt;
    &lt;td style=&#34;background-color: white; padding: 0&#34;&gt;
      &lt;img src=&#34;/blog/2019/06/endpoint-petroleum-scam/image-5.jpg&#34; /&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td style=&#34;background-color: white; padding: 0&#34;&gt;
      &lt;img src=&#34;/blog/2019/06/endpoint-petroleum-scam/image-6.jpg&#34; /&gt;
    &lt;/td&gt;
    &lt;td style=&#34;background-color: white; padding: 0&#34;&gt;
      &lt;img src=&#34;/blog/2019/06/endpoint-petroleum-scam/image-7.jpg&#34; /&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;h3 id=&#34;december-2019-update-pz-petroleum&#34;&gt;December 2019 Update: P&amp;amp;Z Petroleum&lt;/h3&gt;
&lt;p&gt;Several potential victims have informed us that the scammers have begun using another name, P&amp;amp;Z Petroleum or PZ Petroleum, signing letters with:&lt;/p&gt;
&lt;p&gt;Mr. Gary Mason&lt;br&gt;
Human Resource Manager&lt;br&gt;
3422 San Felipe Street&lt;br&gt;
Houston, TX 77056-2723&lt;br&gt;
United States&lt;br&gt;
&lt;a href=&#34;mailto:hr.department@pzpetroleum.com&#34;&gt;hr.department@pzpetroleum.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Contact Person: Mr. Kris Clark&lt;br&gt;
STAR TRAVELS AGENCY&lt;br&gt;
2400 Augusta Dr&lt;br&gt;
Houston, TX 77057, USA&lt;br&gt;
Phone Number: +1 832-861-1505&lt;br&gt;
Email: &lt;a href=&#34;mailto:kris.clark@startravelsagency.com&#34;&gt;kris.clark@startravelsagency.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;They are also trying to lure medical doctors and nurses, in addition to various management and engineering roles.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If they have contacted you, run away and report them to the authorities!&lt;/strong&gt;&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>OWASP Top Ten Application Security Risks</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2019/02/owasp-top-ten-application-security-risks/"/>
      <id>https://www.endpointdev.com/blog/2019/02/owasp-top-ten-application-security-risks/</id>
      <published>2019-02-27T00:00:00+00:00</published>
      <author>
        <name>Marco Pessotto</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2019/02/owasp-top-ten-application-security-risks/wasps.jpg&#34; alt=&#34;wasps&#34;&gt;&lt;/p&gt;
&lt;p&gt;I don’t consider myself a security expert. Still, to my surprise, I
was asked to give a talk about security to all the End Point
developers. Obviously I realized too late what I was getting myself
into! Such an audience is not only pretty large, it is also
challenging, many are more competent than me, and the risk to bore them
is very high. Yet, the slides were prepared, the talk was given and
the feedback was good.&lt;/p&gt;
&lt;p&gt;It goes without saying that a broad, generic training about security,
which still can give something to the listener, can’t be really
improvised.&lt;/p&gt;
&lt;p&gt;The platform for the talk was the
&lt;a href=&#34;https://www.owasp.org/index.php/Category:OWASP_Top_Ten_2017_Project&#34;&gt;OWASP Top Ten 2017 Project&lt;/a&gt;,
which discusses the most critical security risks to
web applications.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.owasp.org/&#34;&gt;OWASP&lt;/a&gt; stands for Open Web Application
Security Project and describes itself as “an open community dedicated
to enabling organizations to develop, purchase, and maintain
applications and APIs that can be trusted.” Its website provides
plenty of resources to the developers.&lt;/p&gt;
&lt;p&gt;The Top Ten consists of 10 broad classes of vulnerabilities. The
data behind that comes from specialized firms and surveys, gathering
information from 100,000 real-world applications and APIs. Some of
these classes are very broad and have a taxonomic value. Saying, for
example, “Broken access control” or “Security misconfiguration”,
doesn’t say much what you are doing wrong. It still reminds us that a
lot of things can actually go wrong and that when working on a service
you should ask yourself how are you doing with regard to security.&lt;/p&gt;
&lt;p&gt;The current (2017) Top Ten classes are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Injection&lt;/li&gt;
&lt;li&gt;Broken Authentication&lt;/li&gt;
&lt;li&gt;Sensitive Data Exposure&lt;/li&gt;
&lt;li&gt;XML External Entities (XXE)&lt;/li&gt;
&lt;li&gt;Broken Access Control&lt;/li&gt;
&lt;li&gt;Security Misconfiguration&lt;/li&gt;
&lt;li&gt;Cross-Site Scripting (XSS)&lt;/li&gt;
&lt;li&gt;Insecure Deserialization&lt;/li&gt;
&lt;li&gt;Using Components with Known Vulnerabilities&lt;/li&gt;
&lt;li&gt;Insufficient Logging &amp;amp; Monitoring&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I’m not going over the Top Ten in detail like I did at the training, but I’m
going to leave here a couple of brief notes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Injections:&lt;/strong&gt; in my naivety I was convinced that this class of
vulnerability, which is very specific and whose fix is well-​known, was
something belonging to the past, to legacy and neglected applications.
Still, it’s not so. It’s 2019 and injections are still winning.&lt;/p&gt;
&lt;p&gt;If by chance you are still interpolating variables inside the SQL and not
using placeholders, it’s time to take a look at &lt;a href=&#34;http://bobby-tables.com/&#34;&gt;Bobby
Tables&lt;/a&gt; (or equivalent) and start doing it
right, as every major language has a library supporting them. Maybe
you avoid this vulnerability in your own code, but have inherited code
from others who did not defend against it. Look and see!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Broken Authentication:&lt;/strong&gt; On the other hand, finding the authentication problems in second
place was not a surprise at all, given the frequency of leaked
databases. The recommended practice appears to be checking the
password against a list of known passwords and ensuring it has a
decent length. (Please note: no requiring regular password rotation, nor
uppercase/​lowercase/​digit enforcing.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;XSS:&lt;/strong&gt; For web developers an interesting class is the ubiquitous Cross-Site
Scripting (XSS) vulnerability, which is found in two-​thirds of all
applications. Statistically speaking, it’s probable that the
application you are currently working on is affected.&lt;/p&gt;
&lt;p&gt;XSS comes in many flavors, usually generated server-​side, when a
variable coming from user input is interpolated without proper
escaping into the HTML, opening the door to JavaScript execution, but
DOM XSS is also common. How many times did you see something like
this, for example, with jQuery:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#app&amp;#39;&lt;/span&gt;).html(string_from_user)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;html()&lt;/code&gt; method stuffs whatever string is provided by the user
into the HTML page. This should be rewritten as:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;#app&amp;#39;&lt;/span&gt;).text(string_from_user)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If formatting is required, the &lt;code&gt;append()&lt;/code&gt; method is your friend.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CSRF:&lt;/strong&gt; Interestingly enough, Cross Site Request Forgery (when an
application doesn’t check if a POST request comes from the site itself
and not from a random site on the internet which is fooling the
authenticated browser to perform an operation on your site) didn’t
make into the Top Ten. This is probably due to the fact that
mainstream frameworks like Ruby on Rails and Django come with CSRF
protections almost out of the box.&lt;/p&gt;
&lt;p&gt;The goal of the OWASP document is to increase the awareness about
security problem. We are trying to do our part to prevent security problems at End Point.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Where are you with your Windows OS in 2019?</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2019/02/where-are-you-with-windows-2019/"/>
      <id>https://www.endpointdev.com/blog/2019/02/where-are-you-with-windows-2019/</id>
      <published>2019-02-12T00:00:00+00:00</published>
      <author>
        <name>Dan Briones</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2019/02/where-are-you-with-windows-2019/image-0.jpg&#34; alt=&#34;Windows home row&#34; /&gt;&lt;br&gt;&lt;a href=&#34;https://www.flickr.com/photos/bradleypjohnson/6139142250/&#34;&gt;Photo by bradleypjohnson&lt;/a&gt; · &lt;a href=&#34;https://creativecommons.org/licenses/by/2.0/&#34;&gt;CC BY 2.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It should be of little surprise that on January 14, 2020, after a decade of Windows 7, Microsoft will stop providing security updates and support for this older operating system. Windows 7 was released in 2009, and due to its stability enjoyed many years as the go-to operating system for home and business alike.&lt;/p&gt;
&lt;p&gt;Even now, it is estimated by &lt;a href=&#34;https://netmarketshare.com/operating-system-market-share.aspx&#34;&gt;NetMarketShare&lt;/a&gt; that over 40% of businesses still rely on it. Despite Microsoft having ended mainstream support for Windows 7 in 2015, it still offered extended support because of the operating system’s popularity, and the generally slow adoption of newer releases. However, that support shall soon end, as will support for Windows Server 2008 R2 (release 2), which also remains in wide use. Organizations of all kinds will need to upgrade to newer operating systems to remain secure.&lt;/p&gt;
&lt;p&gt;The corporate adoption of Windows 8 and 8.1 may have been slow in part due to Microsoft’s radical changes to the user interface, such as replacing navigation menus with information-​filled “live” tiles. Windows 10, however, was designed as a compromise, providing a Windows 7-​like Start menu, while preserving the live tile interface for accessing applications and services from the desktop. There are many compelling reasons for moving from Windows 7 to Windows 10. The most notable is that Windows 7 will no longer receive any security updates, creating serious security risk and compliance issues.&lt;/p&gt;
&lt;p&gt;In 2018, Microsoft adopted a Semi-​Annual Channel (SAC) governed by the &lt;a href=&#34;https://support.microsoft.com/en-us/help/30881&#34;&gt;Modern Lifecycle Policy&lt;/a&gt;. This means that feature updates are released to the public twice a year, around March and September. Updates are cumulative. Updating to these releases is required to remain eligible for support from Microsoft. Security updates are released monthly and quarterly as rollups, and critical updates are released as needed. This is part of Microsoft’s plan to minimize vulnerabilities and security exposure in an ever-​changing digital world.&lt;/p&gt;
&lt;h3 id=&#34;time-to-change&#34;&gt;Time to change&lt;/h3&gt;
&lt;p&gt;At End Point we consider keeping our clients’ systems updated both fundamental and essential. Companies need stable systems in order to operate, and uptime is of critical importance. This is one reason we offer our RMM (Remote Management and Monitoring) services. We connect with our clients’ systems to receive custom-​defined policies for patching, then monitor and test updates before we deploy them on the cycle that best suits our customer’s workflow.&lt;/p&gt;
&lt;p&gt;We are committed to helping our clients transition safely and efficiently to Windows 10. It can be a lengthy process when accounting for the time it takes to properly test all business applications and processes before transitioning to a new OS, and the time it takes to make any necessary hardware changes and complete the update.&lt;/p&gt;
&lt;p&gt;See the charts below to learn when service will end for various versions of Windows, and contact us for a consultation on the best ways to manage your IT to keep your organization secure.&lt;/p&gt;
&lt;h3 id=&#34;windows10&#34;&gt;Windows 10&lt;/h3&gt;
&lt;hr&gt;
&lt;div class=&#34;table-scroll&#34;&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;OS version&lt;/th&gt;&lt;th&gt;Date of availability&lt;/th&gt;&lt;th&gt;End of service for Home, Pro, and Pro for Workstation Editions&lt;/th&gt;&lt;th&gt;End of service for Enterprise and Education Editions&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
 &lt;tr&gt;&lt;td&gt;Windows&amp;nbsp;10, version 1809&lt;/td&gt;&lt;td&gt;November 13, 2018&lt;/td&gt;&lt;td&gt;May 12, 2020&lt;/td&gt;&lt;td&gt;May 11, 2021&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;Windows&amp;nbsp;10, version 1803&lt;/td&gt;&lt;td&gt;April 30, 2018&lt;/td&gt;&lt;td&gt;November 12, 2019&lt;/td&gt;&lt;td&gt;November 10, 2020&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;Windows&amp;nbsp;10, version 1709&lt;/td&gt;&lt;td&gt;October 17, 2017&lt;/td&gt;&lt;td&gt;April 9, 2019&lt;/td&gt;&lt;td&gt;April 14, 2020&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;Windows&amp;nbsp;10, version 1703&lt;/td&gt;&lt;td&gt;April 5, 2017&lt;/td&gt;&lt;td&gt;October 9, 2018&lt;/td&gt;&lt;td&gt;October 8, 2019&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;Windows&amp;nbsp;10, version 1607&lt;/td&gt;&lt;td&gt;August 2, 2016&lt;/td&gt;&lt;td&gt;April 10, 2018&lt;/td&gt;&lt;td&gt;April 9, 2019&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;Windows&amp;nbsp;10, version 1511&lt;/td&gt;&lt;td&gt;November 10, 2015&lt;/td&gt;&lt;td&gt;October 10, 2017&lt;/td&gt;&lt;td&gt;October 10, 2017&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;Windows&amp;nbsp;10, released July 2015 (version 1507)&lt;/td&gt;&lt;td&gt;July 29, 2015&lt;/td&gt;&lt;td&gt;May 9, 2017&lt;/td&gt;&lt;td&gt;May 9, 2017&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;h3 id=&#34;enterprise-ltscltsb-editions&#34;&gt;Enterprise LTSC/​LTSB Editions&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;Windows 10 LTSC/​LTSB editions also follow the Fixed Lifecycle policy. To learn more, see &lt;a href=&#34;https://support.microsoft.com/en-us/help/14085&#34;&gt;Microsoft Business, Developer and Desktop Operating Systems Policy&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;table-scroll&#34;&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;OS version&lt;/th&gt;&lt;th&gt;Date of availability&lt;/th&gt;&lt;th&gt;Mainstream support end date&lt;/th&gt;&lt;th&gt;Extended support end date&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
 &lt;tr&gt;&lt;td&gt;Windows&amp;nbsp;10 Enterprise LTSC 2019 / Windows&amp;nbsp;10 IoT Enterprise LTSC 2019&lt;/td&gt;&lt;td&gt;November 13, 2018&lt;/td&gt;&lt;td&gt;January 9, 2024&lt;/td&gt;&lt;td&gt;January 9, 2029&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;Windows&amp;nbsp;10 Enterprise 2016 LTSB / Windows&amp;nbsp;10 IoT Enterprise 2016 LTSB&lt;/td&gt;&lt;td&gt;August 2, 2016&lt;/td&gt;&lt;td&gt;October 12, 2021&lt;/td&gt;&lt;td&gt;October 13, 2026&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;Windows&amp;nbsp;10 Enterprise 2015 LTSB / Windows&amp;nbsp;10 IoT Enterprise 2015 LTSB&lt;/td&gt;&lt;td&gt;July 29, 2015&lt;/td&gt;&lt;td&gt;October 13, 2020&lt;/td&gt;&lt;td&gt;October 14, 2025&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Note: Not all features in an update will work on all devices. A device may not be able to receive updates if the device hardware is incompatible, lacks current drivers, or is otherwise outside the original equipment manufacturer’s (OEM) support period.&lt;/p&gt;
&lt;p&gt;For more information on the Windows 10 lifecycle, see &lt;a href=&#34;https://support.microsoft.com/en-us/help/4076506&#34;&gt;Windows 10 Client and Windows Server Semi-​Annual Channel Lifecycle Policy update (February 1, 2018)&lt;/a&gt; or the &lt;a href=&#34;https://technet.microsoft.com/windows/release-info.aspx&#34;&gt;Windows 10 release information&lt;/a&gt; page. To learn more about the Windows 10 mobile lifecycle, see &lt;a href=&#34;https://support.microsoft.com/lifecycle/search?alpha=Windows%2010%20Mobile&#34;&gt;Windows 10 Mobile&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;windows81-and-7&#34;&gt;Windows 8.1 and 7&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;Prior releases of the Windows operating system are likewise governed by the Fixed Lifecycle Policy. This policy comprises two phases: mainstream support and extended support. See &lt;a href=&#34;https://support.microsoft.com/en-us/help/14085&#34;&gt;Microsoft Business, Developer and Desktop Operating Systems Policy&lt;/a&gt; for more details.&lt;/p&gt;
&lt;div class=&#34;table-scroll&#34;&gt;
&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;OS version&lt;/th&gt;&lt;th&gt;End of mainstream support&lt;/th&gt;&lt;th&gt;End of extended support&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
 &lt;tr&gt;&lt;td&gt;Windows&amp;nbsp;8.1&lt;/td&gt;&lt;td&gt;January 9, 2018&lt;/td&gt;&lt;td&gt;January 10, 2023&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;Windows&amp;nbsp;7, service pack 1*&lt;/td&gt;&lt;td&gt;January 13, 2015&lt;/td&gt;&lt;td&gt;January 14, 2020&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;* Support for Windows 7 RTM without service packs ended on April 9, 2013. Be sure to install &lt;a href=&#34;https://support.microsoft.com/en-us/help/15090&#34;&gt;Windows 7 Service Pack 1&lt;/a&gt; to continue to receive support and updates.&lt;/p&gt;
&lt;p&gt;Prior versions of Windows, including Windows 7 and Windows 8.1, have limited support when running on new processors and chipsets from manufacturers like Intel, AMD, Nvidia, and Qualcomm. For more information, see the &lt;a href=&#34;http://go.microsoft.com/fwlink/p/?LinkId=722733&#34;&gt;Microsoft Lifecycle Policy&lt;/a&gt;. A device may not be able to run prior versions of Windows if the device hardware is incompatible, lacks current drivers, or is otherwise outside the original equipment manufacturer’s (OEM) support period.&lt;/p&gt;
&lt;p&gt;(Windows life cycle charts and information were taken from Microsoft’s windows support &lt;a href=&#34;https://support.microsoft.com/en-us/help/13853/windows-lifecycle-fact-sheet&#34;&gt;site&lt;/a&gt;.)&lt;/p&gt;

      </content>
    </entry>
  
</feed>
