<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ authentication - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ authentication - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 16:30:16 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/authentication/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Authenticate Users in Kubernetes: x509 Certificates, OIDC, and Cloud Identity ]]>
                </title>
                <description>
                    <![CDATA[ Kubernetes doesn't know who you are. It has no user database, no built-in login system, no password file. When you run kubectl get pods, Kubernetes receives an HTTP request and asks one question: who  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-authenticate-users-in-kubernetes-x509-certificates-oidc-and-cloud-identity/</link>
                <guid isPermaLink="false">69d4182f40c9cabf4484dbdb</guid>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Destiny Erhabor ]]>
                </dc:creator>
                <pubDate>Mon, 06 Apr 2026 20:31:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/36356282-0cfb-43a8-8461-84f20e64b041.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Kubernetes doesn't know who you are.</p>
<p>It has no user database, no built-in login system, no password file. When you run <code>kubectl get pods</code>, Kubernetes receives an HTTP request and asks one question: who signed this, and do I trust that signature? Everything else — what you're allowed to do, which namespaces you can access, whether your request goes through at all — comes after that question is answered.</p>
<p>This surprises most engineers who are new to Kubernetes. They expect something like a database of users with passwords. Instead, they find a pluggable chain of authenticators, each one able to vouch for a request in a different way:</p>
<ul>
<li><p>Client certificates</p>
</li>
<li><p>OIDC tokens from an external identity provider</p>
</li>
<li><p>Cloud provider IAM tokens</p>
</li>
<li><p>Service account tokens projected into pods.</p>
</li>
</ul>
<p>Any of these can be active at the same time.</p>
<p>Understanding this model is what separates engineers who can debug authentication failures from engineers who copy kubeconfig files and hope for the best.</p>
<p>In this article, you'll work through how the Kubernetes authentication chain works from first principles. You'll see how x509 client certificates are used — and why they're a poor choice for human users in production. You'll configure OIDC authentication with Dex, giving your cluster a real browser-based login flow. And you'll see how AWS, GCP, and Azure each plug into the same underlying model.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>A running kind cluster — a fresh one works fine, or reuse an existing one</p>
</li>
<li><p><code>kubectl</code> and <code>helm</code> installed</p>
</li>
<li><p><code>openssl</code> available on your machine (comes pre-installed on macOS and most Linux distros)</p>
</li>
<li><p>Basic familiarity with what a JWT is (a signed JSON object with claims) — you don't need to be able to write one, just recognise one</p>
</li>
</ul>
<p>All demo files are in the <a href="https://github.com/Caesarsage/DevOps-Cloud-Projects/tree/main/intermediate/k8/security">companion GitHub repository</a>.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-how-kubernetes-authentication-works">How Kubernetes Authentication Works</a></p>
<ul>
<li><p><a href="#heading-the-authenticator-chain">The Authenticator Chain</a></p>
</li>
<li><p><a href="#heading-users-vs-service-accounts">Users vs Service Accounts</a></p>
</li>
<li><p><a href="#heading-what-happens-after-authentication">What Happens After Authentication</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-use-x509-client-certificates">How to Use x509 Client Certificates</a></p>
<ul>
<li><p><a href="#heading-how-the-certificate-maps-to-an-identity">How the Certificate Maps to an Identity</a></p>
</li>
<li><p><a href="#the-cluster-ca">The Cluster CA</a></p>
</li>
<li><p><a href="#heading-the-limits-of-certificate-based-auth">The Limits of Certificate-Based Auth</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-demo-1--create-and-use-an-x509-client-certificate">Demo 1 — Create and Use an x509 Client Certificate</a></p>
</li>
<li><p><a href="#heading-how-to-set-up-oidc-authentication">How to Set Up OIDC Authentication</a></p>
<ul>
<li><p><a href="#heading-how-the-oidc-flow-works-in-kubernetes">How the OIDC Flow Works in Kubernetes</a></p>
</li>
<li><p><a href="#heading-the-api-server-configuration">The API Server Configuration</a></p>
</li>
<li><p><a href="#heading-jwt-claims-kubernetes-uses">JWT Claims Kubernetes Uses</a></p>
</li>
<li><p><a href="#heading-how-kubelogin-works">How kubelogin Works</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-demo-2--configure-oidc-login-with-dex-and-kubelogin">Demo 2 — Configure OIDC Login with Dex and kubelogin</a></p>
</li>
<li><p><a href="#heading-cloud-provider-authentication">Cloud Provider Authentication</a></p>
<ul>
<li><p><a href="#heading-aws-eks">AWS EKS</a></p>
</li>
<li><p><a href="#heading-google-gke">Google GKE</a></p>
</li>
<li><p><a href="#heading-azure-aks">Azure AKS</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-webhook-token-authentication">Webhook Token Authentication</a></p>
</li>
<li><p><a href="#heading-cleanup">Cleanup</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-how-kubernetes-authentication-works">How Kubernetes Authentication Works</h2>
<p>Every request that reaches the Kubernetes API server — whether from <code>kubectl</code>, a pod, a controller, or a CI pipeline — carries a credential of some kind.</p>
<p>The API server passes that credential through a chain of authenticators in sequence. The first authenticator that can verify the credential wins. If none can, the request is treated as anonymous.</p>
<h3 id="heading-the-authenticator-chain">The Authenticator Chain</h3>
<p>Kubernetes supports several authentication strategies simultaneously. You can have client certificate authentication and OIDC authentication active on the same cluster at the same time, which is common in production: cluster administrators use certificates, regular developers use OIDC. The strategies active on a cluster are determined by flags passed to the <code>kube-apiserver</code> process.</p>
<p>The strategies available are x509 client certificates, bearer tokens (static token files — rarely used in production), bootstrap tokens (used during node join operations), service account tokens, OIDC tokens, authenticating proxies, and webhook token authentication. A cluster doesn't have to use all of them, and most don't. But knowing they all exist helps when you're diagnosing an auth failure.</p>
<h3 id="heading-users-vs-service-accounts">Users vs Service Accounts</h3>
<p>There is an important distinction in how Kubernetes thinks about identity. Service accounts are Kubernetes objects — they live in a namespace, get created with <code>kubectl create serviceaccount</code>, and have tokens managed by the cluster itself. Every pod runs as a service account. These are machine identities for workloads.</p>
<p>Users, on the other hand, don't exist as Kubernetes objects at all. There is no <code>kubectl create user</code> command. Kubernetes doesn't manage user accounts. Instead, it trusts external systems to assert user identity — a certificate authority, an OIDC provider, or a cloud provider's IAM system. Kubernetes just verifies the assertion and extracts the username and group memberships from it.</p>
<table>
<thead>
<tr>
<th></th>
<th>Service Account</th>
<th>User</th>
</tr>
</thead>
<tbody><tr>
<td>Kubernetes object?</td>
<td>Yes — lives in a namespace</td>
<td>No — managed externally</td>
</tr>
<tr>
<td>Created with</td>
<td><code>kubectl create serviceaccount</code></td>
<td>External system (CA, IdP, cloud IAM)</td>
</tr>
<tr>
<td>Used by</td>
<td>Pods and workloads</td>
<td>Humans and CI systems</td>
</tr>
<tr>
<td>Token managed by</td>
<td>Kubernetes</td>
<td>External system</td>
</tr>
<tr>
<td>Namespaced?</td>
<td>Yes</td>
<td>No</td>
</tr>
</tbody></table>
<h3 id="heading-what-happens-after-authentication">What Happens After Authentication</h3>
<p>Authentication only answers one question: who is this? Once the API server has a verified identity — a username and zero or more group memberships — it passes the request to the authorisation layer. By default that is RBAC, which checks the identity against Role and ClusterRole bindings to determine what the request is allowed to do.</p>
<p>This is why authentication and authorisation are separate concerns in Kubernetes. A valid certificate gets you past the front door. What you can do inside is RBAC's job. An authenticated user with no RBAC bindings can authenticate successfully but will be denied every API call.</p>
<p>If you want a deep dive into how RBAC rules, roles, and bindings work, check out this handbook on <a href="https://www.freecodecamp.org/news/how-to-secure-a-kubernetes-cluster-handbook/">How to Secure a Kubernetes Cluster: RBAC, Pod Hardening, and Runtime Protection</a>.</p>
<h2 id="heading-how-to-use-x509-client-certificates">How to Use x509 Client Certificates</h2>
<p>x509 client certificate authentication is the oldest and simplest authentication method in Kubernetes. It's how <code>kubectl</code> works out of the box when you create a cluster — the kubeconfig file that <code>kind</code> or <code>kubeadm</code> generates contains an embedded client certificate signed by the cluster's Certificate Authority.</p>
<h3 id="heading-how-the-certificate-maps-to-an-identity">How the Certificate Maps to an Identity</h3>
<p>When the API server receives a request with a client certificate, it validates the certificate against its trusted CA, then reads two fields (The Common Name and Organization) from the certificate to construct an identity.</p>
<p>The <strong>Common Name (CN)</strong> field becomes the username. The <strong>Organization (O)</strong> field, which can contain multiple values, becomes the list of groups the user belongs to.</p>
<p>So a certificate with <code>CN=jane</code> and <code>O=engineering</code> authenticates as username <code>jane</code> in group <code>engineering</code>. If you want to give <code>jane</code> permissions, you create a RoleBinding that references either the username <code>jane</code> or the group <code>engineering</code> as a subject.</p>
<p>This is the same mechanism behind <code>system:masters</code>. When <code>kind</code> creates a cluster and writes a kubeconfig for you, it generates a certificate with <code>O=system:masters</code>. Kubernetes has a built-in ClusterRoleBinding that grants <code>cluster-admin</code> to anyone in the <code>system:masters</code> group. That's why your default kubeconfig has full admin access — it's not magic, it's a certificate with the right group.</p>
<h3 id="heading-the-cluster-ca">The Cluster CA</h3>
<p>Every Kubernetes cluster has a root Certificate Authority — a private key and a self-signed certificate that the API server trusts. Any client certificate signed by this CA is trusted by the cluster.</p>
<p>The CA certificate and key are typically stored in <code>/etc/kubernetes/pki/</code> on the control plane node, or in the <code>kube-system</code> namespace as a secret, depending on how the cluster was created.</p>
<p>On kind clusters, you can copy the CA cert and key directly from the control plane container:</p>
<pre><code class="language-bash">docker cp k8s-security-control-plane:/etc/kubernetes/pki/ca.crt ./ca.crt
docker cp k8s-security-control-plane:/etc/kubernetes/pki/ca.key ./ca.key
</code></pre>
<p>Whoever holds the CA key can issue certificates for any username and any group, including <code>system:masters</code>. This makes the CA key the most sensitive secret in a Kubernetes cluster. Guard it accordingly.</p>
<h3 id="heading-the-limits-of-certificate-based-auth">The Limits of Certificate-Based Auth</h3>
<p>Client certificates work, but they have two fundamental problems that make them a poor choice for human users in production.</p>
<p>The first is that <strong>Kubernetes doesn't check certificate revocation lists (CRLs)</strong>. If a developer's kubeconfig is stolen, the embedded certificate remains valid until it expires — which is typically one year in most Kubernetes setups. There's no way to immediately invalidate it. You can't "log out" a certificate. The only mitigation is to rotate the entire cluster CA, which invalidates every certificate including those belonging to other legitimate users.</p>
<p>The second is <strong>operational overhead</strong>. Certificates must be generated, distributed to users, and rotated before expiry. There's no self-service. In a team of ten engineers, managing certificates is annoying. In a team of a hundred, it's a full-time job.</p>
<p>For human access in production, OIDC is the right answer: short-lived tokens issued by a trusted identity provider, with a central revocation mechanism, and a standard browser-based login flow. Certificates are fine for service accounts and automation, where token management can be automated and rotation is handled programmatically.</p>
<p>That said, understanding certificates isn't optional. Your kubeconfig uses one. Your CI system probably does too. And cert-based auth is what you fall back to when everything else breaks.</p>
<h2 id="heading-demo-1-create-and-use-an-x509-client-certificate">Demo 1 — Create and Use an x509 Client Certificate</h2>
<p>In this section, you'll generate a user certificate signed by the cluster CA, bind it to an RBAC role, and use it to authenticate to the cluster as a different user.</p>
<p><strong>This guide is for local development and learning only.</strong> Manually signing certificates with the cluster CA and storing keys on disk is done here for simplicity.</p>
<p>In production, you should use the Kubernetes CertificateSigningRequest API or cert-manager for certificate issuance, enforce short-lived certificates with automatic rotation, and store private keys in a secrets manager (HashiCorp Vault, AWS Secrets Manager) or hardware security module (HSM) — never distribute the cluster CA key.</p>
<h3 id="heading-step-1-copy-the-ca-cert-and-key-from-the-kind-control-plane">Step 1: Copy the CA cert and key from the kind control plane</h3>
<pre><code class="language-bash">docker cp k8s-security-control-plane:/etc/kubernetes/pki/ca.crt ./ca.crt
docker cp k8s-security-control-plane:/etc/kubernetes/pki/ca.key ./ca.key
</code></pre>
<p>This will create two files in your current directory called <code>ca.crt</code> and <code>ca.key</code></p>
<h3 id="heading-step-2-generate-a-private-key-and-csr-for-a-new-user">Step 2: Generate a private key and CSR for a new user</h3>
<p>You're creating a certificate for a user named <code>jane</code> in the <code>engineering</code> group:</p>
<pre><code class="language-bash"># Generate the private key
openssl genrsa -out jane.key 2048

# Generate a Certificate Signing Request
# CN = username, O = group
openssl req -new \
  -key jane.key \
  -out jane.csr \
  -subj "/CN=jane/O=engineering"
</code></pre>
<h3 id="heading-step-3-sign-the-csr-with-the-cluster-ca">Step 3: Sign the CSR with the cluster CA</h3>
<pre><code class="language-bash">openssl x509 -req \
  -in jane.csr \
  -CA ca.crt \
  -CAkey ca.key \
  -CAcreateserial \
  -out jane.crt \
  -days 365
</code></pre>
<p>Expected output:</p>
<pre><code class="language-plaintext">Certificate request self-signature ok
subject=CN=jane, O=engineering
</code></pre>
<h3 id="heading-step-4-inspect-the-certificate">Step 4: Inspect the certificate</h3>
<p>Before using it, confirm the identity it carries:</p>
<pre><code class="language-bash">openssl x509 -in jane.crt -noout -subject -dates
</code></pre>
<pre><code class="language-plaintext">subject=CN=jane, O=engineering
notBefore=Mar 20 10:00:00 2024 GMT
notAfter=Mar 20 10:00:00 2025 GMT
</code></pre>
<p>One year from now, this certificate becomes invalid and must be replaced. There's no way to extend it — you have to issue a new one.</p>
<h3 id="heading-step-5-build-a-kubeconfig-entry-for-jane">Step 5: Build a kubeconfig entry for jane</h3>
<pre><code class="language-bash"># Get the cluster API server address from the current context
APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')

# Create a kubeconfig for jane
kubectl config set-cluster k8s-security \
  --server=$APISERVER \
  --certificate-authority=ca.crt \
  --embed-certs=true \
  --kubeconfig=jane.kubeconfig

kubectl config set-credentials jane \
  --client-certificate=jane.crt \
  --client-key=jane.key \
  --embed-certs=true \
  --kubeconfig=jane.kubeconfig

kubectl config set-context jane@k8s-security \
  --cluster=k8s-security \
  --user=jane \
  --kubeconfig=jane.kubeconfig

kubectl config use-context jane@k8s-security \
  --kubeconfig=jane.kubeconfig
</code></pre>
<h3 id="heading-step-6-test-authentication-before-rbac">Step 6: Test authentication — before RBAC</h3>
<p>Try to list pods using jane's kubeconfig:</p>
<pre><code class="language-bash">kubectl get pods -n staging --kubeconfig=jane.kubeconfig
</code></pre>
<pre><code class="language-plaintext">Error from server (Forbidden): pods is forbidden: User "jane" cannot list
resource "pods" in API group "" in the namespace "staging"
</code></pre>
<p>This is correct. Jane authenticated successfully — Kubernetes knows who she is. But she has no RBAC bindings, so every API call is denied. Authentication passed, but authorisation failed.</p>
<h3 id="heading-step-7-grant-jane-access-with-rbac">Step 7: Grant jane access with RBAC</h3>
<p>RBAC bindings use the username exactly as it appears in the certificate's CN field. If you need a refresher on how Roles, ClusterRoles, and RoleBindings work, this handbook <a href="https://www.freecodecamp.org/news/how-to-secure-a-kubernetes-cluster-handbook/">How to Secure a Kubernetes Cluster: RBAC, Pod Hardening, and Runtime Protection</a> covers the full RBAC model. For now, a simple RoleBinding using the built-in <code>view</code> ClusterRole is enough:</p>
<pre><code class="language-yaml"># jane-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: jane-reader
  namespace: staging
subjects:
  - kind: User
    name: jane          # matches the CN in the certificate
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: view
  apiGroup: rbac.authorization.k8s.io
</code></pre>
<pre><code class="language-bash">kubectl apply -f jane-rolebinding.yaml
kubectl get pods -n staging --kubeconfig=jane.kubeconfig
</code></pre>
<pre><code class="language-plaintext">No resources found in staging namespace.
</code></pre>
<p>No error — jane can now list pods in <code>staging</code>. She can't delete them, create them, or access other namespaces. The certificate got her in. RBAC determines what she can do.</p>
<h2 id="heading-how-to-set-up-oidc-authentication">How to Set Up OIDC Authentication</h2>
<p>OpenID Connect is an identity layer on top of OAuth 2.0. It's how Kubernetes integrates with enterprise identity providers — Active Directory, Okta, Google Workspace, Keycloak, and any other provider that speaks OIDC. Understanding how Kubernetes uses it requires following the token from the user's browser to the API server's decision.</p>
<h3 id="heading-how-the-oidc-flow-works-in-kubernetes">How the OIDC Flow Works in Kubernetes</h3>
<p>When a developer runs <code>kubectl get pods</code> with OIDC configured, the following happens:</p>
<ol>
<li><p><code>kubectl</code> checks whether the current credential in the kubeconfig is a valid, unexpired OIDC token</p>
</li>
<li><p>If not, it launches <code>kubelogin</code>, a kubectl plugin that opens a browser window</p>
</li>
<li><p>The browser redirects to the OIDC provider (Dex, Okta, your corporate IdP)</p>
</li>
<li><p>The user logs in with their corporate credentials</p>
</li>
<li><p>The OIDC provider issues a signed JWT and returns it to kubelogin</p>
</li>
<li><p>kubelogin caches the token locally (under <code>~/.kube/cache/oidc-login/</code>) and returns it to <code>kubectl</code></p>
</li>
<li><p><code>kubectl</code> sends the token to the API server as a <code>Bearer</code> header</p>
</li>
<li><p>The API server fetches the provider's public keys from its JWKS endpoint and verifies the token signature</p>
</li>
<li><p>If valid, the API server extracts the username and group claims from the token</p>
</li>
<li><p>RBAC takes over from there</p>
</li>
</ol>
<p>The Kubernetes API server never contacts the OIDC provider for each request. It only fetches the provider's public keys periodically to verify signatures locally. This makes OIDC authentication stateless and scalable.</p>
<h3 id="heading-the-api-server-configuration">The API Server Configuration</h3>
<p>For OIDC to work, the API server needs to know where to find the identity provider and how to interpret the tokens it issues.</p>
<p>In Kubernetes v1.30+, this is configured through an <code>AuthenticationConfiguration</code> file passed via the <code>--authentication-config</code> flag. (In older versions, individual <code>--oidc-*</code> flags were used instead, but these were removed in v1.35.)</p>
<p>The <code>AuthenticationConfiguration</code> defines OIDC providers under the <code>jwt</code> key:</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>What it does</th>
<th>Example</th>
</tr>
</thead>
<tbody><tr>
<td><code>issuer.url</code></td>
<td>The OIDC provider's base URL — must match the <code>iss</code> claim in the token</td>
<td><code>https://dex.example.com</code></td>
</tr>
<tr>
<td><code>issuer.audiences</code></td>
<td>The client IDs the token was issued for — must match the <code>aud</code> claim</td>
<td><code>["kubernetes"]</code></td>
</tr>
<tr>
<td><code>issuer.certificateAuthority</code></td>
<td>CA certificate to trust when contacting the OIDC provider (inlined PEM)</td>
<td><code>-----BEGIN CERTIFICATE-----...</code></td>
</tr>
<tr>
<td><code>claimMappings.username.claim</code></td>
<td>Which JWT claim to use as the Kubernetes username</td>
<td><code>email</code></td>
</tr>
<tr>
<td><code>claimMappings.groups.claim</code></td>
<td>Which JWT claim to use as the Kubernetes group list</td>
<td><code>groups</code></td>
</tr>
<tr>
<td><code>claimMappings.*.prefix</code></td>
<td>Prefix added to the claim value — set to <code>""</code> for no prefix</td>
<td><code>""</code></td>
</tr>
</tbody></table>
<p>On a kind cluster, the <code>--authentication-config</code> flag is set in the cluster configuration before creation, not after. You'll see this in the next demo.</p>
<h3 id="heading-jwt-claims-kubernetes-uses">JWT Claims Kubernetes Uses</h3>
<p>A JWT is a signed JSON object with three sections: a header, a payload, and a signature. The payload is a set of claims – key-value pairs that assert facts about the token. Kubernetes reads specific claims from the payload to build an identity.</p>
<p>The required claims are <code>iss</code> (the issuer URL, must match <code>issuer.url</code> in the <code>AuthenticationConfiguration</code>), <code>sub</code> (the subject, a unique identifier for the user), and <code>aud</code> (the audience, must match the <code>issuer.audiences</code> list). The <code>exp</code> claim (expiry time) is also required as the API server rejects expired tokens.</p>
<p>The most useful optional claim is <code>groups</code> (or whatever you configure via <code>claimMappings.groups.claim</code>). When this claim is present, Kubernetes can map OIDC group memberships directly to RBAC group bindings. A user in the <code>platform-engineers</code> group in your identity provider automatically gets the RBAC permissions you've bound to that group in Kubernetes — no manual user management required.</p>
<h3 id="heading-how-kubelogin-works">How kubelogin Works</h3>
<p>kubelogin (also distributed as <code>kubectl oidc-login</code>) is a kubectl credential plugin. Instead of embedding a static certificate or token in your kubeconfig, you configure a credential plugin that runs a helper binary when <code>kubectl</code> needs a token.</p>
<p>When kubelogin is invoked, it checks its local token cache. If the cached token is still valid, it returns it immediately. If the token has expired, it initiates the OIDC authorization code flow — opens a browser, redirects to the identity provider, receives the token after login, caches it locally, and returns it to <code>kubectl</code>. The whole flow takes about five seconds when it triggers.</p>
<p>This means tokens are short-lived (typically an hour) and rotate automatically. If a developer's machine is compromised, the token expires on its own. There is no long-lived credential sitting in a file somewhere.</p>
<h2 id="heading-demo-2-configure-oidc-login-with-dex-and-kubelogin">Demo 2 — Configure OIDC Login with Dex and kubelogin</h2>
<p>In this section, you'll deploy Dex as a self-hosted OIDC provider, configure a kind cluster to trust it, and log in with a browser. Dex is a good demo vehicle because it runs inside the cluster and doesn't require a cloud account or an external service.</p>
<p><strong>This guide is for local development and learning only.</strong> Self-signed certificates, static passwords, and certs stored on disk are used here for simplicity.</p>
<p>In production, use a managed identity provider (Azure Entra ID, Google Workspace, Okta), automate certificate lifecycle with cert-manager, and store secrets in a secrets manager (HashiCorp Vault, AWS Secrets Manager) or inject them via CSI driver — never commit or store certs as local files.</p>
<h3 id="heading-step-1-create-a-kind-cluster-with-oidc-authentication">Step 1: Create a kind cluster with OIDC authentication</h3>
<p>OIDC authentication for the API server must be configured at cluster creation time on Kind because the API server needs to know which identity provider to trust before it starts accepting requests.</p>
<p><strong>Note:</strong> Kubernetes v1.30+ deprecated the <code>--oidc-*</code> API server flags in favor of the structured <code>AuthenticationConfiguration</code> API (via <code>--authentication-config</code>). In v1.35+ the old flags are removed entirely. This guide uses the new approach.</p>
<p><strong>nip.io</strong> is a wildcard DNS service — <code>dex.127.0.0.1.nip.io</code> resolves to <code>127.0.0.1</code>. This lets us use a real hostname for TLS without editing <code>/etc/hosts</code>.</p>
<p>First, generate a self-signed CA and TLS certificate for Dex:</p>
<pre><code class="language-bash"># Generate a CA for Dex
openssl req -x509 -newkey rsa:4096 -keyout dex-ca.key \
  -out dex-ca.crt -days 365 -nodes \
  -subj "/CN=dex-ca"

# Generate a certificate for Dex signed by that CA
openssl req -newkey rsa:2048 -keyout dex.key \
  -out dex.csr -nodes \
  -subj "/CN=dex.127.0.0.1.nip.io"

openssl x509 -req -in dex.csr \
  -CA dex-ca.crt -CAkey dex-ca.key \
  -CAcreateserial -out dex.crt -days 365 \
  -extfile &lt;(printf "subjectAltName=DNS:dex.127.0.0.1.nip.io")
</code></pre>
<p>Next, generate the <code>AuthenticationConfiguration</code> file. This tells the API server how to validate JWTs — which issuer to trust (<code>url</code>), which audience to expect (<code>audiences</code>), and which JWT claims map to Kubernetes usernames and groups (<code>claimMappings</code>). The CA cert is inlined so the API server can verify Dex's TLS certificate when fetching signing keys:</p>
<pre><code class="language-bash">cat &gt; auth-config.yaml &lt;&lt;EOF
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthenticationConfiguration
jwt:
  - issuer:
      url: https://dex.127.0.0.1.nip.io:32000
      audiences:
        - kubernetes
      certificateAuthority: |
$(sed 's/^/        /' dex-ca.crt)
    claimMappings:
      username:
        claim: email
        prefix: ""
      groups:
        claim: groups
        prefix: ""
EOF
</code></pre>
<p>The <code>kind-oidc.yaml</code> config uses <code>extraPortMappings</code> to expose Dex's port to your browser, <code>extraMounts</code> to copy files into the Kind node, and a <code>kubeadmConfigPatch</code> to pass <code>--authentication-config</code> to the API server:</p>
<pre><code class="language-yaml"># kind-oidc.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    extraPortMappings:
      # Forward port 32000 from the Docker container to localhost,
      # so your browser can reach Dex's login page
      - containerPort: 32000
        hostPort: 32000
        protocol: TCP
    extraMounts:
      # Copy files from your machine into the Kind node's filesystem
      - hostPath: ./dex-ca.crt
        containerPath: /etc/ca-certificates/dex-ca.crt
        readOnly: true
      - hostPath: ./auth-config.yaml
        containerPath: /etc/kubernetes/auth-config.yaml
        readOnly: true
    kubeadmConfigPatches:
      # Patch the API server to enable OIDC authentication
      - |
        kind: ClusterConfiguration
        apiServer:
          extraArgs:
            # Tell the API server to load our AuthenticationConfiguration
            authentication-config: /etc/kubernetes/auth-config.yaml
          extraVolumes:
            # Mount files into the API server pod (it runs as a static pod,
            # so it needs explicit volume mounts even though files are on the node)
            - name: dex-ca
              hostPath: /etc/ca-certificates/dex-ca.crt
              mountPath: /etc/ca-certificates/dex-ca.crt
              readOnly: true
              pathType: File
            - name: auth-config
              hostPath: /etc/kubernetes/auth-config.yaml
              mountPath: /etc/kubernetes/auth-config.yaml
              readOnly: true
              pathType: File
</code></pre>
<p>Create the cluster:</p>
<pre><code class="language-bash">kind create cluster --name k8s-auth --config kind-oidc.yaml
</code></pre>
<h3 id="heading-step-2-deploy-dex">Step 2: Deploy Dex</h3>
<p>Dex is an OIDC-compliant identity provider that acts as a bridge between Kubernetes and upstream identity sources (LDAP, SAML, GitHub, and so on). In this demo it runs inside the cluster with a static password database — two hardcoded users you can log in as.</p>
<p>The API server doesn't talk to Dex directly on every request. It only needs Dex's CA certificate (which you inlined in the <code>AuthenticationConfiguration</code>) to verify the JWT signatures on tokens that Dex issues.</p>
<p>The deployment has four parts: a ConfigMap with Dex's configuration, a Deployment to run Dex, a NodePort Service to expose it on port 32000 (matching the issuer URL), and RBAC resources so Dex can store state using Kubernetes CRDs.</p>
<p>First, create the namespace and load the TLS certificate as a Kubernetes Secret. Dex needs this to serve HTTPS. Without it, your browser and the API server would refuse to connect:</p>
<pre><code class="language-bash">kubectl create namespace dex

kubectl create secret tls dex-tls \
  --cert=dex.crt \
  --key=dex.key \
  -n dex
</code></pre>
<p>Save the following as <code>dex-config.yaml</code>. This configures Dex with a static password connector — two hardcoded users for the demo:</p>
<pre><code class="language-yaml"># dex-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: dex-config
  namespace: dex
data:
  config.yaml: |
    # issuer must exactly match the URL in your AuthenticationConfiguration
    issuer: https://dex.127.0.0.1.nip.io:32000

    # Dex stores refresh tokens and auth codes — here it uses Kubernetes CRDs
    storage:
      type: kubernetes
      config:
        inCluster: true

    # Dex's HTTPS listener — serves the login page and token endpoints
    web:
      https: 0.0.0.0:5556
      tlsCert: /etc/dex/tls/tls.crt
      tlsKey: /etc/dex/tls/tls.key

    # staticClients defines which applications can request tokens.
    # "kubernetes" is the client ID that kubelogin uses when authenticating
    staticClients:
      - id: kubernetes
        redirectURIs:
          - http://localhost:8000     # kubelogin listens here to receive the callback
        name: Kubernetes
        secret: kubernetes-secret     # shared secret between kubelogin and Dex

    # Two demo users with the password "password" (bcrypt-hashed).
    # In production, you'd connect Dex to LDAP, SAML, or a social login instead
    enablePasswordDB: true
    staticPasswords:
      - email: "jane@example.com"
        # bcrypt hash of "password" — generate your own with: htpasswd -bnBC 10 "" password
        hash: "\(2a\)10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
        username: "jane"
        userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
      - email: "admin@example.com"
        hash: "\(2a\)10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
        username: "admin"
        userID: "a8b53e13-7e8c-4f7b-9a33-6c2f4d8c6a1b"
        groups:
          - platform-engineers
</code></pre>
<p>Save the following as <code>dex-deployment.yaml</code>. This creates the Deployment, Service, ServiceAccount, and RBAC that Dex needs to run:</p>
<pre><code class="language-yaml"># dex-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dex
  namespace: dex
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dex
  template:
    metadata:
      labels:
        app: dex
    spec:
      serviceAccountName: dex
      containers:
        - name: dex
          # v2.45.0+ required — earlier versions don't include groups from staticPasswords in tokens
          image: ghcr.io/dexidp/dex:v2.45.0
          command: ["dex", "serve", "/etc/dex/cfg/config.yaml"]
          ports:
            - name: https
              containerPort: 5556
          volumeMounts:
            - name: config
              mountPath: /etc/dex/cfg
            - name: tls
              mountPath: /etc/dex/tls
      volumes:
        - name: config
          configMap:
            name: dex-config
        - name: tls
          secret:
            secretName: dex-tls
---
# NodePort Service — exposes Dex on port 32000 on the Kind node.
# Combined with extraPortMappings, this makes Dex reachable from your browser
apiVersion: v1
kind: Service
metadata:
  name: dex
  namespace: dex
spec:
  type: NodePort
  ports:
    - name: https
      port: 5556
      targetPort: 5556
      nodePort: 32000
  selector:
    app: dex
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: dex
  namespace: dex
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: dex
rules:
  - apiGroups: ["dex.coreos.com"]
    resources: ["*"]
    verbs: ["*"]
  - apiGroups: ["apiextensions.k8s.io"]
    resources: ["customresourcedefinitions"]
    verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: dex
subjects:
  - kind: ServiceAccount
    name: dex
    namespace: dex
roleRef:
  kind: ClusterRole
  name: dex
  apiGroup: rbac.authorization.k8s.io
</code></pre>
<pre><code class="language-bash">kubectl apply -f dex-config.yaml
kubectl apply -f dex-deployment.yaml
kubectl rollout status deployment/dex -n dex
</code></pre>
<h3 id="heading-step-3-install-kubelogin">Step 3: Install kubelogin</h3>
<pre><code class="language-bash"># macOS
brew install int128/kubelogin/kubelogin

# Linux
curl -LO https://github.com/int128/kubelogin/releases/latest/download/kubelogin_linux_amd64.zip
unzip -j kubelogin_linux_amd64.zip kubelogin -d /tmp
sudo mv /tmp/kubelogin /usr/local/bin/kubectl-oidc_login
rm kubelogin_linux_amd64.zip
</code></pre>
<p>Confirm it's installed:</p>
<pre><code class="language-bash">kubectl oidc-login --version
</code></pre>
<h3 id="heading-step-4-configure-a-kubeconfig-entry-for-oidc">Step 4: Configure a kubeconfig entry for OIDC</h3>
<p>This creates a new user and context in your kubeconfig. Instead of using a client certificate (like the default Kind admin), it tells kubectl to use kubelogin to get a token from Dex.</p>
<p>The <code>--oidc-extra-scope</code> flags are important: without <code>email</code> and <code>groups</code>, Dex won't include those claims in the JWT, and the API server won't know who you are or what groups you belong to.</p>
<pre><code class="language-bash">kubectl config set-credentials oidc-user \
  --exec-api-version=client.authentication.k8s.io/v1beta1 \
  --exec-command=kubectl \
  --exec-arg=oidc-login \
  --exec-arg=get-token \
  --exec-arg=--oidc-issuer-url=https://dex.127.0.0.1.nip.io:32000 \
  --exec-arg=--oidc-client-id=kubernetes \
  --exec-arg=--oidc-client-secret=kubernetes-secret \
  --exec-arg=--oidc-extra-scope=email \
  --exec-arg=--oidc-extra-scope=groups \
  --exec-arg=--certificate-authority=$(pwd)/dex-ca.crt

kubectl config set-context oidc@k8s-auth \
  --cluster=kind-k8s-auth \
  --user=oidc-user

kubectl config use-context oidc@k8s-auth
</code></pre>
<h3 id="heading-step-5-trigger-the-login-flow">Step 5: Trigger the login flow</h3>
<p>Jane has no RBAC permissions yet, so first grant her read access from the admin context:</p>
<pre><code class="language-bash">kubectl --context kind-k8s-auth create clusterrolebinding jane-view \
  --clusterrole=view --user=jane@example.com
</code></pre>
<p>Now switch to the OIDC context and trigger a login:</p>
<pre><code class="language-bash">kubectl get pods -n default
</code></pre>
<p>Your browser opens and redirects to the Dex login page. Log in as <code>jane@example.com</code> with password <code>password</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5f2a6b76d7d55f162b5da2ee/44fe0657-b383-4245-9e43-45daea7a3f4f.png" alt="dexidp login screen" style="display:block;margin:0 auto" width="866" height="549" loading="lazy">

<img src="https://cdn.hashnode.com/uploads/covers/5f2a6b76d7d55f162b5da2ee/4f77442a-3055-47fc-a141-8d881731a1f4.png" alt="dexidp grant access" style="display:block;margin:0 auto" width="925" height="512" loading="lazy">

<p>After login, the terminal completes:</p>
<pre><code class="language-plaintext">No resources found in default namespace.
</code></pre>
<p>The browser-based authentication worked. <code>kubectl</code> received the token from Dex, sent it to the API server, the API server validated the JWT signature using the CA certificate from the <code>AuthenticationConfiguration</code>, extracted <code>jane@example.com</code> from the <code>email</code> claim, matched it against the RBAC binding, and authorized the request.</p>
<p>Without the <code>clusterrolebinding</code>, you would see <code>Error from server (Forbidden)</code> — authentication succeeds (the API server knows <em>who</em> you are) but authorization fails (jane has no permissions). This is the distinction between 401 Unauthorized and 403 Forbidden.</p>
<h3 id="heading-step-6-inspect-the-jwt">Step 6: Inspect the JWT</h3>
<p>A JWT (JSON Web Token) is a signed JSON payload that contains claims about the user. kubelogin caches the token locally under <code>~/.kube/cache/oidc-login/</code> so you don't have to log in on every kubectl command.</p>
<p>List the directory to find the cached file:</p>
<pre><code class="language-bash">ls ~/.kube/cache/oidc-login/
</code></pre>
<p>Decode the JWT payload directly from the cache:</p>
<pre><code class="language-bash">cat ~/.kube/cache/oidc-login/$(ls ~/.kube/cache/oidc-login/ | grep -v lock | head -1) | \
  python3 -c "
import json, sys, base64
token = json.load(sys.stdin)['id_token'].split('.')[1]
token += '=' * (4 - len(token) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(token)), indent=2))
"
</code></pre>
<p>You'll see something like:</p>
<pre><code class="language-json">{
  "iss": "https://dex.127.0.0.1.nip.io:32000",
  "sub": "CiQwOGE4Njg0Yi1kYjg4LTRiNzMtOTBhOS0zY2QxNjYxZjU0NjYSBWxvY2Fs",
  "aud": "kubernetes",
  "exp": 1775307910,
  "iat": 1775221510,
  "email": "jane@example.com",
  "email_verified": true
}
</code></pre>
<p>The <code>email</code> claim becomes jane's Kubernetes username because the <code>AuthenticationConfiguration</code> maps <code>username.claim: email</code>. The <code>aud</code> matches the configured <code>audiences</code>. The <code>iss</code> matches the issuer <code>url</code>. This is how the API server validates the token without contacting Dex on every request — it only needs the CA certificate to verify the JWT signature.</p>
<h3 id="heading-step-7-map-oidc-groups-to-rbac">Step 7: Map OIDC groups to RBAC</h3>
<p>The <code>admin@example.com</code> user has a <code>groups</code> claim in the Dex config containing <code>platform-engineers</code>. Instead of creating individual RBAC bindings per user, you can bind permissions to a group — anyone whose JWT contains that group gets the permissions automatically:</p>
<pre><code class="language-yaml"># platform-engineers-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: platform-engineers-admin
subjects:
  - kind: Group
    name: platform-engineers     # matches the groups claim in the JWT
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
</code></pre>
<p>You're currently logged in as <code>jane@example.com</code> via the OIDC context, but jane only has <code>view</code> permissions — she can't create cluster-wide RBAC bindings. Switch back to the admin context to apply this:</p>
<pre><code class="language-bash">kubectl config use-context kind-k8s-auth
kubectl apply -f platform-engineers-binding.yaml
kubectl config use-context oidc@k8s-auth
</code></pre>
<p>Now clear the cached token to log out of jane's session, then trigger a new login as <code>admin@example.com</code>:</p>
<pre><code class="language-bash"># Clear the cached token — this is how you "log out" with kubelogin
rm -rf ~/.kube/cache/oidc-login/

# This will open the browser again for a fresh login
kubectl get pods -n default
</code></pre>
<p>Log in as <code>admin@example.com</code> with password <code>password</code>. This time the JWT will contain <code>"groups": ["platform-engineers"]</code>, which matches the <code>ClusterRoleBinding</code> you just created. The admin user gets full cluster access — without ever being added to a kubeconfig by name.</p>
<p>You can verify by decoding the new token (Step 6) — the <code>groups</code> claim will be present:</p>
<pre><code class="language-json">{
  "email": "admin@example.com",
  "groups": ["platform-engineers"]
}
</code></pre>
<p>This is the real power of OIDC group claims: you manage group membership in your identity provider, and Kubernetes permissions follow automatically. Add someone to the <code>platform-engineers</code> group in Dex (or any upstream IdP), and they get cluster-admin access on their next login — no kubeconfig or RBAC changes needed.</p>
<h2 id="heading-cloud-provider-authentication">Cloud Provider Authentication</h2>
<p>AWS, GCP, and Azure each give Kubernetes clusters a native authentication mechanism that ties into their IAM systems.</p>
<p>The implementations differ in API surface, but they all use the same underlying mechanism: OIDC token projection. Once you understand how Dex works above, these are all variations on the same theme.</p>
<h3 id="heading-aws-eks">AWS EKS</h3>
<p>EKS uses the <code>aws-iam-authenticator</code> to translate AWS IAM identities into Kubernetes identities. When you run <code>kubectl</code> against an EKS cluster, the AWS CLI generates a short-lived token signed with your IAM credentials. The API server passes this token to the aws-iam-authenticator webhook, which verifies it against AWS STS and returns the corresponding username and groups.</p>
<p>User access is controlled via the <code>aws-auth</code> ConfigMap in <code>kube-system</code>, which maps IAM role ARNs and IAM user ARNs to Kubernetes usernames and groups. A typical entry looks like this:</p>
<pre><code class="language-yaml"># In kube-system/aws-auth ConfigMap
mapRoles:
  - rolearn: arn:aws:iam::123456789:role/platform-engineers
    username: platform-engineer:{{SessionName}}
    groups:
      - platform-engineers
</code></pre>
<p>AWS is migrating from the <code>aws-auth</code> ConfigMap to a newer Access Entries API, which manages the same mapping through the EKS API rather than a ConfigMap. The underlying authentication mechanism is the same.</p>
<h3 id="heading-google-gke">Google GKE</h3>
<p>GKE integrates with Google Cloud IAM using two different mechanisms, depending on whether you're authenticating as a human user or as a workload.</p>
<p>For human users, GKE accepts standard Google OAuth2 tokens. Running <code>gcloud container clusters get-credentials</code> writes a kubeconfig that uses the <code>gcloud</code> CLI as a credential plugin, generating short-lived tokens from your Google account automatically.</p>
<p>For pod-level identity — letting a pod assume a Google Cloud IAM role — GKE uses Workload Identity. You annotate a Kubernetes service account to bind it to a Google Service Account, and pods running as that service account can call Google Cloud APIs using the GSA's permissions:</p>
<pre><code class="language-bash"># Bind a Kubernetes SA to a Google Service Account
kubectl annotate serviceaccount my-app \
  --namespace production \
  iam.gke.io/gcp-service-account=my-app@my-project.iam.gserviceaccount.com
</code></pre>
<h3 id="heading-azure-aks">Azure AKS</h3>
<p>AKS integrates with Azure Active Directory. When Azure AD integration is enabled, <code>kubectl</code> requests an Azure AD token on behalf of the user via the Azure CLI, and the AKS API server validates it against Azure AD.</p>
<p>For pod-level identity, AKS uses Azure Workload Identity, which follows the same OIDC federation pattern as GKE Workload Identity. A Kubernetes service account is annotated with an Azure Managed Identity client ID, and pods can request Azure AD tokens without storing any credentials:</p>
<pre><code class="language-bash"># Annotate a service account with the Azure Managed Identity client ID
kubectl annotate serviceaccount my-app \
  --namespace production \
  azure.workload.identity/client-id=&lt;MANAGED_IDENTITY_CLIENT_ID&gt;
</code></pre>
<p>The underlying pattern across all three providers is the same: a trusted OIDC token is issued by the cloud provider, verified by the Kubernetes API server, and mapped to an identity through a binding (the <code>aws-auth</code> ConfigMap, a GKE Workload Identity binding, or an AKS federated identity credential). The OIDC section in this article is the conceptual foundation for all of them.</p>
<h2 id="heading-webhook-token-authentication">Webhook Token Authentication</h2>
<p>Webhook token authentication is worth knowing about because it appears in several common Kubernetes setups, even if you never configure it yourself.</p>
<p>When a request arrives with a bearer token that no other authenticator recognises, Kubernetes can send that token to an external HTTP endpoint for validation. The endpoint returns a response indicating who the token belongs to.</p>
<p>This is how EKS authentication worked before the aws-iam-authenticator was built into the API server. It's also how bootstrap tokens work during node join operations: a token is generated, embedded in the <code>kubeadm join</code> command, and validated by the bootstrap webhook when the new node contacts the API server for the first time.</p>
<p>For most clusters, you'll encounter webhook auth as something already running rather than something you configure. The main thing to know is that it exists and what it looks like when it appears in logs or configuration.</p>
<h2 id="heading-cleanup">Cleanup</h2>
<p>To remove everything created in this article:</p>
<pre><code class="language-bash"># Delete the OIDC demo cluster
kind delete cluster --name k8s-auth

# Remove generated certificate files
rm -f ca.crt ca.key jane.key jane.csr jane.crt jane.kubeconfig
rm -f dex-ca.crt dex-ca.key dex.crt dex.key dex.csr dex-ca.srl auth-config.yaml

# Remove the kubelogin token cache
rm -rf ~/.kube/cache/oidc-login/
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Kubernetes authentication is not a single mechanism — it's a chain of pluggable strategies, each one suited to different use cases. In this article you worked through the most important ones.</p>
<p>x509 client certificates are how Kubernetes works out of the box. The CN field becomes the username, the O field becomes the group, and the cluster CA is the trust anchor. You created a certificate for a new user, bound it to RBAC, and saw exactly how authentication and authorisation interact — authentication gets you in, RBAC determines what you can do.</p>
<p>You also saw the fundamental limitation: Kubernetes doesn't check certificate revocation lists, so a compromised certificate remains valid until it expires. This makes certificates a poor fit for human users in production environments.</p>
<p>OIDC is the production-grade answer. Tokens are short-lived, issued by a trusted identity provider, and map directly to Kubernetes groups through JWT claims. You deployed Dex as a self-hosted OIDC provider, configured the API server to trust it, and set up kubelogin for browser-based authentication.</p>
<p>You then decoded a JWT to see exactly what the API server reads from it, and mapped an OIDC group claim to a Kubernetes ClusterRoleBinding.</p>
<p>Cloud provider authentication — EKS, GKE, AKS — uses the same OIDC foundation with provider-specific wrappers. Understanding how Dex works makes each of those systems immediately readable.</p>
<p>All YAML, certificates, and configuration files from this article are in the <a href="https://github.com/Caesarsage/DevOps-Cloud-Projects/tree/main/intermediate/k8/security">companion GitHub repository</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Prevent IDOR Vulnerabilities in Next.js API Routes ]]>
                </title>
                <description>
                    <![CDATA[ Imagine this situation: A user logs in successfully to your application, but upon loading their dashboard, they see someone else’s data. Why does this happen? The authentication worked, the session is ]]>
                </description>
                <link>https://www.freecodecamp.org/news/prevent-idor-in-nextjs/</link>
                <guid isPermaLink="false">69a1f073d4053a09f3430559</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authorization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayodele Aransiola ]]>
                </dc:creator>
                <pubDate>Fri, 27 Feb 2026 19:28:51 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/b14a67ea-e78b-4ebd-996f-98da3a0a8027.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Imagine this situation: A user logs in successfully to your application, but upon loading their dashboard, they see someone else’s data.</p>
<p>Why does this happen? The authentication worked, the session is valid, the user is authenticated, but the authorization failed.</p>
<p>This specific issue is called <strong>IDOR (Insecure Direct Object Reference)</strong>. It’s one of the most common security bugs and is categorized under <strong>Broken Object Level Authorization (BOLA)</strong> in the OWASP API Security Top 10.</p>
<p>In this tutorial, you’ll learn:</p>
<ul>
<li><p>Why IDOR happens</p>
</li>
<li><p>Why authentication alone is not enough</p>
</li>
<li><p>How object-level authorization works</p>
</li>
<li><p>How to fix IDOR properly in Next.js API routes</p>
</li>
<li><p>How to design safer APIs from the start</p>
</li>
</ul>
<h2 id="heading-table-of-content">Table of Content</h2>
<ul>
<li><p><a href="#heading-table-of-content">Table of Content</a></p>
</li>
<li><p><a href="#heading-authentication-vs-authorization">Authentication vs. Authorization</a></p>
</li>
<li><p><a href="#heading-what-is-an-idor-vulnerability">What is an IDOR Vulnerability?</a></p>
</li>
<li><p><a href="#heading-the-vulnerable-pattern-in-nextjs">The Vulnerable Pattern in Next.js</a></p>
</li>
<li><p><a href="#heading-how-to-handle-idor-in-nextjs">How to Handle IDOR in Next.js</a></p>
<ul>
<li><a href="#heading-object-level-authorization">Object-Level Authorization</a></li>
</ul>
</li>
<li><p><a href="#heading-how-to-design-safer-endpoints-apime">How to Design Safer Endpoints (/api/me)</a></p>
</li>
<li><p><a href="#heading-mental-model-for-api-design">Mental Model for API Design</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-authentication-vs-authorization">Authentication vs. Authorization</h2>
<p>Before writing further, let’s clarify something critical.</p>
<ul>
<li><p><strong>Authentication answers:</strong> Who are you?</p>
</li>
<li><p><strong>Authorization answers:</strong> What are you allowed to access?</p>
</li>
</ul>
<p>In IDOR scenarios, authentication works (the user is logged in), while authorization is missing or incomplete. That distinction is the core lesson of this article.</p>
<h2 id="heading-what-is-an-idor-vulnerability">What is an IDOR Vulnerability?</h2>
<p>An IDOR vulnerability happens when your API fetches a resource by an identifier (like a user ID), and then you do not verify that the requester owns or is allowed to access that resource.</p>
<p>Example of such a request:</p>
<pre><code class="language-plaintext">GET /api/users/123
</code></pre>
<p>The code above is an HTTP <strong>GET</strong> request to the <code>/api/users/123</code> route. The <code>GET</code> method is used to request data from the server. This indicates that the client is requesting a specific user with the ID <code>123</code> and this request returns the user data in a response (often in JSON format).</p>
<p>If your backend makes the request using a similar structure to the code snippet below without checking who is making the request, you have an IDOR vulnerability, even if the user is logged in.</p>
<pre><code class="language-tsx">db.user.findUnique({ where: { id: "123" } })
</code></pre>
<p>What the code does is to query the database for a single user record. The <code>db.user</code> part refers to the <code>user</code> model/table and <code>findUnique()</code> is a method that returns only one record based on a unique field. Inside the method, the <code>where</code> clause specifies the filter condition and <code>{ id: "123" }</code> tells the database to find the user whose unique <code>id</code> equals <code>"123"</code>. If a matching record exists, it returns that user object; otherwise, it returns <code>null</code>.</p>
<h2 id="heading-the-vulnerable-pattern-in-nextjs">The Vulnerable Pattern in Next.js</h2>
<p>Looking at this Next.js App Router API route:</p>
<pre><code class="language-tsx">// app/api/users/[id]/route.ts
import { NextResponse } from "next/server";
import { db } from "@/lib/db";

export async function GET(
  req: Request,
  { params }: { params: { id: string } }
) {
  const user = await db.user.findUnique({
    where: { id: params.id },
    select: { id: true, email: true, name: true },
  });

  return NextResponse.json({ user });
}
</code></pre>
<p>Before going to the implication of this code snippet, let's understand what the code does. It defines a dynamic API route for <code>/api/users/[id]</code>. The exported <code>GET</code> function is an async route handler that runs when a GET request is made to this endpoint. It receives the request object and a <code>params</code> object, where <code>params.id</code> contains the dynamic <code>[id]</code> in the URL segment. The <code>db.user.findUnique()</code> method queries the database for a user whose <code>id</code> matches <code>params.id</code>, and the <code>select</code> option limits the returned fields to <code>id</code>, <code>email</code>, and <code>name</code>. Finally, <code>NextResponse.json()</code> sends the retrieved user data back to the client as a JSON response.</p>
<p>Now, to the implication, the code is a bad approach because the route accepts a user ID from the URL, fetches that user directly from the database, and returns the result. There is no session validation, no ownership check, and no role check.</p>
<p>If a logged-in user changes the <code>id</code> in the URL, they may access other users’ data. This is simply IDOR.</p>
<h2 id="heading-how-to-handle-idor-in-nextjs">How to Handle IDOR in Next.js</h2>
<p>The first element of defense is verifying identity. We’ll use <code>getServerSession</code> from NextAuth (adjust if using another auth provider). This change ensures that you read the session from the cookies, verify it on the server side, and ensure the user has a valid ID. This prevents unauthenticated access.</p>
<pre><code class="language-tsx">// lib/auth.ts
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/authOptions";

export async function requireSession() {
  const session = await getServerSession(authOptions);

  if (!session?.user?.id) {
    return null;
  }

  return session;
}
</code></pre>
<p>The code above defines an authentication helper function called <code>requireSession</code>. The <code>getServerSession(authOptions)</code> function retrieves the current user session on the server using the provided authentication configuration. The optional chaining (<code>session?.user?.id</code>) in the <code>if</code> block that follows safely checks whether a logged-in user and their <code>id</code> exist. If no valid session or user ID is found, the function returns <code>null</code>, indicating the request is unauthenticated. Otherwise, it returns the full <code>session</code> object so it can be used in protected routes or server logic.</p>
<p>You have successfully confirmed that the user and session exist; now, update the route:</p>
<pre><code class="language-tsx">export async function GET(
  req: Request,
  { params }: { params: { id: string } }
) {
  const session = await requireSession();

  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const user = await db.user.findUnique({
    where: { id: params.id },
    select: { id: true, email: true, name: true },
  });

  return NextResponse.json({ user });
}
</code></pre>
<p>The fix is incomplete yet, but in the above code, you’ve prevented anonymous access. The <code>GET</code> handler calls the <code>requireSession()</code> that was created earlier to verify that the request is authenticated. If no valid session is returned, it immediately responds with a JSON error message and a <code>401 Unauthorized</code> HTTP status. If the user is authenticated, it proceeds to call <code>db.user.findUnique()</code> to fetch the user whose <code>id</code> matches <code>params.id</code>, selecting only the <code>id</code>, <code>email</code>, and <code>name</code> fields. Finally, it returns the retrieved user data as a JSON response using <code>NextResponse.json()</code>.</p>
<p>Something is still missing. Can you guess? Any authenticated user can still request any resource by changing the URL path to the request they want. How? This leads us to object-level authorization.</p>
<h3 id="heading-object-level-authorization">Object-Level Authorization</h3>
<p>An object-level authorization ensures that a user can only access their own data (unless explicitly permitted).</p>
<p>The improvement to the code would be to add an ownership check. The adjustment ensures the API request checks if the requester is authenticated and owns the requested object. If either fails, access is denied.</p>
<pre><code class="language-tsx">export async function GET(
  req: Request,
  { params }: { params: { id: string } }
) {
  const session = await requireSession();

  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  if (session.user.id !== params.id) {
    return NextResponse.json({ error: "Forbidden" }, { status: 403 });
  }

  const user = await db.user.findUnique({
    where: { id: params.id },
    select: { id: true, email: true, name: true },
  });

  return NextResponse.json({ user });
}
</code></pre>
<p>Let's take a look at what happened in the code, the <code>GET</code> handler first authenticates the request using <code>requireSession()</code>, returning a <code>401</code> response if no valid session exists. It then performs an authorization check by comparing <code>session.user.id</code> with <code>params.id</code>. If they do not match, it returns a <code>403 Forbidden</code> response, preventing users from accessing other users’ data. If both checks pass, it queries the database using <code>db.user.findUnique()</code> to retrieve the specified user and limits the result to selected fields. Finally, it sends the user data back as a JSON response. With this, you’ve enforced an <strong>object-level authorization</strong>.</p>
<h2 id="heading-how-to-design-safer-endpoints-apime">How to Design Safer Endpoints (<code>/api/me</code>)</h2>
<p>The safest approach in designing your endpoint is to eliminate the risk entirely. Instead of allowing users to specify IDs (<code>/api/users/:id</code>), use <code>/api/me</code>, because the server already knows the user’s identity from the session.</p>
<pre><code class="language-tsx">// app/api/me/route.ts
export async function GET() {
  const session = await requireSession();

  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const user = await db.user.findUnique({
    where: { id: session.user.id },
    select: { id: true, email: true, name: true },
  });

  return NextResponse.json({ user });
}
</code></pre>
<p>This approach makes sure that your API only returns data for the currently authenticated user. It first calls <code>requireSession()</code> to ensure the request is authenticated, returning a <code>401</code> response if no session exists. Instead of using a URL parameter, it reads the user’s ID directly from <code>session.user.id</code>, ensuring the user can only access their own data. It then calls <code>db.user.findUnique()</code> to retrieve that user from the database, selecting only specific fields, and returns the result as a JSON response.</p>
<p>You can be confident with this approach because the client cannot manipulate user IDs. The server gets the user identity from a trusted source, and the attack surface is reduced. This is called <code>secure-by-design</code> <strong>API model</strong>.</p>
<p>Now, you should clearly understand that authentication does not imply authorization. Hence,</p>
<ul>
<li><p>IDOR occurs when object ownership is not verified</p>
</li>
<li><p>Every API route that accepts an ID must validate access</p>
</li>
<li><p>Safer API design reduces vulnerability surface</p>
</li>
<li><p>Authorization must always run on the server</p>
</li>
</ul>
<h2 id="heading-mental-model-for-api-design">Mental Model for API Design</h2>
<p>When writing any API route, answer these questions:</p>
<ol>
<li><p>Who is making this request?</p>
</li>
<li><p>What object are they requesting?</p>
</li>
<li><p>Does policy allow them to access it?</p>
</li>
</ol>
<p>If you cannot clearly answer all three, your route may be vulnerable.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>IDOR vulnerabilities happen when APIs trust user-supplied identifiers without verifying ownership or permission.</p>
<p>To prevent them in Next.js, authenticate every private route, enforce object-level authorization, centralize authorization logic, and write tests for forbidden access.</p>
<p>Security is not about adding logins, it’s about enforcing security policy on every object access.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Secure Authentication System with JWT and Refresh Tokens ]]>
                </title>
                <description>
                    <![CDATA[ Every app that handles user accounts needs a way to confirm who’s who. That’s what authentication is for, making sure the person using an app is the person they claim to be. But doing this securely is harder than it sounds. Traditional methods often ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-secure-authentication-system-with-jwt-and-refresh-tokens/</link>
                <guid isPermaLink="false">6925f655569c4dde127d2f88</guid>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JWT ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Joan Ayebola ]]>
                </dc:creator>
                <pubDate>Tue, 25 Nov 2025 18:32:53 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764095460886/51b9c653-fa95-42f0-8c51-37f6d6805da4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every app that handles user accounts needs a way to confirm who’s who. That’s what authentication is for, making sure the person using an app is the person they claim to be. But doing this securely is harder than it sounds.</p>
<p>Traditional methods often rely on server sessions and cookies. Those work, but they don’t always scale well, especially when you’re building APIs or mobile apps that talk to multiple services. This is why JWTs, or JSON Web Tokens, are useful. They’re small, self-contained tokens that can carry user data safely between a client and a server.</p>
<p>JWTs make it easy to verify users without constantly checking a database – but they also expire fast to reduce risk. To keep users logged in without forcing them to sign in again every few minutes, we use something called a refresh token. It’s a separate, long-lived token that can request new access tokens when the old ones expire.</p>
<p>In this guide, we’ll walk through how to build a secure authentication system using JWTs and refresh tokens. You’ll learn how to generate tokens, validate them, handle expiry, and keep everything safe from common security threats.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-understanding-jwts-json-web-tokens">Understanding JWTs (JSON Web Tokens)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-setting-up-the-project">Setting Up the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-jwt-authentication">How to Implement JWT Authentication</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-verify-jwts-and-protect-routes">How to Verify JWTs and Protect Routes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-refresh-tokens-and-rotation">Refresh Tokens and Rotation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-understanding-jwts-json-web-tokens">Understanding JWTs (JSON Web Tokens)</h2>
<p>A JWT, short for JSON Web Token, is a compact way to share information between a client and a server. It’s often used to prove that a user is who they say they are. The token is created on the server after a user logs in and is then sent back to the client. The client then includes this token with each request, so the server knows who is making the call.</p>
<p>A JWT has three parts: a header, a payload, and a signature.</p>
<ul>
<li><p>The <strong>header</strong> usually tells the system which algorithm was used to sign the token.</p>
</li>
<li><p>The <strong>payload</strong> contains the data, such as the user’s ID or role.</p>
</li>
<li><p>The <strong>signature</strong> is the part that keeps everything secure. It’s created by hashing the header and payload with a secret key.</p>
</li>
</ul>
<p>Once created, a JWT looks like a long string of random characters separated by dots. When the client sends it back to the server, the server verifies the signature using the same secret key. If it matches, the request is trusted.</p>
<p>One of the main benefits of JWTs is that they are stateless. The server doesn’t need to store session data. Everything needed to verify the user is already inside the token. This makes them fast and easy to use in modern APIs and microservices.</p>
<p>JWTs do have a downside: they cannot be revoked easily once issued. If a token is stolen, the attacker can use it until it expires. This is why short token lifetimes matter. It’s also why refresh tokens exist.</p>
<p>In the next section, we’ll finish the basic JWT setup. After that, we’ll add refresh tokens in <strong>“Refresh Tokens and Rotation.”</strong> That part shows how to handle expiry without making users log in again.</p>
<h2 id="heading-setting-up-the-project">Setting Up the Project</h2>
<p>Before writing any code, let’s set up a simple backend where we can build and test our authentication system. For this guide, we’ll use Node.js with Express, since it’s lightweight and easy to follow. You can use any stack later once you understand the flow.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>Make sure you have:</p>
<ul>
<li><p>Node.js and npm installed</p>
</li>
<li><p>A text editor (VS Code works great)</p>
</li>
<li><p>Basic knowledge of JavaScript and APIs</p>
</li>
</ul>
<h3 id="heading-1-initialize-the-project">1. Initialize the Project</h3>
<p>Create a new folder and open it in your terminal.</p>
<pre><code class="lang-bash">mkdir jwt-auth-demo
<span class="hljs-built_in">cd</span> jwt-auth-demo
npm init -y
</code></pre>
<p>This creates a <code>package.json</code> file that will track your dependencies.</p>
<h3 id="heading-2-install-dependencies">2. Install Dependencies</h3>
<p>You’ll need a few packages to get started:</p>
<ul>
<li><p><code>express</code>: the web framework</p>
</li>
<li><p><code>jsonwebtoken</code>: to create and verify tokens</p>
</li>
<li><p><code>bcryptjs</code>: to hash passwords</p>
</li>
<li><p><code>dotenv</code>: to manage environment variables</p>
</li>
</ul>
<p>Install them all at once like this:</p>
<pre><code class="lang-bash">npm install express jsonwebtoken bcryptjs dotenv
</code></pre>
<p>If you want auto-reloading while developing, install nodemon as a dev dependency:</p>
<pre><code class="lang-bash">npm install --save-dev nodemon
</code></pre>
<h3 id="heading-3-project-structure">3. Project Structure</h3>
<p>Here’s a clean structure to keep things organized:</p>
<pre><code class="lang-plaintext">jwt-auth-demo/
│
├── server.js
├── .env
├── package.json
│
├── config/
│   └── db.js
│
├── middleware/
│   └── auth.js
│
├── routes/
│   └── auth.js
│
└── models/
    └── user.js
</code></pre>
<h3 id="heading-4-basic-express-setup">4. Basic Express Setup</h3>
<p>In <code>server.js</code>, start with a minimal Express server.</p>
<pre><code class="lang-js"><span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config();
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> app = express();

app.use(express.json());

app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">'JWT Auth API running'</span>);
});

<span class="hljs-keyword">const</span> PORT = process.env.PORT || <span class="hljs-number">5000</span>;
app.listen(PORT, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server running on port <span class="hljs-subst">${PORT}</span>`</span>));
</code></pre>
<p>You can now run it using:</p>
<pre><code class="lang-bash">node server.js
</code></pre>
<p>or, if you’re using nodemon:</p>
<pre><code class="lang-bash">npx nodemon server.js
</code></pre>
<p>If everything is set up correctly, visiting <code>http://localhost:5000</code> should display <strong>“JWT Auth API running”:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760559643076/8fb7dcbf-50ca-44bc-b2a3-32273d82957f.png" alt="Screenshot of a terminal running nodemon server.js next to a browser window showing the text “JWT Auth API running” at http://localhost:5000, confirming the server started correctly." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-implement-jwt-authentication"><strong>How to Implement JWT Authentication</strong></h2>
<p>Now that your server is up, let’s add real authentication. We’ll start with user registration, password hashing, and login. Each user will get a token after logging in, which they can use to access protected routes.</p>
<h3 id="heading-1-set-up-the-user-model">1. Set Up the User Model</h3>
<p>We’ll store users in a simple database. For this demo, let’s use MongoDB with Mongoose, since it’s quick to set up and easy to scale later.</p>
<p>Install the required packages:</p>
<pre><code class="lang-bash">npm install mongoose
</code></pre>
<p>Then create <code>models/user.js</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mongoose'</span>);

<span class="hljs-keyword">const</span> userSchema = <span class="hljs-keyword">new</span> mongoose.Schema({
  <span class="hljs-attr">username</span>: { <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">unique</span>: <span class="hljs-literal">true</span> },
  <span class="hljs-attr">email</span>: { <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">unique</span>: <span class="hljs-literal">true</span> },
  <span class="hljs-attr">password</span>: { <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span> }
});

<span class="hljs-built_in">module</span>.exports = mongoose.model(<span class="hljs-string">'User'</span>, userSchema);
</code></pre>
<p>We store users with a unique email and a hashed password. The database never sees the raw password. Hashing makes stolen data harder to use.</p>
<h3 id="heading-2-connect-to-mongodb">2. Connect to MongoDB</h3>
<p>Inside <code>config/db.js</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mongoose'</span>);

<span class="hljs-keyword">const</span> connectDB = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> mongoose.connect(process.env.MONGO_URI);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'MongoDB connected'</span>);
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.error(err.message);
    process.exit(<span class="hljs-number">1</span>);
  }
};

<span class="hljs-built_in">module</span>.exports = connectDB;
</code></pre>
<p><code>mongoose.connect</code> reads the connection string from <code>.env</code>. If the connection fails, we exit the process so we don’t continue in a broken state.</p>
<p>Update your <code>server.js</code> to include the connection:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> connectDB = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./config/db'</span>);
connectDB();
</code></pre>
<p>And don’t forget to add your MongoDB URI in the <code>.env</code> file:</p>
<pre><code class="lang-plaintext">MONGO_URI=mongodb+srv://yourusername:yourpassword@cluster.mongodb.net/auth
JWT_SECRET=your_jwt_secret_key
</code></pre>
<h3 id="heading-3-create-registration-and-login-routes">3. Create Registration and Login Routes</h3>
<p>In <code>routes/auth.js</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> bcrypt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'bcryptjs'</span>);
<span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'jsonwebtoken'</span>);
<span class="hljs-keyword">const</span> User = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../models/user'</span>);

<span class="hljs-keyword">const</span> router = express.Router();

<span class="hljs-comment">// Register a new user</span>
router.post(<span class="hljs-string">'/register'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { username, email, password } = req.body;

    <span class="hljs-keyword">const</span> existingUser = <span class="hljs-keyword">await</span> User.findOne({ email });
    <span class="hljs-keyword">if</span> (existingUser) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'User already exists'</span> });

    <span class="hljs-keyword">const</span> hashedPassword = <span class="hljs-keyword">await</span> bcrypt.hash(password, <span class="hljs-number">10</span>);

    <span class="hljs-keyword">const</span> newUser = <span class="hljs-keyword">new</span> User({ username, email, <span class="hljs-attr">password</span>: hashedPassword });
    <span class="hljs-keyword">await</span> newUser.save();

    res.status(<span class="hljs-number">201</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'User created successfully'</span> });
  } <span class="hljs-keyword">catch</span> (err) {
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Server error'</span> });
  }
});

<span class="hljs-comment">// Login and issue JWT</span>
router.post(<span class="hljs-string">'/login'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { email, password } = req.body;

    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findOne({ email });
    <span class="hljs-keyword">if</span> (!user) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Invalid credentials'</span> });

    <span class="hljs-keyword">const</span> isMatch = <span class="hljs-keyword">await</span> bcrypt.compare(password, user.password);
    <span class="hljs-keyword">if</span> (!isMatch) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Invalid credentials'</span> });

    <span class="hljs-keyword">const</span> payload = { <span class="hljs-attr">id</span>: user._id, <span class="hljs-attr">email</span>: user.email };

    <span class="hljs-keyword">const</span> token = jwt.sign(payload, process.env.JWT_SECRET, { <span class="hljs-attr">expiresIn</span>: <span class="hljs-string">'15m'</span> });

    res.json({ token });
  } <span class="hljs-keyword">catch</span> (err) {
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Server error'</span> });
  }
});

<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<p>Add it to your server in <code>server.js</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> authRoutes = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./routes/auth'</span>);
app.use(<span class="hljs-string">'/api/auth'</span>, authRoutes);
</code></pre>
<h3 id="heading-4-test-it-out">4. Test It Out</h3>
<p>You can now test these routes using Postman or Insomnia.</p>
<p>Send a <code>POST</code> request to <code>/api/auth/register</code> with a JSON body:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"username"</span>: <span class="hljs-string">"demoUser"</span>,
  <span class="hljs-attr">"email"</span>: <span class="hljs-string">"demo@email.com"</span>,
  <span class="hljs-attr">"password"</span>: <span class="hljs-string">"mypassword"</span>
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760713863394/c13ddbd5-ebb1-47d1-9b6d-06bc0f33eb7d.png" alt="Screenshot of a Postman request sending a POST call to http://localhost:3000/api/auth/register with a JSON body containing a username, email, and password. The response area shows a 201 Created status and the message “User created successfully.&quot;" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The register route checks for an existing user by email. It hashes the password with a cost factor of 10 and then returns a 201 on success. We don’t log the password or include it in the response.</p>
<p>Then log in at <code>/api/auth/login</code> to receive a JWT.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760713960135/58eeaa4e-d652-4509-ad6e-756baf19ff8c.png" alt="Screenshot of a Postman request sending a POST call to http://localhost:3000/api/auth/login with a JSON body containing a username, email, and password. The response panel shows a 200 OK status and a JSON object with a generated JWT token." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The login route finds the user by email and compares the password with bcrypt.compare. If it matches, we sign a token with a small payload: the user ID and email. The JWT_SECRET signs the token so the server can verify it later. The expiresIn: '15m' setting keeps the token short-lived to limit risk. The response only includes the token. User data can be fetched from a protected route.</p>
<p>Once you get the token, copy it, you’ll use it to access protected routes later.</p>
<h2 id="heading-how-to-verify-jwts-and-protect-routes">How to Verify JWTs and Protect Routes</h2>
<p>Now that login returns a token, we should verify it on each request that needs auth. We will write a small middleware that checks the <code>Authorization</code> header, validates the token, and adds the user info to the request.</p>
<h3 id="heading-1-create-the-auth-middleware">1. Create the Auth Middleware</h3>
<p>Create <code>middleware/auth.js</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'jsonwebtoken'</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">auth</span>(<span class="hljs-params">req, res, next</span>) </span>{
  <span class="hljs-keyword">const</span> authHeader = req.headers.authorization || <span class="hljs-string">''</span>;
  <span class="hljs-keyword">const</span> [scheme, token] = authHeader.split(<span class="hljs-string">' '</span>);

  <span class="hljs-keyword">if</span> (scheme !== <span class="hljs-string">'Bearer'</span> || !token) {
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Missing or invalid Authorization header'</span> });
  }

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = { <span class="hljs-attr">id</span>: decoded.id, <span class="hljs-attr">email</span>: decoded.email };
    next();
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-keyword">if</span> (err.name === <span class="hljs-string">'TokenExpiredError'</span>) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Access token expired'</span> });
    }
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Invalid token'</span> });
  }
}

<span class="hljs-built_in">module</span>.exports = auth;
</code></pre>
<p>What it does:</p>
<ul>
<li><p>Reads the <code>Authorization</code> header.</p>
</li>
<li><p>Checks for the <code>Bearer &lt;token&gt;</code> format.</p>
</li>
<li><p>Verifies the token with the secret.</p>
</li>
<li><p>Attaches a simple <code>user</code> object to <code>req</code> for later use.</p>
</li>
</ul>
<h3 id="heading-2-create-the-protected-route">2. Create the Protected Route</h3>
<p>Create a small profile route that returns the current user. Add <code>routes/profile.js</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> auth = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../middleware/auth'</span>);
<span class="hljs-keyword">const</span> User = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../models/user'</span>);

<span class="hljs-keyword">const</span> router = express.Router();

router.get(<span class="hljs-string">'/me'</span>, auth, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findById(req.user.id).select(<span class="hljs-string">'-password'</span>);
    <span class="hljs-keyword">if</span> (!user) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'User not found'</span> });
    }
    res.json({ user });
  } <span class="hljs-keyword">catch</span> (err) {
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Server error'</span> });
  }
});

<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<p>Wire it in <code>server.js</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> profileRoutes = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./routes/profile'</span>);
app.use(<span class="hljs-string">'/api/profile'</span>, profileRoutes);
</code></pre>
<p>Now a <code>GET /api/profile/me</code> call will only work with a valid token.</p>
<h3 id="heading-3-handle-token-expiry-clearly">3. Handle Token Expiry Clearly</h3>
<p>Short access tokens reduce damage if they leak. We set <code>expiresIn: '15m'</code> during login. When a token expires, the middleware returns a 401 with <code>Access token expired</code>.</p>
<p>We won’t refresh the token here because refresh requires its own endpoint, storage, and rotation rules. You’ll add that in <strong>“Refresh Tokens and Rotation.”</strong> For now, the 401 proves that the expiry is enforced.</p>
<h3 id="heading-4-testing-the-flow">4. Testing the Flow</h3>
<p>In this section, we’ll test that the server blocks requests without a valid token and allows requests with a valid token.</p>
<p>Log in at <code>/api/auth/login</code> and copy the token. Then call <code>/api/profile/me</code> with:</p>
<pre><code class="lang-typescript">Authorization: Bearer &lt;paste_token_here&gt;
</code></pre>
<p>You should see the current user without the password field.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760715340324/13779fe0-304c-460b-87ac-c86133eea2a4.png" alt="Screenshot of a Postman GET request to http://localhost:3000/api/profile/me using a valid JWT. The response shows a 200 OK status and returns the user’s _id, username, and email, confirming that the protected route works when a proper token is included." width="600" height="400" loading="lazy"></p>
<p>Then remove the header or change the token and call again. You should get a 401.</p>
<p>Next, wait for the token to expire or change <code>expiresIn</code> to a very short value for a quick test. Call again and confirm you get <code>Access token expired</code>.</p>
<h4 id="heading-tips-for-debugging">Tips for debugging</h4>
<ul>
<li><p>401 with “Missing or invalid Authorization header” means the header format is wrong. Use <code>Authorization: Bearer &lt;token&gt;</code>.</p>
</li>
<li><p>401 with “Invalid token” means the token string is wrong, signed with the wrong secret, or corrupted.</p>
</li>
<li><p>401 with “Access token expired” means the expiry check works. You will fix the client experience with the refresh endpoint later.</p>
</li>
<li><p>If all calls fail, confirm your <code>JWT_SECRET</code> is set in <code>.env</code> and that the server was restarted after changes.</p>
</li>
</ul>
<h3 id="heading-5-optional-cookie-support">5. Optional Cookie Support</h3>
<p>You can store tokens in HTTP-only cookies. The browser sends them automatically. Scripts cannot read HTTP-only cookies, which reduces the risk from XSS.</p>
<p>Install and enable cookies:</p>
<pre><code class="lang-plaintext">npm install cookie-parser
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">// server.js</span>
<span class="hljs-keyword">const</span> cookieParser = <span class="hljs-built_in">require</span>(<span class="hljs-string">'cookie-parser'</span>);
app.use(cookieParser());
</code></pre>
<p>Read the access token from a cookie as a fallback:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// middleware/auth.js</span>
<span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'jsonwebtoken'</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">auth</span>(<span class="hljs-params">req, res, next</span>) </span>{
  <span class="hljs-keyword">const</span> header = req.headers.authorization || <span class="hljs-string">''</span>;
  <span class="hljs-keyword">const</span> [scheme, tokenFromHeader] = header.split(<span class="hljs-string">' '</span>);
  <span class="hljs-keyword">const</span> tokenFromCookie = req.cookies?.access_token;

  <span class="hljs-keyword">const</span> token = scheme === <span class="hljs-string">'Bearer'</span> &amp;&amp; tokenFromHeader ? tokenFromHeader : tokenFromCookie;

  <span class="hljs-keyword">if</span> (!token) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'No token provided'</span> });

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = { <span class="hljs-attr">id</span>: decoded.id, <span class="hljs-attr">email</span>: decoded.email };
    next();
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-keyword">const</span> msg = err.name === <span class="hljs-string">'TokenExpiredError'</span> ? <span class="hljs-string">'Access token expired'</span> : <span class="hljs-string">'Invalid token'</span>;
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: msg });
  }
}

<span class="hljs-built_in">module</span>.exports = auth;
</code></pre>
<p>How this works:</p>
<ul>
<li><p>The access token can live in a cookie named <code>access_token</code>.</p>
</li>
<li><p>Mark the cookie as <code>httpOnly</code> and <code>secure</code> in production.</p>
</li>
<li><p>Set <code>sameSite: 'strict'</code> to reduce CSRF risk.</p>
</li>
<li><p>For APIs used by browsers, cookies simplify sending tokens. For SPAs that call many domains, an <code>Authorization</code> header may be simpler.</p>
</li>
</ul>
<p>In the next section, we’ll use the same cookie approach for the refresh token. That section explains why refresh belongs in a cookie and how rotation blocks replay.</p>
<h2 id="heading-refresh-tokens-and-rotation">Refresh Tokens and Rotation</h2>
<p>Access tokens are short-lived and used on every request. They prove the user identity quickly. Refresh tokens live longer and are used only to get new access tokens when the old ones expire. This split keeps day-to-day requests fast and limits the damage if a token leaks.</p>
<p>We will store the refresh token in an HTTP-only cookie. This reduces exposure to scripts and keeps the flow smooth.</p>
<h3 id="heading-1-install-and-setup">1. Install and Setup</h3>
<p>We already have <code>cookie-parser</code>. We won’t add anything new for now, but we will use <a target="_blank" href="https://nodejs.org/api/crypto.html">Node’s built-in <code>crypto</code> module</a> to hash the refresh token before storing it. As a reminder, hashing means the raw token is never saved. If the database leaks, attackers cannot use the hashes to log in.</p>
<p>Create <code>models/refreshToken.js</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mongoose'</span>);

<span class="hljs-keyword">const</span> refreshTokenSchema = <span class="hljs-keyword">new</span> mongoose.Schema({
  <span class="hljs-attr">user</span>: { <span class="hljs-attr">type</span>: mongoose.Schema.Types.ObjectId, <span class="hljs-attr">ref</span>: <span class="hljs-string">'User'</span>, <span class="hljs-attr">index</span>: <span class="hljs-literal">true</span> },
  <span class="hljs-attr">tokenHash</span>: { <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">unique</span>: <span class="hljs-literal">true</span> },
  <span class="hljs-attr">jti</span>: { <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">index</span>: <span class="hljs-literal">true</span> },
  <span class="hljs-attr">expiresAt</span>: { <span class="hljs-attr">type</span>: <span class="hljs-built_in">Date</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">index</span>: <span class="hljs-literal">true</span> },
  <span class="hljs-attr">revokedAt</span>: { <span class="hljs-attr">type</span>: <span class="hljs-built_in">Date</span>, <span class="hljs-attr">default</span>: <span class="hljs-literal">null</span> },
  <span class="hljs-attr">replacedBy</span>: { <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">default</span>: <span class="hljs-literal">null</span> }, <span class="hljs-comment">// new jti when rotated</span>
  <span class="hljs-attr">createdAt</span>: { <span class="hljs-attr">type</span>: <span class="hljs-built_in">Date</span>, <span class="hljs-attr">default</span>: <span class="hljs-built_in">Date</span>.now },
  <span class="hljs-attr">ip</span>: <span class="hljs-built_in">String</span>,
  <span class="hljs-attr">userAgent</span>: <span class="hljs-built_in">String</span>
});

<span class="hljs-built_in">module</span>.exports = mongoose.model(<span class="hljs-string">'RefreshToken'</span>, refreshTokenSchema);
</code></pre>
<h3 id="heading-2-token-helpers">2. Token Helpers</h3>
<p>Create <code>utils/tokens.js</code> for clean, reusable logic.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'jsonwebtoken'</span>);
<span class="hljs-keyword">const</span> crypto = <span class="hljs-built_in">require</span>(<span class="hljs-string">'crypto'</span>);
<span class="hljs-keyword">const</span> RefreshToken = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../models/refreshToken'</span>);

<span class="hljs-keyword">const</span> ACCESS_TTL = <span class="hljs-string">'15m'</span>;
<span class="hljs-keyword">const</span> REFRESH_TTL_SEC = <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">24</span> * <span class="hljs-number">7</span>; <span class="hljs-comment">// 7 days</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hashToken</span>(<span class="hljs-params">token</span>) </span>{
  <span class="hljs-keyword">return</span> crypto.createHash(<span class="hljs-string">'sha256'</span>).update(token).digest(<span class="hljs-string">'hex'</span>);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createJti</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> crypto.randomBytes(<span class="hljs-number">16</span>).toString(<span class="hljs-string">'hex'</span>);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">signAccessToken</span>(<span class="hljs-params">user</span>) </span>{
  <span class="hljs-keyword">const</span> payload = { <span class="hljs-attr">id</span>: user._id.toString(), <span class="hljs-attr">email</span>: user.email };
  <span class="hljs-keyword">return</span> jwt.sign(payload, process.env.JWT_SECRET, { <span class="hljs-attr">expiresIn</span>: ACCESS_TTL });
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">signRefreshToken</span>(<span class="hljs-params">user, jti</span>) </span>{
  <span class="hljs-keyword">const</span> payload = { <span class="hljs-attr">id</span>: user._id.toString(), jti };
  <span class="hljs-keyword">const</span> token = jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET, { <span class="hljs-attr">expiresIn</span>: REFRESH_TTL_SEC });
  <span class="hljs-keyword">return</span> token;
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">persistRefreshToken</span>(<span class="hljs-params">{ user, refreshToken, jti, ip, userAgent }</span>) </span>{
  <span class="hljs-keyword">const</span> tokenHash = hashToken(refreshToken);
  <span class="hljs-keyword">const</span> expiresAt = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now() + REFRESH_TTL_SEC * <span class="hljs-number">1000</span>);
  <span class="hljs-keyword">await</span> RefreshToken.create({ <span class="hljs-attr">user</span>: user._id, tokenHash, jti, expiresAt, ip, userAgent });
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setRefreshCookie</span>(<span class="hljs-params">res, refreshToken</span>) </span>{
  <span class="hljs-keyword">const</span> isProd = process.env.NODE_ENV === <span class="hljs-string">'production'</span>;
  res.cookie(<span class="hljs-string">'refresh_token'</span>, refreshToken, {
    <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">secure</span>: isProd,
    <span class="hljs-attr">sameSite</span>: <span class="hljs-string">'strict'</span>,
    <span class="hljs-attr">path</span>: <span class="hljs-string">'/api/auth/refresh'</span>,
    <span class="hljs-attr">maxAge</span>: REFRESH_TTL_SEC * <span class="hljs-number">1000</span>
  });
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rotateRefreshToken</span>(<span class="hljs-params">oldDoc, user, req, res</span>) </span>{
  <span class="hljs-comment">// revoke old</span>
  oldDoc.revokedAt = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>();
  <span class="hljs-keyword">const</span> newJti = createJti();
  oldDoc.replacedBy = newJti;
  <span class="hljs-keyword">await</span> oldDoc.save();

  <span class="hljs-comment">// issue new</span>
  <span class="hljs-keyword">const</span> newAccess = signAccessToken(user);
  <span class="hljs-keyword">const</span> newRefresh = signRefreshToken(user, newJti);
  <span class="hljs-keyword">await</span> persistRefreshToken({
    user,
    <span class="hljs-attr">refreshToken</span>: newRefresh,
    <span class="hljs-attr">jti</span>: newJti,
    <span class="hljs-attr">ip</span>: req.ip,
    <span class="hljs-attr">userAgent</span>: req.headers[<span class="hljs-string">'user-agent'</span>] || <span class="hljs-string">''</span>
  });
  setRefreshCookie(res, newRefresh);
  <span class="hljs-keyword">return</span> { <span class="hljs-attr">accessToken</span>: newAccess };
}

<span class="hljs-built_in">module</span>.exports = {
  hashToken,
  createJti,
  signAccessToken,
  signRefreshToken,
  persistRefreshToken,
  setRefreshCookie,
  rotateRefreshToken
};
</code></pre>
<p>In this code,</p>
<ul>
<li><p>signAccessToken creates a short token with the user ID and email.</p>
</li>
<li><p>signRefreshToken creates a long-lived token with a jti value. The jti lets us rotate and track tokens.</p>
</li>
<li><p>persistRefreshToken hashes the refresh token and stores metadata like expiry and device info.</p>
</li>
<li><p>setRefreshCookie writes the HTTP-only cookie so the browser sends it to the refresh endpoint automatically.</p>
</li>
<li><p>rotateRefreshToken revokes the old token, issues a new pair, and saves the new record. Rotation blocks replay if an old refresh token is stolen.</p>
</li>
</ul>
<h3 id="heading-3-issue-refresh-token-on-login">3. Issue Refresh Token on Login</h3>
<p>Update your <code>routes/auth.js</code> login handler to create and store a refresh token, then set the cookie.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> bcrypt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'bcryptjs'</span>);
<span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'jsonwebtoken'</span>);
<span class="hljs-keyword">const</span> User = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../models/user'</span>);
<span class="hljs-keyword">const</span> RefreshToken = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../models/refreshToken'</span>);
<span class="hljs-keyword">const</span> {
  createJti,
  signAccessToken,
  signRefreshToken,
  persistRefreshToken,
  setRefreshCookie
} = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../utils/tokens'</span>);

<span class="hljs-keyword">const</span> router = express.Router();

router.post(<span class="hljs-string">'/login'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { email, password } = req.body;

    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findOne({ email });
    <span class="hljs-keyword">if</span> (!user) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Invalid credentials'</span> });

    <span class="hljs-keyword">const</span> isMatch = <span class="hljs-keyword">await</span> bcrypt.compare(password, user.password);
    <span class="hljs-keyword">if</span> (!isMatch) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Invalid credentials'</span> });

    <span class="hljs-keyword">const</span> accessToken = signAccessToken(user);

    <span class="hljs-keyword">const</span> jti = createJti();
    <span class="hljs-keyword">const</span> refreshToken = signRefreshToken(user, jti);

    <span class="hljs-keyword">await</span> persistRefreshToken({
      user,
      refreshToken,
      jti,
      <span class="hljs-attr">ip</span>: req.ip,
      <span class="hljs-attr">userAgent</span>: req.headers[<span class="hljs-string">'user-agent'</span>] || <span class="hljs-string">''</span>
    });

    setRefreshCookie(res, refreshToken);

    res.json({ accessToken });
  } <span class="hljs-keyword">catch</span> (err) {
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Server error'</span> });
  }
});

<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<p>On login, we issue both tokens. The access token goes to the JSON response. The refresh token goes to an HTTP-only cookie scoped to <code>/api/auth/refresh</code>. This keeps the refresh token away from frontend code while still letting the browser send it to the refresh endpoint.</p>
<h3 id="heading-4-the-refresh-endpoint">4. The Refresh Endpoint</h3>
<p>Create an endpoint that reads the refresh cookie, verifies it, checks the database entry, and rotates it. If all checks pass, it returns a new access token and sets a new refresh cookie.</p>
<p>Add to <code>routes/auth.js</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { hashToken, rotateRefreshToken } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../utils/tokens'</span>);

router.post(<span class="hljs-string">'/refresh'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> token = req.cookies?.refresh_token;
    <span class="hljs-keyword">if</span> (!token) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'No refresh token'</span> });

    <span class="hljs-keyword">let</span> decoded;
    <span class="hljs-keyword">try</span> {
      decoded = jwt.verify(token, process.env.REFRESH_TOKEN_SECRET);
    } <span class="hljs-keyword">catch</span> (err) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Invalid or expired refresh token'</span> });
    }

    <span class="hljs-keyword">const</span> tokenHash = hashToken(token);
    <span class="hljs-keyword">const</span> doc = <span class="hljs-keyword">await</span> RefreshToken.findOne({ tokenHash, <span class="hljs-attr">jti</span>: decoded.jti }).populate(<span class="hljs-string">'user'</span>);

    <span class="hljs-keyword">if</span> (!doc) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Refresh token not recognized'</span> });
    }
    <span class="hljs-keyword">if</span> (doc.revokedAt) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Refresh token revoked'</span> });
    }
    <span class="hljs-keyword">if</span> (doc.expiresAt &lt; <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Refresh token expired'</span> });
    }

    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> rotateRefreshToken(doc, doc.user, req, res);
    <span class="hljs-keyword">return</span> res.json({ <span class="hljs-attr">accessToken</span>: result.accessToken });
  } <span class="hljs-keyword">catch</span> (err) {
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Server error'</span> });
  }
});
</code></pre>
<p>The refresh endpoint verifies the cookie, checks the database record, confirms it is not expired or revoked, then rotates it. Rotation sets <code>revokedAt</code> on the old record and creates a new one with a fresh <code>jti</code>. The response returns a new access token and sets a new refresh cookie.</p>
<h3 id="heading-5-logout-and-revoke">5. Logout and Revoke</h3>
<p>On logout, revoke the current refresh token and clear the cookie.</p>
<pre><code class="lang-js">router.post(<span class="hljs-string">'/logout'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> token = req.cookies?.refresh_token;
    <span class="hljs-keyword">if</span> (token) {
      <span class="hljs-keyword">const</span> tokenHash = hashToken(token);
      <span class="hljs-keyword">const</span> doc = <span class="hljs-keyword">await</span> RefreshToken.findOne({ tokenHash });
      <span class="hljs-keyword">if</span> (doc &amp;&amp; !doc.revokedAt) {
        doc.revokedAt = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>();
        <span class="hljs-keyword">await</span> doc.save();
      }
    }
    res.clearCookie(<span class="hljs-string">'refresh_token'</span>, { <span class="hljs-attr">path</span>: <span class="hljs-string">'/api/auth/refresh'</span> });
    res.json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Logged out'</span> });
  } <span class="hljs-keyword">catch</span> (err) {
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Server error'</span> });
  }
});
</code></pre>
<p>Logout revokes the matching refresh token if present and clears the cookie. This ends the session cleanly on the server side and the client side.</p>
<h3 id="heading-6-client-flow">6. Client Flow</h3>
<p>Here is how the browser app should behave:</p>
<ul>
<li><p>Keep the access token in memory. Do not put it in localStorage.</p>
</li>
<li><p>Call protected APIs with the <code>Authorization</code> header or let cookies handle it if you chose the cookie approach for access.</p>
</li>
<li><p>If a call fails with <code>Access token expired</code>, call <code>/api/auth/refresh</code>. The browser sends the refresh cookie automatically.</p>
</li>
<li><p>Replace the in-memory access token with the new one.</p>
</li>
<li><p>Retry the original request.</p>
</li>
<li><p>On logout, call <code>/api/auth/logout</code> and clear any local state.</p>
</li>
</ul>
<h3 id="heading-7-security-notes">7. Security Notes</h3>
<p>There are some key steps you can take to make sure everything is secure:</p>
<h4 id="heading-separate-secrets">Separate secrets</h4>
<p>Use a different secret for access and refresh tokens. If the access secret leaks, refresh tokens still use a different key. Set <code>JWT_SECRET</code> and <code>REFRESH_TOKEN_SECRET</code> in <code>.env</code>.</p>
<h4 id="heading-https-only">HTTPS only</h4>
<p>Serve production traffic over HTTPS. Cookies marked <code>secure: true</code> only travel over HTTPS. This protects tokens in transit.</p>
<h4 id="heading-rotate-on-every-refresh">Rotate on every refresh</h4>
<p>Issue a new refresh token and revoke the old one each time you refresh. Rotation makes a stolen old token useless after the next refresh.</p>
<h4 id="heading-hash-refresh-tokens-in-the-database">Hash refresh tokens in the database</h4>
<p>Store a SHA-256 hash, not the raw token. This way a database leak does not give attackers the actual token string.</p>
<h4 id="heading-scope-and-flags-for-cookies">Scope and flags for cookies</h4>
<p>Use <code>httpOnly: true</code>, <code>secure: true</code> in production, <code>sameSite: 'strict'</code>, and a narrow <code>path</code> such as <code>/api/auth/refresh</code>. These flags reduce XSS and CSRF risk and limit where the cookie is sent.</p>
<h4 id="heading-short-access-ttl-and-moderate-refresh-ttl">Short access TTL and moderate refresh TTL</h4>
<p>Keep access tokens short, such as 15 minutes. Use a refresh lifetime like 7 days. This keeps risk low without annoying users.</p>
<h4 id="heading-device-awareness">Device awareness</h4>
<p>Store <code>ip</code> and <code>userAgent</code>. If patterns change in a suspicious way, you can revoke or challenge the session.</p>
<h4 id="heading-auditing-and-limits">Auditing and limits</h4>
<p>Log refresh events and consider rate limits on the refresh endpoint. This helps detect abuse.’</p>
<p>Add to <code>.env</code>:</p>
<pre><code class="lang-plaintext">REFRESH_TOKEN_SECRET=your_refresh_secret_key
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You now have a working authentication system that uses JWTs and refresh tokens to keep users logged in safely. The access token handles quick verification. The refresh token quietly renews access when it expires. Together, they strike a balance between security and convenience.</p>
<p>You built user registration, login, protected routes, and a full refresh flow. You also learned how to rotate refresh tokens, store them securely, and handle logout cleanly. Each step adds another layer of safety that keeps your app and users protected.</p>
<p>From here, you can expand this setup to match your real project. You can add role-based permissions, track user sessions by device, or move the logic into a dedicated authentication service. What matters most is understanding the flow and keeping tokens short-lived and well-guarded.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The JSON Web Token Handbook: Learn to Use JWTs for Web Authentication ]]>
                </title>
                <description>
                    <![CDATA[ JWT stands for JSON Web Token, and it’s one of those terms you’ll constantly come across in modern web development. At its core, a JWT is a JSON-based open standard format that allows you to represent specific claims securely between two parties. The... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-json-web-token-handbook-learn-to-use-jwts-for-web-authentication/</link>
                <guid isPermaLink="false">68e6ab03a5598a61a63ce39d</guid>
                
                    <category>
                        <![CDATA[ JSON Web Tokens (JWT) ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JWT ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ token ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sumit Saha ]]>
                </dc:creator>
                <pubDate>Wed, 08 Oct 2025 18:18:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759947512495/9c8aee78-1a83-4958-8c01-110e2247286d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>JWT stands for JSON Web Token, and it’s one of those terms you’ll constantly come across in modern web development.</p>
<p>At its core, a JWT is a JSON-based open standard format that allows you to represent specific claims securely between two parties. The exciting part is how widely JWT is used, especially in microservice architectures and modern authentication systems.</p>
<p>In this article, we’ll break down what JWTs really are, explore their structure, and see exactly how they help secure web applications. By the end, you’ll understand why developers rely on JWTs every single day.</p>
<h2 id="heading-heres-what-well-cover">Here’s What We’ll Cover</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-jwt">What is a JWT?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-do-we-need-tokens">Why Do We Need Tokens?</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-session-tokens-the-classic-approach">Session Tokens: The Classic Approach</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-jwt-the-modern-solution">JWT: The Modern Solution</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-jwt-structure-header-payload-amp-signature">JWT Structure: Header, Payload &amp; Signature</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-example-decoding-a-jwt">Example: Decoding a JWT</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-jwts-ensure-security-the-signature">How JWTs Ensure Security: The Signature</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-security-considerations-and-token-management">Security Considerations and Token Management</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-jwts-in-different-languages">How to Create JWTs in Different Languages</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-practical-implementation-jwt-authentication-with-express-mongodb">Practical Implementation: JWT Authentication with Express + MongoDB</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-project-setup-amp-dependencies">1. Project Setup &amp; Dependencies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-project-folder-structure">2. Project Folder Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-step-by-step-implementation">3. Step-by-Step Implementation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-how-to-test-your-api">4. How to Test Your API</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-words">Final Words</a></p>
</li>
</ol>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To follow along and get the most out of this guide, you should have:</p>
<ol>
<li><p>Basic familiarity with JavaScript / Node.js</p>
</li>
<li><p>Node.js and npm installed on your local machine</p>
</li>
<li><p>Basic understanding of HTTP and REST APIs</p>
</li>
<li><p>Understanding of JSON and how to parse/serialize it</p>
</li>
<li><p>Basic knowledge of Express (or ability to follow along)</p>
</li>
<li><p>A running instance of MongoDB (local or remote)</p>
</li>
<li><p>Experience with asynchronous code / Promises / async-await</p>
</li>
<li><p>Familiarity with environment variables / .env setup</p>
</li>
</ol>
<p>I’ve also created a video to go along with this article. If you’re the type who likes to learn from video as well as text, you can check it out here:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/6drpx_QcMdg" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<h2 id="heading-what-is-a-jwt">What is a JWT?</h2>
<p>JWTs are most commonly used for authentication today, but that wasn’t actually their original purpose. They were created to provide a standard way for two parties to securely exchange information. In fact, there’s even an industry standard specification (<a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7519">RFC 7519</a>) that lays out exactly how JWTs should be structured and how they’re meant to be used for data exchange. Think of it like <a target="_blank" href="https://en.wikipedia.org/wiki/ECMAScript#:~:text=ECMAScript%20\(%2F%CB%88%C9%9Bkm,pages%20across%20different%20web%20browsers.">ECMAScript</a>, or ES, which defines the standard for JavaScript.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759525281325/62565bc2-dc09-4565-8e5b-12b6333e6ff6.jpeg" alt="Client Server Secure Communication" class="image--center mx-auto" width="1919" height="1080" loading="lazy"></p>
<p>In real-world applications, JWTs are primarily used for authentication, and that’s the angle we’ll focus on in this article.</p>
<p>But remember that JWTs weren’t designed only for authentication. There are other ways to handle authentication too, and one of the most popular alternatives is session tokens.</p>
<h2 id="heading-why-do-we-need-tokens">Why Do We Need Tokens?</h2>
<p>Whatever authentication strategy we use, whether it’s a session token or a JWT, the underlying reason is the same: the stateless nature of the HTTP protocol.</p>
<p>When we exchange requests and responses from a browser to a server or between servers using HTTP, the protocol itself does not retain any information.</p>
<p><em>Stateless</em> means that during interactions between the client and the server, HTTP doesn’t remember any previous requests or data. In other words, every request must carry all the necessary information separately. HTTP doesn’t store any data on its own. Once it receives information, it forgets it. That’s why we say HTTP is stateless, as it has no inherent state or persistent information.</p>
<p>Think of it this way: when we access a webpage from a server, what information do we actually send to the server? If it’s a simple static website, we don’t need to send much. We just send the URL of the page to the server, and the server responds by delivering the corresponding HTML page. This means the server doesn’t need to remember any information or maintain any state, which is exactly how HTTP is designed to work, because HTTP itself is stateless.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759525352836/7e6081f5-7d34-462a-9a7d-bcffd0242e00.jpeg" alt="Simple HTML Response from a Static Website" class="image--center mx-auto" width="1919" height="1080" loading="lazy"></p>
<p>But if the web application provides different responses for each user – in other words, if the website is dynamic – then sending only the URL is not sufficient. The user must also send their identity along with the URL to the server.</p>
<p>For example, if a user wants to access <code>page-1</code>, they must tell the server: “<em>I am User A, please give me page-1.</em>” The server will then respond with <code>page-1</code> accordingly. But next time, if the user requests, “<em>Now give me page-2</em>”, what will the server do? Since HTTP is stateless, if the request doesn’t include the user’s identity, the server won’t know which response to provide. This means that with every request, the user must provide their identity, right?</p>
<p>But if we look at the websites around us, do we really have to provide our identity every single time? Take Facebook as an example. Once we authenticate and log in, the server shows us the homepage when we request it, or our profile page when we request that, without requiring us to authenticate with every single request.</p>
<p>So the question is, if HTTP is stateless, how is this possible? How does the web application remember our browsing session? The answer is that, web applications can maintain sessions in different ways, and one of the most common methods is by using <strong>tokens</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759525399836/7b7cdeab-4baa-4cda-bbeb-aaf4e4d4170c.jpeg" alt="How Server Remember our Browsing Session?" class="image--center mx-auto" width="1918" height="1077" loading="lazy"></p>
<h3 id="heading-session-tokens-the-classic-approach">Session Tokens: The Classic Approach</h3>
<p>There are two popular options for this. One is a <strong>Session Token</strong>, and the other is a <strong>JSON Web Token (JWT)</strong>. Let’s understand both so that it becomes clear what JWTs are and why they’re used.</p>
<p>Imagine a scenario in a company’s customer care department. A customer calls in with a complaint. The customer support representative listens to the issue and tries various troubleshooting steps but is unable to resolve the problem.</p>
<p>At this point, they forward the case to their higher management team and create a case file for the customer. This file contains all conversations with the customer and details of the troubleshooting attempts. The customer is then given a case ID or ticket ID, so that the next time they call, they don’t have to go through the same steps all over again.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759525453002/c56bb7da-f6dd-4afe-b16b-966149bc7f91.jpeg" alt="Customer Care Scenerio 1 - Session Token Analogy" class="image--center mx-auto" width="1920" height="1080" loading="lazy"></p>
<p>The next day, when the customer calls again, they give their ticket ID to the customer care representative. The representative searches the system using that ticket ID, retrieves the details, and is able to respond accurately to the customer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759525515798/426af5fa-ff38-4ce2-ae1b-48ca1a8f1e6c.jpeg" alt="Customer Care Scenerio 2 - Session Token Analogy" class="image--center mx-auto" width="1920" height="1080" loading="lazy"></p>
<p>This scenario illustrates how authentication works in a web application using a session token. When a user authenticates, the server creates a session and keeps track of it. A session ID is generated for that session and sent back to the user, similar to the support ticket in the earlier example. From then on, whenever the user sends a request to the server, they include this session ID or token. The server looks up the session using that ID and identifies the client. Since the server has to handle multiple clients, this session token method has become an effective and widely used strategy for authentication.</p>
<p>And how the client sends the session ID to the server can vary depending on the implementation. The most common method is to store the session ID in the browser’s cookies. The advantage of this approach is that whenever the browser sends a request to the same server, it automatically adds the cookie information to the request header. This is a built-in behaviour of browsers, so no extra steps are needed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759525561275/5881de41-571d-40ca-a0f7-4022d8c41754.jpeg" alt="Session Token Example" class="image--center mx-auto" width="1920" height="1080" loading="lazy"></p>
<p>When the user authenticates, the server saves data in the browser’s cookie, and from then on, that cookie information is sent automatically with every request, allowing the server to recognize the user. This was a very popular method, although in modern applications it has become a bit outdated.</p>
<p>But this mechanism has some issues. The biggest problem is that it assumes there is only a single server. In modern web applications, there are usually multiple servers. In such cases, a load balancer sits in front and decides which server will handle the user’s request.</p>
<p>Let’s say the session token method is being used. When the user sends the first request, the load balancer forwards it to <code>Server-1</code>. <code>Server-1</code> creates a session ID and sends it back to the client. Later, when the user sends another request, the load balancer routes it to <code>Server-2</code>. But <code>Server-2</code> doesn’t have that session ID stored, so how will it know which user the request belongs to?</p>
<p>The common solution to this is to store session IDs not on a specific server but in a shared <a target="_blank" href="https://redis.io/">Redis</a> database, so that any server can verify the session ID from there. This is what’s called a <strong>Redis cache</strong>. But in a microservice architecture, this approach has a weakness. If for some reason the Redis cache goes down, the servers may still be running, but the authentication mechanism will fail. This is exactly where JSON Web Tokens come in, offering a slightly different approach.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759525611999/a970e2d9-6663-4a4e-9c63-37ea13470b90.jpeg" alt="Session Token Handling Multiple Servers with Redis Cache" class="image--center mx-auto" width="1920" height="1080" loading="lazy"></p>
<h3 id="heading-jwt-the-modern-solution">JWT: The Modern Solution</h3>
<p>Let’s revisit the customer care department example. This time, imagine there’s no phone or system. The customer comes directly to the office and meets the support agent in person. Since the agent doesn’t have any system this time, they can’t store all the information like before. Instead, they write everything down on a piece of paper and tell the customer, “<em>Next time you come, bring this with you.</em>”</p>
<p>This means the method is a bit different from the previous concept, right? But there’s still a problem: “<strong>validity</strong>”. If the customer isn’t legitimate and acts maliciously, how can the support representative trust them? The next day, if the customer comes in with the same information written on a blank sheet of paper, how can the agent verify the validity of their identity?</p>
<p>In this case, a possible solution is for the customer care executive to sign the paper when giving it to the customer. Then, when the customer brings the paper back, the support representative can verify the signature and confidently provide the service.</p>
<p>JSON Web Tokens work in a similar way. Here, when the client authenticates, instead of the server saving all the information, it sends all the user’s information as a JSON token along with a signature. Later, with each subsequent request, the client sends the entire token along with the request, which contains information like which user it is, their name, and other necessary details.</p>
<p>In this case, the server doesn’t save anything, and all the information stays with the client. Each time the client sends a request with this token, the server can read it, identify which user made the request, and provide the necessary data.</p>
<p>This token is not just a simple ID. It’s a JSON object containing all the information, and this is what we call a JSON Web Token. How the client stores this JWT is entirely up to the client. The most common methods are storing it in the browser’s cookies or local storage.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759525648690/691848c9-e4c2-4b3f-b3f5-06623627e38f.jpeg" alt="JSON Web Token Analogy" class="image--center mx-auto" width="1920" height="1080" loading="lazy"></p>
<h3 id="heading-jwt-structure-header-payload-amp-signature">JWT Structure: Header, Payload, &amp; Signature</h3>
<p>As mentioned, the server receives a JSON object, but a JWT doesn’t look like a regular JSON.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759525702339/f74219b8-4a01-4ac4-920b-449faf103520.png" alt="JWT Structure" class="image--center mx-auto" width="1920" height="1078" loading="lazy"></p>
<p>In the image above, it may seem a bit unusual. In fact, it’s an encoded version of the JSON object, a kind of scrambled or compact representation. If you look closely, you’ll see that a JWT is divided into three parts, separated by dots. The first part is the <strong>header</strong>, the second part is the <strong>JSON payload,</strong> which essentially holds our data, and the third part is the <strong>signature</strong>.</p>
<p>If we examine each part individually:</p>
<ul>
<li><p>The <strong>header</strong> is a separate JSON object.</p>
</li>
<li><p>The <strong>payload</strong> is also a separate JSON object containing our data.</p>
</li>
<li><p>The third part is the <strong>signature</strong>.</p>
</li>
</ul>
<p>But what does the signature mean here? Simply put, the signature is a hash value. Our data is hashed using a secret key to create the signature. This secret key is kept on the server. So, when this JSON Web Token is sent to the server, the server can use that secret key to verify the signature. This ensures that the token is valid and has not been tampered with.</p>
<h2 id="heading-example-decoding-a-jwt">Example: Decoding a JWT</h2>
<p>Let’s look at an example. The best website for working with JWTs and understanding their structure is <a target="_blank" href="http://jwt.io">jwt.io</a><a target="_blank" href="https://jwt.io/">.</a> If you paste a JWT into the site, three sections appear: the header, payload, and signature. The payload is shown in the “Decoded Payload” section, which contains content and data. You’ll see there’s an ID, a JSON object with a name, and an expiration time.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759525738886/84c2532f-dc09-4a96-83de-ecd4a24d958f.jpeg" alt="Decoding a JWT" class="image--center mx-auto" width="636" height="367" loading="lazy"></p>
<p>The header is also a completely valid JSON object, which specifies an algorithm and shows the type –essentially indicating which algorithm will be used to create or verify this JWT.</p>
<p>So, the main data is in the “Decoded Payload” section, and the third part is the signature. Now there’s an important point to note: you might wonder where this scrambled-looking token comes from. It’s actually very simple. The data in the “Decoded Payload” is <strong>Base64 encoded</strong>, and that’s what forms the appearance of this scrambled token.</p>
<p>If you copy this part of the JWT and paste it into any online Base64 decoder, you’ll immediately see the data.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759525794705/4ee950a2-2ad0-40b4-8287-fdfea9543a6f.png" alt="Base64 Encode Decode" class="image--center mx-auto" width="1919" height="1080" loading="lazy"></p>
<p>What does this mean? It means that if this data is encoded again using Base64, the same token will be generated. The header works the same way as well.</p>
<p>And the final point: the scrambled or encoded part. Is it done for security? No, it’s not for security. It’s done purely for convenience. JSON objects can be quite large, and not all programming languages handle them in the same way. In JavaScript it’s easy, but in other languages, it can sometimes cause issues. So to make it easier to handle, the data is Base64 encoded. This is not for security, as encoding it like this doesn’t make the data secure, because the information can still be viewed publicly.</p>
<p>As you can see in the diagram above, the moment you enter it on this site, your data is immediately visible. This means that no sensitive information should be stored here, only user identification details, like a user ID or other public information. <strong>Passwords or any secret keys should never be stored in the token, because they can be easily read.</strong> Even though it looks scrambled or encoded, it is actually public.</p>
<h2 id="heading-how-jwts-ensure-security-the-signature">How JWTs Ensure Security: The Signature</h2>
<p>Now let’s move to the security part, which is ensured by the signature. In our earlier paper example, a person could simply add a signature by hand.</p>
<p>But for data, the process of creating a signature is different. For data, the signature is created cryptographically using a secret key, which is the actual signature. The process of creating the signature is as follows:</p>
<ol>
<li><p>The data is Base64 encoded.</p>
</li>
<li><p>It is concatenated with the secret key.</p>
</li>
<li><p>It is encoded again in Base64.</p>
</li>
</ol>
<p>The configuration specifies an algorithm. This algorithm can be changed, but the same algorithm used to create the token must be used to verify it. In other words, the algorithm for generating and verifying the token must always be the same.</p>
<p>Finally, the data is hashed using a secret key. This secret key is not available to the public. Instead, it’s kept only on the server, usually stored securely in a server vault. When this JWT reaches the server, the server uses the secret key to verify whether the token is valid. If it doesn’t match correctly, it will display “invalid signature.” This ensures that the server can confirm whether the token has been tampered with and that its integrity is intact.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759525829955/bf017016-d9fd-43cb-836a-eafe4f35540b.jpeg" alt="The Big Formula" class="image--center mx-auto" width="1224" height="1078" loading="lazy"></p>
<p>For example, if you use <code>love-you-all-from-logicbaselabs</code> as the signature, and the server verifies it, it will show “<em>signature verified</em>”. This demonstrates that the secret key exists only on the server. This ensures that even though public information is displayed, the token’s validity can be confirmed.</p>
<p>JSON Web Tokens aren’t like a password, though. They primarily serve to identify the user. The server can check the JWT to determine whether it belongs to a valid user. In other words, the JWT represents the user’s identity. It’s a very important token, containing secure content along with the signature.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759525873387/a434b453-0a38-41a5-93f3-bd12b46806f3.jpeg" alt="Signature Verification" class="image--center mx-auto" width="1920" height="1078" loading="lazy"></p>
<h2 id="heading-security-considerations-and-token-management">Security Considerations and Token Management</h2>
<p>One important thing to remember: if someone gets hold of your JWT, meaning they have the exact same token, they can easily log in as that user. They just need to send requests with that token to gain the necessary access.</p>
<p>You could think of it like this: if someone gets hold of your Facebook password, they can log in to your Facebook account. Similarly, if someone obtains your PayPal account PIN, they can easily access your account. In other words, if someone gets hold of your most secure information, there’s no way to protect it.</p>
<p>The same applies to JWTs: keeping the token safely on the client side is absolutely crucial. In this regard, we are somewhat vulnerable.</p>
<p>There is, though, one key difference. In the case of session tokens, if we assume an account has been compromised, the server can invalidate that session. In other words, no one can log in using that session ID anymore.</p>
<p>But with a JWT, the token remains valid until its expiration time. So there’s no direct way to invalidate it. Since the token is cryptographically self-contained and signed with the server’s secret key, once it’s created, it cannot be directly revoked by the server.</p>
<p>The only way to handle this is what’s done on the web: denylisting the token. In other words, the server maintains a separate database listing all JWT tokens that are denylisted. Whenever a request comes in, the server first verifies whether the token is valid. Then, through middleware, it checks whether the token is on the denylist. Only if it’s not on that list is the user allowed access.</p>
<p>So, these are the rules for using JSON Web Tokens. JWTs can be used in any programming language, especially in the context of REST APIs. They are extremely popular and widely used in microservice architectures.</p>
<h2 id="heading-how-to-create-jwts-in-different-languages">How to Create JWTs in Different Languages</h2>
<p>How you create a JWT depends on the programming language you’re using. For example, in Node.js, there are specialized libraries available, like <a target="_blank" href="https://www.npmjs.com/package/jsonwebtoken">jsonwebtoken</a>, so it’s straightforward. And in PHP, there are easy-to-use options for creating JWTs as well. So, JWTs are a universal tool, not limited to any specific programming language. Many people think they’re only for JavaScript, but that’s not true.</p>
<p>And remember that JWTs aren’t just used for authentication purposes. You can use them to represent any kind of identity. For example, if you’re going to a concert, access could be granted using a JWT instead of a regular ticket. When your client uses that JWT, the gateway or server can read the token, provide access to the information, and verify it using the signature.</p>
<h2 id="heading-practical-implementation-jwt-authentication-with-express-mongodb">Practical Implementation: JWT Authentication with Express + MongoDB</h2>
<p>In this section, we will put into practice all the concepts we have learned so far. Using <a target="_blank" href="https://www.freecodecamp.org/news/the-express-handbook/"><strong>Express.js</strong></a> and <a target="_blank" href="https://www.freecodecamp.org/news/how-to-start-using-mongodb/"><strong>MongoDB</strong></a>, we will build a complete JWT authentication system step by step.</p>
<p>Don’t worry if it feels overwhelming at first. We will go carefully, one step at a time, and by the end, you will have a fully working project. Think of it as entering a building floor by floor: we’ll explore each section thoroughly and come out with a solid understanding.</p>
<h3 id="heading-1-project-setup-amp-dependencies">1. Project Setup &amp; Dependencies</h3>
<p>Before writing any code, we need to set up our Node.js project and install the required dependencies.</p>
<h4 id="heading-initialize-the-nodejs-project">Initialize the Node.js Project</h4>
<p>Open your terminal and run:</p>
<pre><code class="lang-javascript">mkdir jwt-auth-demo
cd jwt-auth-demo
npm init -y
</code></pre>
<p>This will create a <code>package.json</code> file with default settings.</p>
<h4 id="heading-install-dependencies">Install Dependencies</h4>
<p>We need some packages to build our JWT authentication system:</p>
<pre><code class="lang-javascript">npm install express mongoose bcryptjs jsonwebtoken dotenv
</code></pre>
<ul>
<li><p><code>express</code>: Fast and minimal Node.js web framework to create API routes.</p>
</li>
<li><p><code>mongoose</code>: ODM (Object Data Modeling) library to interact with MongoDB easily.</p>
</li>
<li><p><code>bcryptjs</code>: Library to hash and compare passwords securely.</p>
</li>
<li><p><code>jsonwebtoken</code>: Library to generate and verify JWT tokens.</p>
</li>
<li><p><code>dotenv</code>: Loads environment variables from a <code>.env</code> file to keep secrets secure.</p>
</li>
</ul>
<h4 id="heading-install-dev-dependencies-optional">Install Dev Dependencies (Optional)</h4>
<p>For development convenience, install <strong>nodemon</strong> to auto-restart the server on file changes:</p>
<pre><code class="lang-javascript">npm install --save-dev nodemon
</code></pre>
<p>Update <code>package.json</code> scripts:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"scripts"</span>: {
  <span class="hljs-string">"start"</span>: <span class="hljs-string">"node server.js"</span>,
  <span class="hljs-string">"dev"</span>: <span class="hljs-string">"nodemon server.js"</span>
}
</code></pre>
<ul>
<li><p><code>npm start</code> runs the server normally.</p>
</li>
<li><p><code>npm run dev</code> runs the server with auto-restart using <strong>nodemon</strong>.</p>
</li>
</ul>
<h3 id="heading-2-project-folder-structure">2. Project Folder Structure</h3>
<pre><code class="lang-javascript">jwt-auth-demo/
│
├── config/
│   └── db.js
│
├── controllers/
│   └── authController.js
│
├── middlewares/
│   └── authMiddleware.js
│
├── models/
│   └── User.js
│
├── routes/
│   └── auth.js
│
├── services/
│   ├── hashService.js
│   └── jwtService.js
│
├── .env
├── server.js
├── package.json
</code></pre>
<p><strong>What goes where?</strong></p>
<ul>
<li><p><code>config/</code>: Database connection and environment config.</p>
</li>
<li><p><code>controllers/</code>: Main logic for each endpoint.</p>
</li>
<li><p><code>middlewares/</code>: Functions that run before controllers (for example, auth checks).</p>
</li>
<li><p><code>models/</code>: Mongoose schemas.</p>
</li>
<li><p><code>routes/</code>: API endpoint definitions.</p>
</li>
<li><p><code>services/</code>: Reusable logic (hashing, JWT).</p>
</li>
<li><p><code>.env</code>: Secrets and config variables.</p>
</li>
<li><p><code>server.js</code>: Entry point of the app.</p>
</li>
</ul>
<h3 id="heading-3-step-by-step-implementation">3. Step-by-Step Implementation</h3>
<h4 id="heading-initialize-the-express-server">Initialize the Express Server</h4>
<p>Before doing anything complex, we need to set up a simple server using Express. Think of this as the heart of our application. This server will be responsible for listening to incoming requests (like user login or register) and sending back responses.</p>
<p><strong>File: server.js</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server.js</span>

<span class="hljs-comment">// Import the express library to build our server</span>
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);

<span class="hljs-comment">// Create an instance of express</span>
<span class="hljs-keyword">const</span> app = express();

<span class="hljs-comment">// Middleware to parse JSON request bodies (important for APIs)</span>
app.use(express.json());

<span class="hljs-comment">// Default route to test server</span>
app.get(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">"Hello World! Your server is working 🚀"</span>);
});

<span class="hljs-comment">// Start the server on port 5000</span>
<span class="hljs-keyword">const</span> PORT = process.env.PORT || <span class="hljs-number">5000</span>;
app.listen(PORT, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server running on http://localhost:<span class="hljs-subst">${PORT}</span>`</span>);
});
</code></pre>
<ul>
<li><p>We import Express and create an app instance.</p>
</li>
<li><p>We use middleware to parse JSON requests (important for APIs).</p>
</li>
<li><p>We define a simple route <code>/</code> to test if our server works.</p>
</li>
<li><p>We start the server on port 5000 and log a message when it's running.</p>
</li>
</ul>
<p>Now, let’s test it:</p>
<ul>
<li><p>Run <code>node server.js</code> or <code>npm run dev</code>.</p>
</li>
<li><p>Open your browser at <code>http://localhost:5000</code>.</p>
</li>
<li><p>You should see: <code>Hello World! Your server is working 🚀</code></p>
</li>
</ul>
<h4 id="heading-connect-mongodb-with-mongoose">Connect MongoDB with Mongoose</h4>
<p>In this step, we want to store users in a database. For that, we will use MongoDB. To interact with MongoDB in Node.js easily, we use Mongoose, which is an ODM library.</p>
<p><strong>File: config/db.js</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// config/db.js</span>

<span class="hljs-comment">// Import mongoose</span>
<span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);

<span class="hljs-comment">// Connect to MongoDB using environment variable</span>
<span class="hljs-keyword">const</span> connectDB = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> mongoose.connect(process.env.MONGO_URI, {
      <span class="hljs-attr">useNewUrlParser</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">useUnifiedTopology</span>: <span class="hljs-literal">true</span>,
    });
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"✅ MongoDB Connected"</span>);
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"❌ MongoDB Connection Error:"</span>, err.message);
    process.exit(<span class="hljs-number">1</span>); <span class="hljs-comment">// Stop server if DB fails</span>
  }
};

<span class="hljs-built_in">module</span>.exports = connectDB;
</code></pre>
<p>Now our server is connected to MongoDB. Whenever we insert, update, or query data, it will go into this database.</p>
<p><strong>File: .env</strong></p>
<pre><code class="lang-javascript">PORT=<span class="hljs-number">5000</span>
MONGO_URI=mongodb:<span class="hljs-comment">//127.0.0.1:27017/jwt-auth-demo</span>
JWT_SECRET=your_super_secret_key
</code></pre>
<p>The .env file stores sensitive information like your database URI, JWT secret, and server port. By using environment variables, you can keep secrets out of your code and easily change configuration without modifying your source files. Never commit .env to public repositories to protect your credentials.</p>
<h4 id="heading-create-user-model">Create User Model</h4>
<p>In this step, we need to define how a User looks in our database. Each user will have a <strong>name, email, and password</strong>.</p>
<p><strong>File: models/User.js</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// models/User.js</span>
<span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);

<span class="hljs-comment">// Define a schema (blueprint of user data)</span>
<span class="hljs-keyword">const</span> userSchema = <span class="hljs-keyword">new</span> mongoose.Schema({
  <span class="hljs-attr">name</span>: { <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span> },
  <span class="hljs-attr">email</span>: { <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">unique</span>: <span class="hljs-literal">true</span> },
  <span class="hljs-attr">password</span>: { <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span> },
});

<span class="hljs-comment">// Create and export the model</span>
<span class="hljs-built_in">module</span>.exports = mongoose.model(<span class="hljs-string">"User"</span>, userSchema);
</code></pre>
<p>As you can see, each user now has a name, email, and hashed password. This ensures that every user we save has these three fields.</p>
<h4 id="heading-hashing-amp-jwt-services">Hashing &amp; JWT Services</h4>
<p>In this step, we will handle password hashing and JWT management using separate services. This keeps our code organized and reusable.</p>
<p><strong>File: services/hashService.js</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">//services/hashService.js</span>

<span class="hljs-keyword">const</span> bcrypt = <span class="hljs-built_in">require</span>(<span class="hljs-string">"bcryptjs"</span>);

<span class="hljs-comment">// Function to hash a plain password</span>
<span class="hljs-built_in">exports</span>.hashPassword = <span class="hljs-keyword">async</span> (plainPassword) =&gt; {
  <span class="hljs-comment">// bcrypt.hash generates a hashed version of the password</span>
  <span class="hljs-comment">// The number 10 is the salt rounds, which affects the hashing complexity</span>
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> bcrypt.hash(plainPassword, <span class="hljs-number">10</span>);
};

<span class="hljs-comment">// Function to compare a plain password with a hashed password</span>
<span class="hljs-built_in">exports</span>.comparePassword = <span class="hljs-keyword">async</span> (plainPassword, hashedPassword) =&gt; {
  <span class="hljs-comment">// bcrypt.compare checks if the plain password matches the hashed one</span>
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> bcrypt.compare(plainPassword, hashedPassword);
};
</code></pre>
<ul>
<li><p><code>hashPassword(plainPassword)</code>: Takes a plain text password and returns a hashed version using bcrypt. Never store plain passwords directly.</p>
</li>
<li><p><code>comparePassword(plainPassword, hashedPassword)</code>: Compares a user-entered password with the hashed password stored in the database. Returns <code>true</code> if they match.</p>
</li>
</ul>
<p><strong>File: services/jwtService.js</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// services/jwtService.js</span>

<span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">"jsonwebtoken"</span>);

<span class="hljs-comment">// Function to generate a JWT</span>
<span class="hljs-built_in">exports</span>.generateToken = <span class="hljs-function">(<span class="hljs-params">payload</span>) =&gt;</span> {
  <span class="hljs-comment">// jwt.sign creates a signed token using our secret key from environment variables</span>
  <span class="hljs-comment">// expiresIn defines how long the token is valid (1 hour here)</span>
  <span class="hljs-keyword">return</span> jwt.sign(payload, process.env.JWT_SECRET, { <span class="hljs-attr">expiresIn</span>: <span class="hljs-string">"1h"</span> });
};

<span class="hljs-comment">// Function to verify a JWT</span>
<span class="hljs-built_in">exports</span>.verifyToken = <span class="hljs-function">(<span class="hljs-params">token</span>) =&gt;</span> {
  <span class="hljs-comment">// jwt.verify checks if the token is valid and not expired</span>
  <span class="hljs-keyword">return</span> jwt.verify(token, process.env.JWT_SECRET);
};
</code></pre>
<ul>
<li><p><code>generateToken(payload)</code>: Generates a JWT for a user. The <code>payload</code> typically contains user ID and email.</p>
</li>
<li><p><code>verifyToken(token)</code>: Verifies that the JWT is valid and returns the decoded payload if successful.</p>
</li>
<li><p>Using a separate JWT service keeps token logic centralized and easy to manage.</p>
</li>
</ul>
<h4 id="heading-auth-controller">Auth Controller</h4>
<p>In this step, we will handle all authentication-related logic in a separate controller. This keeps routes clean and separates business logic from endpoint definitions.</p>
<p><strong>File: controllers/authController.js</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// controllers/authController.js</span>

<span class="hljs-keyword">const</span> User = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../models/User"</span>);
<span class="hljs-keyword">const</span> { hashPassword, comparePassword } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../services/hashService"</span>);
<span class="hljs-keyword">const</span> { generateToken } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../services/jwtService"</span>);

<span class="hljs-comment">// Register new user</span>
<span class="hljs-built_in">exports</span>.register = <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { name, email, password } = req.body; <span class="hljs-comment">// Get user input</span>

    <span class="hljs-comment">// Step 1: Check if user already exists</span>
    <span class="hljs-keyword">const</span> existingUser = <span class="hljs-keyword">await</span> User.findOne({ email });
    <span class="hljs-keyword">if</span> (existingUser)
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"User already exists!"</span> });

    <span class="hljs-comment">// Step 2: Hash password using hashService</span>
    <span class="hljs-keyword">const</span> hashedPassword = <span class="hljs-keyword">await</span> hashPassword(password);

    <span class="hljs-comment">// Step 3: Save user to database</span>
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">new</span> User({ name, email, <span class="hljs-attr">password</span>: hashedPassword });
    <span class="hljs-keyword">await</span> user.save();

    <span class="hljs-comment">// Step 4: Send success response</span>
    res.status(<span class="hljs-number">201</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"User registered successfully!"</span> });
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-comment">// Handle errors gracefully</span>
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">error</span>: err.message });
  }
};

<span class="hljs-comment">// Login user</span>
<span class="hljs-built_in">exports</span>.login = <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { email, password } = req.body; <span class="hljs-comment">// Get user input</span>

    <span class="hljs-comment">// Step 1: Find user by email</span>
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findOne({ email });
    <span class="hljs-keyword">if</span> (!user)
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Invalid email or password"</span> });

    <span class="hljs-comment">// Step 2: Compare provided password with hashed password</span>
    <span class="hljs-keyword">const</span> isMatch = <span class="hljs-keyword">await</span> comparePassword(password, user.password);
    <span class="hljs-keyword">if</span> (!isMatch)
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Invalid email or password"</span> });

    <span class="hljs-comment">// Step 3: Generate JWT using jwtService</span>
    <span class="hljs-keyword">const</span> token = generateToken({ <span class="hljs-attr">id</span>: user._id, <span class="hljs-attr">email</span>: user.email });

    <span class="hljs-comment">// Step 4: Send success response with token</span>
    res.json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Login successful!"</span>, token });
  } <span class="hljs-keyword">catch</span> (err) {
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">error</span>: err.message });
  }
};

<span class="hljs-comment">// Protected profile route</span>
<span class="hljs-built_in">exports</span>.profile = <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-comment">// req.user is set by auth middleware after token verification</span>
  res.json({
    <span class="hljs-attr">message</span>: <span class="hljs-string">"Welcome to your profile!"</span>,
    <span class="hljs-attr">user</span>: req.user,
  });
};
</code></pre>
<ul>
<li><p><strong>File:</strong> <code>controllers/authController.js</code> – Contains all logic related to authentication.</p>
</li>
<li><p><code>exports.register</code> handles user registration:</p>
<ul>
<li><p>Checks if the user exists.</p>
</li>
<li><p>Hashes the password using <code>hashService</code>.</p>
</li>
<li><p>Saves the new user to MongoDB.</p>
</li>
<li><p>Returns a success message.</p>
</li>
</ul>
</li>
<li><p><code>exports.login</code> handles user login:</p>
<ul>
<li><p>Finds the user by email.</p>
</li>
<li><p>Compares passwords using <code>hashService.comparePassword</code>.</p>
</li>
<li><p>Generates a JWT token if valid.</p>
</li>
<li><p>Returns the token in the response.</p>
</li>
</ul>
</li>
<li><p><code>exports.profile</code> handles protected profile route:</p>
<ul>
<li>Returns user information from <code>req.user</code>, which is set by the auth middleware.</li>
</ul>
</li>
<li><p>Using a controller keeps route definitions clean and separates business logic from endpoint handling.</p>
</li>
</ul>
<h4 id="heading-auth-middleware">Auth Middleware</h4>
<p>In this step, we create a middleware to protect routes by verifying JWTs. Only authenticated users can access protected endpoints.</p>
<p><strong>File: middlewares/authMiddleware.js</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// middlewares/authMiddleware.js</span>

<span class="hljs-keyword">const</span> { verifyToken } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../services/jwtService"</span>);

<span class="hljs-comment">// Middleware to protect routes</span>
<span class="hljs-built_in">module</span>.exports = <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {
  <span class="hljs-comment">// Step 1: Get Authorization header</span>
  <span class="hljs-keyword">const</span> authHeader = req.headers[<span class="hljs-string">"authorization"</span>];
  <span class="hljs-keyword">if</span> (!authHeader)
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"No token provided"</span> });

  <span class="hljs-comment">// Step 2: Extract token from format 'Bearer &lt;token&gt;'</span>
  <span class="hljs-keyword">const</span> token = authHeader.split(<span class="hljs-string">" "</span>)[<span class="hljs-number">1</span>];
  <span class="hljs-keyword">if</span> (!token) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Malformed token"</span> });

  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// Step 3: Verify token using jwtService</span>
    <span class="hljs-keyword">const</span> decoded = verifyToken(token);

    <span class="hljs-comment">// Step 4: Attach decoded user info to request object</span>
    req.user = decoded;

    <span class="hljs-comment">// Proceed to next middleware or route handler</span>
    next();
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-comment">// If token is invalid or expired</span>
    res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Invalid or expired token"</span> });
  }
};
</code></pre>
<ul>
<li><p><strong>File:</strong> <code>middlewares/authMiddleware.js</code> – Middleware for protecting routes.</p>
</li>
<li><p>Step 1: Checks if the <code>Authorization</code> header is present.</p>
</li>
<li><p>Step 2: Extracts the token from the <code>Bearer &lt;token&gt;</code> format.</p>
</li>
<li><p>Step 3: Verifies the token using <code>jwtService.verifyToken</code>.</p>
</li>
<li><p>Step 4: Attaches the decoded user info to <code>req.user</code> for use in subsequent route handlers.</p>
</li>
<li><p>If the token is missing, malformed, invalid, or expired, the middleware responds with <strong>401 Unauthorized</strong>. This ensures only authenticated users can access protected routes.</p>
</li>
</ul>
<h4 id="heading-auth-routes">Auth Routes</h4>
<p>In this step, we will define authentication-related routes and connect them with the controller and middleware.</p>
<p><strong>File: routes/auth.js</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// routes/auth.js</span>

<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> router = express.Router();
<span class="hljs-keyword">const</span> authController = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../controllers/authController"</span>);
<span class="hljs-keyword">const</span> authMiddleware = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../middlewares/authMiddleware"</span>);

<span class="hljs-comment">// Step 1: Register route</span>
<span class="hljs-comment">// Users send their name, email, and password to this endpoint</span>
router.post(<span class="hljs-string">"/register"</span>, authController.register);

<span class="hljs-comment">// Step 2: Login route</span>
<span class="hljs-comment">// Users send email and password to receive JWT</span>
router.post(<span class="hljs-string">"/login"</span>, authController.login);

<span class="hljs-comment">// Step 3: Protected profile route</span>
<span class="hljs-comment">// Only accessible to authenticated users with a valid JWT</span>
router.get(<span class="hljs-string">"/profile"</span>, authMiddleware, authController.profile);

<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<ul>
<li><p><strong>File:</strong> <code>routes/auth.js</code> – Central file to define authentication endpoints.</p>
</li>
<li><p><code>router.post("/register", authController.register)</code>: Handles user registration.</p>
</li>
<li><p><code>router.post("/login", authController.login)</code>: Handles user login and token generation.</p>
</li>
<li><p><code>router.get("/profile", authMiddleware, authController.profile)</code>: Protected route, requires JWT. The <code>authMiddleware</code> ensures only authenticated users can access it.</p>
</li>
<li><p>Using routes with controllers and middleware keeps the application organized and professional.</p>
</li>
</ul>
<h4 id="heading-main-server-file">Main Server File</h4>
<p>This is the main entry point of our application. It sets up the server, connects to the database, and mounts all routes.</p>
<p><strong>File: server.js</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server.js</span>

<span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config(); <span class="hljs-comment">// Step 1: Load environment variables from .env</span>
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> connectDB = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./config/db"</span>);

<span class="hljs-keyword">const</span> app = express();

<span class="hljs-comment">// Step 2: Connect to MongoDB</span>
connectDB();

<span class="hljs-comment">// Step 3: Middleware to parse JSON request bodies</span>
app.use(express.json());

<span class="hljs-comment">// Step 4: Mount auth routes</span>
<span class="hljs-comment">// All auth-related routes will start with /api/auth</span>
app.use(<span class="hljs-string">"/api/auth"</span>, <span class="hljs-built_in">require</span>(<span class="hljs-string">"./routes/auth"</span>));

<span class="hljs-comment">// Step 5: Default route to test server</span>
app.get(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">"Hello World! Your server is working 🚀"</span>);
});

<span class="hljs-comment">// Step 6: Start server on PORT from .env or default 5000</span>
<span class="hljs-keyword">const</span> PORT = process.env.PORT || <span class="hljs-number">5000</span>;
app.listen(PORT, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server running on http://localhost:<span class="hljs-subst">${PORT}</span>`</span>);
});
</code></pre>
<ul>
<li><p><strong>Load environment variables:</strong> Using <code>dotenv</code> to keep secrets and configuration separate from code.</p>
</li>
<li><p><strong>Connect to MongoDB:</strong> Calls <code>connectDB()</code> from <code>config/db.js</code>.</p>
</li>
<li><p><strong>Middleware:</strong> <code>express.json()</code> allows Express to parse JSON request bodies.</p>
</li>
<li><p><strong>Mount routes:</strong> <code>app.use("/api/auth", ...)</code> registers all authentication routes.</p>
</li>
<li><p><strong>Default route:</strong> A simple GET endpoint to verify server is running.</p>
</li>
<li><p><strong>Start server:</strong> <code>app.listen</code> starts listening on the configured port.</p>
</li>
</ul>
<h3 id="heading-4-how-to-test-your-api">4. How to Test Your API</h3>
<p>In this section, you’ll learn how to test your JWT authentication API using tools like Postman or any HTTP client.</p>
<p>Before testing, make sure your server is running. If it’s not running, open a terminal and run:</p>
<pre><code class="lang-javascript">npm run dev
</code></pre>
<p>or</p>
<pre><code class="lang-javascript">node server.js
</code></pre>
<p>This will start your server on the port defined in <code>.env</code> (default <code>5000</code>).</p>
<p>Make sure your MongoDB is running. If using local MongoDB, start it with:</p>
<pre><code class="lang-javascript">mongod
</code></pre>
<p>or ensure your MongoDB service is active.</p>
<p>Always check the terminal for any errors. If the server or database fails to start, your API requests will not work.</p>
<h4 id="heading-register-a-user">Register a User</h4>
<p>Request:</p>
<pre><code class="lang-javascript">POST http:<span class="hljs-comment">//localhost:5000/api/auth/register</span>
Content-Type: application/json

{
  <span class="hljs-string">"name"</span>: <span class="hljs-string">"sumit"</span>,
  <span class="hljs-string">"email"</span>: <span class="hljs-string">"sumit@example.com"</span>,
  <span class="hljs-string">"password"</span>: <span class="hljs-string">"mypassword"</span>
}
</code></pre>
<p>Response:</p>
<pre><code class="lang-javascript">{
  <span class="hljs-string">"message"</span>: <span class="hljs-string">"User registered successfully!"</span>
}
</code></pre>
<p>This sends a POST request to <code>http://localhost:5000/api/auth/register</code> with user details. If successful, you get a confirmation message.</p>
<h4 id="heading-login">Login</h4>
<p>Request:</p>
<pre><code class="lang-javascript">POST http:<span class="hljs-comment">//localhost:5000/api/auth/login</span>
Content-Type: application/json

{
  <span class="hljs-string">"email"</span>: <span class="hljs-string">"sumit@example.com"</span>,
  <span class="hljs-string">"password"</span>: <span class="hljs-string">"mypassword"</span>
}
</code></pre>
<p>Response:</p>
<pre><code class="lang-javascript">{
  <span class="hljs-string">"message"</span>: <span class="hljs-string">"Login successful!"</span>,
  <span class="hljs-string">"token"</span>: <span class="hljs-string">"&lt;JWT_TOKEN&gt;"</span>
}
</code></pre>
<p>This sends a POST request to <code>http://localhost:5000/api/auth/login</code> with email and password. If the credentials are correct, you receive a JWT to access protected routes.</p>
<h4 id="heading-access-protected-route">Access Protected Route</h4>
<p>Request:</p>
<pre><code class="lang-javascript">GET http:<span class="hljs-comment">//localhost:5000/api/auth/profile</span>
Authorization: Bearer &lt;JWT_TOKEN&gt;
</code></pre>
<p>Response:</p>
<pre><code class="lang-javascript">{
  <span class="hljs-string">"message"</span>: <span class="hljs-string">"Welcome to your profile!"</span>,
  <span class="hljs-string">"user"</span>: {
    <span class="hljs-string">"id"</span>: <span class="hljs-string">"..."</span>,
    <span class="hljs-string">"email"</span>: <span class="hljs-string">"sumit@example.com"</span>,
    <span class="hljs-string">"iat"</span>: ...,
    <span class="hljs-string">"exp"</span>: ...
  }
}
</code></pre>
<p>This sends the JWT in the <code>Authorization</code> header using the <code>Bearer</code> scheme.</p>
<ul>
<li><p>Only valid tokens will allow access to this protected route.</p>
</li>
<li><p><code>iat</code> and <code>exp</code> indicate issued-at and expiry time of the token.</p>
</li>
</ul>
<p><strong>Note:</strong> Always include <code>Authorization: Bearer &lt;token&gt;</code> for protected routes.</p>
<h2 id="heading-summary">Summary</h2>
<p>This article gave you a comprehensive overview of JSON Web Tokens (JWTs) and their role in web authentication. It explained the stateless nature of HTTP, the need for tokens, and compares classic session tokens with JWTs.</p>
<p>We covered JWT structure, security mechanisms, and practical implementation using Node.js, Express, and MongoDB. We also discussed security considerations, token management, and how to test a JWT authentication API.</p>
<h3 id="heading-heres-a-summary-of-the-key-points">Here’s a Summary of the Key Points:</h3>
<ol>
<li><p><strong>What is JWT?</strong></p>
<ul>
<li><p>JWT is a JSON-based open standard for securely representing claims between two parties, defined by RFC 7519.</p>
</li>
<li><p>Widely used for authorization in modern web applications and microservice architectures.</p>
</li>
<li><p>Alternative to session tokens for maintaining user state.</p>
</li>
</ul>
</li>
<li><p><strong>Stateless Nature of HTTP</strong></p>
<ul>
<li><p>HTTP does not retain information between requests, requiring each request to carry necessary data.</p>
</li>
<li><p>Tokens (session or JWT) are used to maintain user sessions in dynamic web applications.</p>
</li>
</ul>
</li>
<li><p><strong>Session Tokens</strong></p>
<ul>
<li><p>Classic approach where the server creates and stores a session ID, typically in cookies.</p>
</li>
<li><p>Works well for single-server setups but requires shared storage (for example, Redis) in multi-server environments.</p>
</li>
<li><p>Vulnerable if the shared cache goes down.</p>
</li>
</ul>
</li>
<li><p><strong>JWT: The Modern Solution</strong></p>
<ul>
<li><p>Server sends a signed JSON token to the client, which stores and sends it with each request.</p>
</li>
<li><p>No server-side storage required – all user info is in the token.</p>
</li>
<li><p>Signature ensures validity and integrity.</p>
</li>
</ul>
</li>
<li><p><strong>JWT Structure</strong></p>
<ul>
<li><p>Three parts: Header, Payload, Signature (separated by dots).</p>
</li>
<li><p>Header and payload are Base64 encoded JSON objects. Signature is a hash using a secret key.</p>
</li>
<li><p>Base64 encoding is for convenience, not security.</p>
</li>
</ul>
</li>
<li><p><strong>Decoding JWTs</strong></p>
<ul>
<li><p>Tools like <a target="_blank" href="https://jwt.io/">jwt.io</a> can decode JWTs to show header, payload, and signature.</p>
</li>
<li><p>Sensitive data should not be stored in JWTs, as payload is publicly readable.</p>
</li>
</ul>
</li>
<li><p><strong>JWT Security</strong></p>
<ul>
<li><p>Signature is created using a secret key and cryptographic algorithm.</p>
</li>
<li><p>Server verifies token integrity using the secret key.</p>
</li>
<li><p>JWTs identify users but do not act as passwords.</p>
</li>
</ul>
</li>
<li><p><strong>Security Considerations &amp; Token Management</strong></p>
<ul>
<li><p>If a JWT is compromised, the attacker can impersonate the user until the token expires.</p>
</li>
<li><p>JWTs cannot be directly revoked; blacklisting is used to invalidate compromised tokens.</p>
</li>
<li><p>Session tokens can be invalidated by the server.</p>
</li>
</ul>
</li>
<li><p><strong>JWTs in Different Languages</strong></p>
<ul>
<li><p>JWTs are language-agnostic and can be implemented in Node.js, PHP, and other languages.</p>
</li>
<li><p>Useful for authentication and representing any kind of identity.</p>
</li>
</ul>
</li>
<li><p><strong>Practical Implementation: JWT Authentication with Express + MongoDB</strong></p>
<ul>
<li><p>Step-by-step guide to building a JWT authentication system:</p>
<ul>
<li><p>Project setup and dependencies</p>
</li>
<li><p>Folder structure</p>
</li>
<li><p>Express server initialization</p>
</li>
<li><p>MongoDB connection</p>
</li>
<li><p>User model creation</p>
</li>
<li><p>Password hashing and JWT services</p>
</li>
<li><p>Auth controller and middleware</p>
</li>
<li><p>Auth routes</p>
</li>
<li><p>Main server file</p>
</li>
<li><p>API testing instructions</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Testing the API</strong></p>
<ul>
<li><p>Instructions for registering users, logging in, and accessing protected routes using tools like Postman.</p>
</li>
<li><p>Example requests and responses provided.</p>
</li>
</ul>
</li>
<li><p><strong>Summary &amp; Final Words</strong></p>
<ul>
<li><p>JWTs are secure, stateless, and widely used for authorization.</p>
</li>
<li><p>Security depends on safe token storage and proper management.</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-final-words">Final Words</h2>
<p>You can find all the source code from this tutorial in <a target="_blank" href="https://github.com/logicbaselabs/jwt-auth-demo">this GitHub repository</a>. If it helped you in any way, consider giving it a star to show your support!</p>
<p>Also, if you found the information here valuable, feel free to share it with others who might benefit from it. I’d really appreciate your thoughts – mention me on X <a target="_blank" href="https://x.com/sumit_analyzen">@sumit_analyzen</a> or on Facebook <a target="_blank" href="https://facebook.com/sumit.analyzen">@sumit.analyzen</a>, <a target="_blank" href="https://youtube.com/@logicBaseLabs">watch my coding tutorials</a>, or simply <a target="_blank" href="https://www.linkedin.com/in/sumitanalyzen/">connect with me</a> on LinkedIn.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Integrate Facial Recognition Authentication in a Social App with Face API ]]>
                </title>
                <description>
                    <![CDATA[ Social applications have evolved over the years, and there is a major need for secure methods to authenticate users' identities. Integrating multifactor authentication capabilities into applications is crucial for strengthening their integrity. In so... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/integrate-facial-recognition-authentication-in-a-social-application/</link>
                <guid isPermaLink="false">68d20d7a6bd072175081e6b2</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ facial recognition ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwatobi ]]>
                </dc:creator>
                <pubDate>Tue, 23 Sep 2025 03:01:14 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758208687476/3ca6b95d-55c8-4bb6-a4aa-580409e1608f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Social applications have evolved over the years, and there is a major need for secure methods to authenticate users' identities.</p>
<p>Integrating multifactor authentication capabilities into applications is crucial for strengthening their integrity. In social apps, authentication mechanisms eliminate unwanted access to personal information between two parties. Facial authentication is not entirely new, as most devices have it built-in as security measure. It offers stronger protection compared to many traditional methods, especially against risks like phishing, brute-force attacks, and account hacking.</p>
<h2 id="heading-outline">Outline</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-to-expect">What to expect</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-a-brief-intro-to-the-face-api-tool">A Brief Intro to the Face API tool</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-setup">Project Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-demo-project-integrating-facial-recognition-and-authentication">Demo Project: Integrating Facial Recognition and Authentication</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-additional-information-and-tips">Additional Information and Tips</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-to-expect">What to Expect</h2>
<p>In this article, I’ll walk you through creating a multi-factor authentication system for a chat application powered by <a target="_blank" href="https://getstream.io">Stream.io</a>, and ensuring efficient user face ID authentication to allow only authorized access to your app. I will illustrate all these with relevant code examples.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Here are the necessary prerequisites to follow along with this tutorial:</p>
<ul>
<li><p>Intermediate knowledge of Node.js/Express for the backend aspect</p>
</li>
<li><p>Knowledge of React for the frontend aspect</p>
</li>
<li><p><a target="_blank" href="https://getstream.io">Stream.io</a> API key</p>
</li>
</ul>
<p>Before we get started, we’ll briefly highlight the facial authentication tool of choice: <a target="_blank" href="https://justadudewhohacks.github.io/face-api.js/docs/index.html">Face-Api.js</a>.</p>
<h2 id="heading-a-brief-intro-to-the-face-api-tool">A Brief Intro to the Face API tool</h2>
<p>Face-Api.js is a facial recognition package designed for integration with JavaScript-powered applications. It was built on top of the Tensor flow library and provides extensive facial detection based on machine learning models and abstract calculations.</p>
<p>In addition to all these features, it's friendly to use and can also be used locally with its predefined models. Here is a link to its <a target="_blank" href="https://justadudewhohacks.github.io/face-api.js/docs/index.html">documentation page</a>, which provides relevant code examples.</p>
<p>It provides features such as face detection, face capture, and face match, which use the <a target="_blank" href="https://en.wikipedia.org/wiki/Euclidean_algorithm">Euclidean algorithm</a> to make precise distinctions. We'll now set it up alongside our chat application project in the next section.</p>
<h2 id="heading-project-setup">Project Setup</h2>
<p>As mentioned earlier, this is a full-stack project containing both the frontend and the backend aspects. In this section, we’ll set up both code bases before proceeding to the demo project section.</p>
<h3 id="heading-frontend">Frontend</h3>
<p>We will power the application using the Vite framework for the frontend.</p>
<pre><code class="lang-javascript">npm create vite@latest
</code></pre>
<p>After creating the React application, install face-api.js with this command:</p>
<pre><code class="lang-javascript">npm i face-api.js
</code></pre>
<p>This will install the <code>face</code> package and the required dependencies. You can then install Stream’s powered chat SDK, which will form the main crux of the project.</p>
<pre><code class="lang-javascript">npm i stream-chat stream-chat-react
</code></pre>
<p>After successful completion, we are finally done with the project structure scaffold. To aid ease of local testing of our frontend application, we will have to host the face models needed by the Face package locally. Here is a <a target="_blank" href="https://github.com/justadudewhohacks/face-api.js-models">link</a> to the models. Kindly copy the model's folder and paste it into the public folder in the code project. Next, we’ll set up our backend project.</p>
<h3 id="heading-backend">Backend</h3>
<p>The backend is built to store user details and ensure user authentication before accessing the chat application. MongoDB will be the database of choice, and we will use the Express.js library as the backend API development environment of choice. For the ease of setup, kindly clone this <a target="_blank" href="https://github.com/oluwatobi2001/stream-backend.git">code-base</a> and install it on the local PC. It comes preloaded with the necessary installation files. To further enjoy a seamless backend experience, you can utilize the MongoDB <a target="_blank" href="https://www.mongodb.com/products/platform/atlas-database">Atlas</a> option as the database for storing user details. With that, we will now begin the code project in the next section.</p>
<h2 id="heading-demo-project-integrating-facial-recognition-and-authentication">Demo Project: Integrating Facial Recognition and Authentication</h2>
<p>In this section, we will walk through setting up an authentication page on the frontend where a user can register their details, username, email, and password on the registration page. They are also obliged to take a snapshot of their face, and the face API will be called to detect a face in the image. They won't be allowed to proceed beyond this until it is successful.</p>
<p>Thereafter, the image <code>faceDescriptor</code> function is called, which generates a unique face description vector value of the user’s face based on the machine learning models provided. These values are securely stored in the MongoDB database via the Express.js backend after successfully registering. The application is coupled to a multifactor authentication system, which has both the password based authentication and the facial authentication mechanisms.</p>
<p>When the first hurdle (password authentication) is completed, the user is then required to take a face match, comparing it with the user's face descriptor stored from the registration page. The comparison is achieved using the Euclidean algorithmic comparison based on the threshold we provide. If it meets the threshold, the face is said to be matched, and the user gets access to the chat application; else, the user is denied access to the Stream.io-powered chat application. Relevant source code snippets highlighting these steps will be provided concurrently with images.</p>
<p>We’ll begin by building a defunct registration page for our chat application using React, of course. We will begin by importing and initializing the necessary packages.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, {useState, useRef, useEffect} <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> faceapi <span class="hljs-keyword">from</span> <span class="hljs-string">'face-api.js'</span>
<span class="hljs-keyword">import</span> {useNavigate} <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-keyword">const</span> Register =<span class="hljs-function">()=&gt;</span> {

    <span class="hljs-keyword">const</span> navigate= useNavigate(<span class="hljs-string">"/"</span>)
    <span class="hljs-keyword">const</span> userRef = useRef();
    <span class="hljs-keyword">const</span> passwordRef= useRef();
    <span class="hljs-keyword">const</span> emailRef = useRef();
    <span class="hljs-keyword">const</span> FullRef = useRef()
</code></pre>
<p>In the code snippet above, we imported useful React hooks and initialized our installed <code>Face-api.js</code> tool. <a target="_blank" href="https://www.npmjs.com/package/axios">Axios</a> will serve as our API request tool of choice for this project. The <code>useRef</code> hook will be used to track the user inputs. We then defined the register function and initialized the various <code>useRef</code> hooks for the various input fields to be inputted.</p>
<pre><code class="lang-javascript">

    useEffect(<span class="hljs-function">()=&gt;</span> {
<span class="hljs-keyword">const</span> loadModels =<span class="hljs-keyword">async</span>() =&gt; {
<span class="hljs-keyword">await</span> faceapi.nets.tinyFaceDetector.loadFromUri(<span class="hljs-string">'/models'</span>);
<span class="hljs-keyword">await</span> faceapi.nets.faceLandmark68Net.loadFromUri(<span class="hljs-string">'/models'</span>);
<span class="hljs-keyword">await</span> faceapi.nets.faceRecognitionNet.loadFromUri(<span class="hljs-string">'/models'</span>);
<span class="hljs-keyword">await</span> faceapi.nets.faceExpressionNet.loadFromUri(<span class="hljs-string">'/models'</span>);
<span class="hljs-keyword">await</span> faceapi.nets.tinyFaceDetector.loadFromUri(<span class="hljs-string">'/models'</span>);
setModelIsLoaded(<span class="hljs-literal">true</span>);
                startVideo();
}
  loadModels()  }, [])
</code></pre>
<p>In the code above, the <code>useEffect</code> hook is called to ensure that the various locally stored <code>face-api</code> models are initialized and active in our application. The models are stored in the <code>models</code> sub-folder within the <code>public</code> folder. Going forward, after initializing our models, we will now set up our camcorder feature on our webpage.</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">const</span> [faceDetected, setFaceDetected] = useState(<span class="hljs-literal">false</span>);


        <span class="hljs-comment">// Start video feed</span>
        <span class="hljs-keyword">const</span> startVideo = <span class="hljs-function">() =&gt;</span> {
            navigator.mediaDevices
                .getUserMedia({ <span class="hljs-attr">video</span>: <span class="hljs-literal">true</span> })
                .then(<span class="hljs-function">(<span class="hljs-params">stream</span>) =&gt;</span> {
                    videoRef.current.srcObject = stream;
                })
                .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error accessing webcam: "</span>, err));
        };
        <span class="hljs-keyword">const</span> captureSnapshot = <span class="hljs-keyword">async</span> () =&gt; {
            <span class="hljs-keyword">const</span> canvas = snapshotRef.current;
            <span class="hljs-keyword">const</span> context = canvas.getContext(<span class="hljs-string">'2d'</span>);
            context.drawImage(videoRef.current, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height);
            <span class="hljs-keyword">const</span> dataUrl = canvas.toDataURL(<span class="hljs-string">'image/jpeg'</span>);
            setSnapshot(dataUrl);

            <span class="hljs-comment">// Generate the face descriptor (128-dimensional vector)</span>
            <span class="hljs-keyword">const</span> detection = <span class="hljs-keyword">await</span> faceapi
                .detectSingleFace(canvas, <span class="hljs-keyword">new</span> faceapi.TinyFaceDetectorOptions())
                .withFaceLandmarks()
                .withFaceDescriptor();

            <span class="hljs-keyword">if</span> (detection) {
                <span class="hljs-keyword">const</span> newDescriptor = detection.descriptor;
                setDescriptionValue(newDescriptor)
                <span class="hljs-built_in">console</span>.log( newDescriptor);
               setSubmitDisabled(<span class="hljs-literal">false</span>)
                stopVid()
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"No face detected in snapshot"</span>);
            }
        };
    <span class="hljs-keyword">const</span> stopVid =<span class="hljs-function">() =&gt;</span> {

        navigator.mediaDevices
                .getUserMedia({ <span class="hljs-attr">video</span>: <span class="hljs-literal">false</span> })
                <span class="hljs-keyword">const</span> stream = videoRef?.current?.srcObject;
        <span class="hljs-keyword">if</span> (stream) {
            stream.getTracks().forEach(<span class="hljs-function"><span class="hljs-params">track</span> =&gt;</span> {track.stop()})
            videoRef.current.srcObject = <span class="hljs-literal">null</span>;
            setCameraActive(<span class="hljs-literal">false</span>)
        }
    }
        <span class="hljs-comment">// Detect face in the video stream</span>
        <span class="hljs-keyword">const</span> handleVideoPlay = <span class="hljs-keyword">async</span> () =&gt; {
            <span class="hljs-keyword">const</span> video = videoRef.current;
            <span class="hljs-keyword">const</span> canvas = canvasRef.current;

            <span class="hljs-keyword">const</span> displaySize = { <span class="hljs-attr">width</span>: video.width, <span class="hljs-attr">height</span>: video.height };
            faceapi.matchDimensions(canvas, displaySize);

            <span class="hljs-built_in">setInterval</span>(<span class="hljs-keyword">async</span> () =&gt; {
                <span class="hljs-keyword">if</span> (!cameraActive) <span class="hljs-keyword">return</span> ;
                <span class="hljs-keyword">const</span> detections = <span class="hljs-keyword">await</span> faceapi.detectAllFaces(
                    video,
                    <span class="hljs-keyword">new</span> faceapi.TinyFaceDetectorOptions()
                );

                <span class="hljs-keyword">const</span> resizedDetections = faceapi.resizeResults(detections, displaySize);

                canvas.getContext(<span class="hljs-string">'2d'</span>).clearRect(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height);
                faceapi.draw.drawDetections(canvas, resizedDetections);
                    <span class="hljs-keyword">const</span> detected = detections.length &gt; <span class="hljs-number">0</span>;
                 <span class="hljs-keyword">if</span> (detected &amp;&amp; !faceDetected) {
                captureSnapshot();  <span class="hljs-comment">// Capture the snapshot as soon as a face is detected</span>
            }

                setFaceDetected(detections.length &gt; <span class="hljs-number">0</span>);
            }, <span class="hljs-number">100</span>);
        };
</code></pre>
<p>In the code above, we begin by defining a <code>useState</code> array when the user’s face is detected during the sign-up process. Thereafter, the function to trigger the browser camcorder is then activated. With this on, we can then trigger the <code>handlePlayFunction</code> in the code. This function monitors facial detection as highlighted by the face models already initialized. The <code>stopVid</code> function is also triggered when the user’s facial detection has been successfully completed.</p>
<p>In this section, we also activated the browser camcorder tool in our application to provide us with real time video. The <code>CaptureSnapshot</code> function helps to obtain a snapshot from the current video being showcased.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> RegSubmit = <span class="hljs-keyword">async</span> (e) =&gt; {
  e.preventDefault();
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"hello"</span>);

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.post(BACKEND_URL, {
      <span class="hljs-attr">username</span>: userRef.current.value,
      <span class="hljs-attr">email</span>: emailRef.current.value,
      <span class="hljs-attr">FullName</span>: FullRef.current.value,
      <span class="hljs-attr">password</span>: passwordRef.current.value,
      <span class="hljs-attr">faceDescriptor</span>: descriptionValue,
    });

    <span class="hljs-built_in">console</span>.log(res.data);
    setError(<span class="hljs-literal">false</span>);
    navigate(<span class="hljs-string">"/login"</span>);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"help"</span>);
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.error(err);
    setError(<span class="hljs-literal">true</span>);
  }
};
</code></pre>
<p>With all the values obtained, the <code>regSubmit</code> function is then defined. When executed, it stores the provided user details with the face description object on our backend server which can then be accessed in the next section for authentication.</p>
<p>Below is the full registration code.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState, useRef, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> faceapi <span class="hljs-keyword">from</span> <span class="hljs-string">'face-api.js'</span>;
<span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-keyword">const</span> Register = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> navigate = useNavigate(<span class="hljs-string">"/"</span>);

  <span class="hljs-keyword">const</span> userRef = useRef();
  <span class="hljs-keyword">const</span> passwordRef = useRef();
  <span class="hljs-keyword">const</span> emailRef = useRef();
  <span class="hljs-keyword">const</span> FullRef = useRef();
  <span class="hljs-keyword">const</span> snapshotRef = useRef(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> videoRef = useRef(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> canvasRef = useRef(<span class="hljs-literal">null</span>);

  <span class="hljs-keyword">const</span> [modelIsLoaded, setModelIsLoaded] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [detections, setDetections] = useState([]);
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [snapshot, setSnapshot] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [cameraActive, setCameraActive] = useState(<span class="hljs-literal">true</span>);
  <span class="hljs-keyword">const</span> [submitDisabled, setSubmitDisabled] = useState(<span class="hljs-literal">true</span>);
  <span class="hljs-keyword">const</span> [descriptionValue, setDescriptionValue] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [faceDetected, setFaceDetected] = useState(<span class="hljs-literal">false</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> loadModels = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">await</span> faceapi.nets.tinyFaceDetector.loadFromUri(<span class="hljs-string">'/models'</span>);
      <span class="hljs-keyword">await</span> faceapi.nets.faceLandmark68Net.loadFromUri(<span class="hljs-string">'/models'</span>);
      <span class="hljs-keyword">await</span> faceapi.nets.faceRecognitionNet.loadFromUri(<span class="hljs-string">'/models'</span>);
      <span class="hljs-keyword">await</span> faceapi.nets.faceExpressionNet.loadFromUri(<span class="hljs-string">'/models'</span>);
      <span class="hljs-keyword">await</span> faceapi.nets.tinyFaceDetector.loadFromUri(<span class="hljs-string">'/models'</span>);
      setModelIsLoaded(<span class="hljs-literal">true</span>);
      startVideo();
    };

    loadModels();
  }, []);

  <span class="hljs-keyword">const</span> RegSubmit = <span class="hljs-keyword">async</span> (e) =&gt; {
    e.preventDefault();
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"hello"</span>);

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">'http://localhost:5000/v1/users'</span>, {
        <span class="hljs-attr">username</span>: userRef.current.value,
        <span class="hljs-attr">email</span>: emailRef.current.value,
        <span class="hljs-attr">FullName</span>: FullRef.current.value,
        <span class="hljs-attr">password</span>: passwordRef.current.value,
        <span class="hljs-attr">faceDescriptor</span>: descriptionValue
      });

      <span class="hljs-built_in">console</span>.log(res.data);
      setError(<span class="hljs-literal">false</span>);
      navigate(<span class="hljs-string">"/login"</span>);
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"help"</span>);
    } <span class="hljs-keyword">catch</span> (err) {
      <span class="hljs-built_in">console</span>.log(err);
      setError(<span class="hljs-literal">true</span>);
    }
  };

  <span class="hljs-keyword">const</span> startVideo = <span class="hljs-function">() =&gt;</span> {
    navigator.mediaDevices
      .getUserMedia({ <span class="hljs-attr">video</span>: <span class="hljs-literal">true</span> })
      .then(<span class="hljs-function">(<span class="hljs-params">stream</span>) =&gt;</span> {
        videoRef.current.srcObject = stream;
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error accessing webcam: "</span>, err));
  };

  <span class="hljs-keyword">const</span> stopVid = <span class="hljs-function">() =&gt;</span> {
    navigator.mediaDevices.getUserMedia({ <span class="hljs-attr">video</span>: <span class="hljs-literal">false</span> });
    <span class="hljs-keyword">const</span> stream = videoRef?.current?.srcObject;
    <span class="hljs-keyword">if</span> (stream) {
      stream.getTracks().forEach(<span class="hljs-function">(<span class="hljs-params">track</span>) =&gt;</span> track.stop());
      videoRef.current.srcObject = <span class="hljs-literal">null</span>;
      setCameraActive(<span class="hljs-literal">false</span>);
    }
  };

  <span class="hljs-keyword">const</span> captureSnapshot = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> canvas = snapshotRef.current;
    <span class="hljs-keyword">const</span> context = canvas.getContext(<span class="hljs-string">'2d'</span>);
    context.drawImage(videoRef.current, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height);
    <span class="hljs-keyword">const</span> dataUrl = canvas.toDataURL(<span class="hljs-string">'image/jpeg'</span>);
    setSnapshot(dataUrl);

    <span class="hljs-keyword">const</span> detection = <span class="hljs-keyword">await</span> faceapi
      .detectSingleFace(canvas, <span class="hljs-keyword">new</span> faceapi.TinyFaceDetectorOptions())
      .withFaceLandmarks()
      .withFaceDescriptor();

    <span class="hljs-keyword">if</span> (detection) {
      <span class="hljs-keyword">const</span> newDescriptor = detection.descriptor;
      setDescriptionValue(newDescriptor);
      <span class="hljs-built_in">console</span>.log(newDescriptor);
      setSubmitDisabled(<span class="hljs-literal">false</span>);
      stopVid();

      <span class="hljs-keyword">if</span> (storedDescriptor &amp;&amp; isMatchingFace(storedDescriptor, newDescriptor)) {
        <span class="hljs-built_in">setInterval</span>(alert(<span class="hljs-string">"face matched"</span>), <span class="hljs-number">100</span>);
      } <span class="hljs-keyword">else</span> {
        alert(<span class="hljs-string">"No Match Found!"</span>);
      }
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"No face detected in snapshot"</span>);
    }
  };

  <span class="hljs-keyword">const</span> handleVideoPlay = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> video = videoRef.current;
    <span class="hljs-keyword">const</span> canvas = canvasRef.current;
    <span class="hljs-keyword">const</span> displaySize = { <span class="hljs-attr">width</span>: video.width, <span class="hljs-attr">height</span>: video.height };
    faceapi.matchDimensions(canvas, displaySize);

    <span class="hljs-built_in">setInterval</span>(<span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">if</span> (!cameraActive) <span class="hljs-keyword">return</span>;

      <span class="hljs-keyword">const</span> detections = <span class="hljs-keyword">await</span> faceapi.detectAllFaces(
        video,
        <span class="hljs-keyword">new</span> faceapi.TinyFaceDetectorOptions()
      );

      <span class="hljs-keyword">const</span> resizedDetections = faceapi.resizeResults(detections, displaySize);
      canvas.getContext(<span class="hljs-string">'2d'</span>).clearRect(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height);
      faceapi.draw.drawDetections(canvas, resizedDetections);

      <span class="hljs-keyword">const</span> detected = detections.length &gt; <span class="hljs-number">0</span>;
      <span class="hljs-keyword">if</span> (detected &amp;&amp; !faceDetected) {
        captureSnapshot();
      }

      setFaceDetected(detected);
    }, <span class="hljs-number">100</span>);
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col w-full h-screen justify-center"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col mb-2 w-full"</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{RegSubmit}</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col mx-auto mb-5"</span>&gt;</span>Registration Page<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col mb-2 w-[50%] mx-auto items-center"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
              <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
              <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Email"</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"w-full rounded-2xl h-[50px] border-2 p-2 mb-2 border-gray-900"</span>
              <span class="hljs-attr">required</span>
              <span class="hljs-attr">ref</span>=<span class="hljs-string">{emailRef}</span>
            /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
              <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
              <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Username"</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"w-full rounded-2xl h-[50px] border-2 p-2 mb-2 border-gray-900"</span>
              <span class="hljs-attr">required</span>
              <span class="hljs-attr">ref</span>=<span class="hljs-string">{userRef}</span>
            /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
              <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
              <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Full Name"</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"w-full rounded-2xl h-[50px] border-2 p-2 mb-2 border-gray-900"</span>
              <span class="hljs-attr">required</span>
              <span class="hljs-attr">ref</span>=<span class="hljs-string">{FullRef}</span>
            /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
              <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>
              <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Password"</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"w-full rounded-2xl h-[50px] border-2 p-2 mb-2 border-gray-900"</span>
              <span class="hljs-attr">required</span>
              <span class="hljs-attr">ref</span>=<span class="hljs-string">{passwordRef}</span>
            /&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              {!modelIsLoaded &amp;&amp; cameraActive &amp;&amp; !descriptionValue ? (
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Loading<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
              ) : (
                <span class="hljs-tag">&lt;&gt;</span>
                  {!descriptionValue &amp;&amp; (
                    <span class="hljs-tag">&lt;&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">video</span>
                        <span class="hljs-attr">ref</span>=<span class="hljs-string">{videoRef}</span>
                        <span class="hljs-attr">width</span>=<span class="hljs-string">"200"</span>
                        <span class="hljs-attr">height</span>=<span class="hljs-string">"160"</span>
                        <span class="hljs-attr">onPlay</span>=<span class="hljs-string">{handleVideoPlay}</span>
                        <span class="hljs-attr">autoPlay</span>
                        <span class="hljs-attr">muted</span>
                      /&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">canvas</span>
                        <span class="hljs-attr">ref</span>=<span class="hljs-string">{canvasRef}</span>
                        <span class="hljs-attr">width</span>=<span class="hljs-string">"200"</span>
                        <span class="hljs-attr">height</span>=<span class="hljs-string">"160"</span>
                        <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">position:</span> '<span class="hljs-attr">absolute</span>', <span class="hljs-attr">top:</span> <span class="hljs-attr">0</span>, <span class="hljs-attr">left:</span> <span class="hljs-attr">0</span> }}
                      /&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
                        {faceDetected ? (
                          <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">color:</span> '<span class="hljs-attr">green</span>' }}&gt;</span>Face Detected<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                        ) : (
                          <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">color:</span> '<span class="hljs-attr">red</span>' }}&gt;</span>No Face Detected<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                        )}
                      <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">canvas</span>
                        <span class="hljs-attr">ref</span>=<span class="hljs-string">{snapshotRef}</span>
                        <span class="hljs-attr">width</span>=<span class="hljs-string">"480"</span>
                        <span class="hljs-attr">height</span>=<span class="hljs-string">"360"</span>
                        <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">display:</span> '<span class="hljs-attr">none</span>' }}
                      /&gt;</span>
                    <span class="hljs-tag">&lt;/&gt;</span>
                  )}
                <span class="hljs-tag">&lt;/&gt;</span>
              )}

              {snapshot &amp;&amp; (
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginTop:</span> '<span class="hljs-attr">20px</span>' }}&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">h4</span>&gt;</span>Face Snapshot:<span class="hljs-tag">&lt;/<span class="hljs-name">h4</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
                    <span class="hljs-attr">src</span>=<span class="hljs-string">{snapshot}</span>
                    <span class="hljs-attr">alt</span>=<span class="hljs-string">"Face Snapshot"</span>
                    <span class="hljs-attr">width</span>=<span class="hljs-string">"200"</span>
                    <span class="hljs-attr">height</span>=<span class="hljs-string">"160"</span>
                  /&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
              )}
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-2"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{stopVid}</span>&gt;</span>
                Stop Video
              <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
              <span class="hljs-attr">disabled</span>=<span class="hljs-string">{submitDisabled}</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"mx-auto mt-4 rounded-2xl cursor-pointer text-white bg-primary w-[80%] lg:w-[50%] h-[40px] text-center items-center justify-center"</span>
              <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>
            &gt;</span>
              Register
            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col mt-1 w-full"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex justify-center"</span>&gt;</span>
              Registered previously?<span class="hljs-symbol">&amp;nbsp;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/login"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-blue-600 underline"</span>&gt;</span>
                Login
              <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

          {error &amp;&amp; (
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-red-600 text-center mt-2"</span>&gt;</span>
              Error while registering, try again
            <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          )}
        <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Register;
</code></pre>
<p>Going forward, we will be working on our multifactor authentication system. In the code below, we will be highlighting the <code>loginSubmit</code> function which will be triggered when the user email and password credentials are provided for logging in to our chat application. The <code>useRef</code> hook is initialized which ensures that the values passed in the input boxes are parsed to the backend via the <code>Axios</code> request tool.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState, useRef, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { Link, useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Login</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> navigate = useNavigate();
  <span class="hljs-keyword">const</span> userRef = useRef();
  <span class="hljs-keyword">const</span> passwordRef = useRef();

  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-literal">false</span>);

  <span class="hljs-keyword">const</span> LoginSubmit = <span class="hljs-keyword">async</span> (e) =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.post(
        <span class="hljs-string">'http://localhost:5000/v1/auth/login'</span>,
        {
          <span class="hljs-attr">email</span>: userRef.current.value,
          <span class="hljs-attr">password</span>: passwordRef.current.value,
        },
        { <span class="hljs-attr">withCredentials</span>: <span class="hljs-literal">true</span> }
      );

      <span class="hljs-built_in">console</span>.log(res?.data);
      setError(<span class="hljs-literal">false</span>);
      navigate(<span class="hljs-string">'/confirm-auth'</span>);
      <span class="hljs-built_in">console</span>.log(res);
    } <span class="hljs-keyword">catch</span> (err) {
      setError(<span class="hljs-literal">true</span>);
      <span class="hljs-built_in">console</span>.log(err);
    }
  };
}
</code></pre>
<p>The full login page code example will be provided <a target="_blank" href="http://github.com/oluwatobi2001/Stream-frontend.git">here</a>. After successfully confirming their identity via the use of the password authentication feature, we can then go on to confirm the user’s identity via the use of the face recognition system.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
<span class="hljs-keyword">import</span> React, { useRef, useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> faceapi <span class="hljs-keyword">from</span> <span class="hljs-string">'face-api.js'</span>;
<span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
</code></pre>
<p>First of all, we will set up the app by importing the necessary dependencies as highlighted in the code snippet above.</p>
<pre><code class="lang-javascript">
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> loadModels = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">await</span> faceapi.nets.tinyFaceDetector.loadFromUri(<span class="hljs-string">'/models'</span>);
      <span class="hljs-keyword">await</span> faceapi.nets.faceLandmark68Net.loadFromUri(<span class="hljs-string">'/models'</span>);
      <span class="hljs-keyword">await</span> faceapi.nets.faceRecognitionNet.loadFromUri(<span class="hljs-string">'/models'</span>);
      <span class="hljs-keyword">await</span> faceapi.nets.faceExpressionNet.loadFromUri(<span class="hljs-string">'/models'</span>);
    };

    loadModels();
  }, []);

  <span class="hljs-keyword">const</span> handleVideoPlay = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> video = videoRef.current;
    <span class="hljs-keyword">const</span> canvas = canvasRef.current;

    <span class="hljs-keyword">const</span> displaySize = { <span class="hljs-attr">width</span>: video.width, <span class="hljs-attr">height</span>: video.height };
    faceapi.matchDimensions(canvas, displaySize);

    <span class="hljs-built_in">setInterval</span>(<span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">if</span> (!cameraActive) <span class="hljs-keyword">return</span>;

      <span class="hljs-keyword">const</span> detections = <span class="hljs-keyword">await</span> faceapi.detectAllFaces(
        video,
        <span class="hljs-keyword">new</span> faceapi.TinyFaceDetectorOptions()
      );

      <span class="hljs-keyword">const</span> resizedDetections = faceapi.resizeResults(detections, displaySize);
      canvas.getContext(<span class="hljs-string">'2d'</span>).clearRect(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height);
      faceapi.draw.drawDetections(canvas, resizedDetections);

      <span class="hljs-keyword">const</span> detected = detections.length &gt; <span class="hljs-number">0</span>;
      <span class="hljs-keyword">if</span> (detected &amp;&amp; !faceDetected) {
        captureSnapshot();
      }

      setFaceDetected(detected);
    }, <span class="hljs-number">100</span>);
  };

  <span class="hljs-keyword">const</span> startVideo = <span class="hljs-function">() =&gt;</span> {
    navigator.mediaDevices
      .getUserMedia({ <span class="hljs-attr">video</span>: <span class="hljs-literal">true</span> })
      .then(<span class="hljs-function">(<span class="hljs-params">stream</span>) =&gt;</span> {
        videoRef.current.srcObject = stream;
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error accessing webcam: "</span>, err));
  };

  <span class="hljs-keyword">const</span> stopVid = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> stream = videoRef.current.srcObject;
    <span class="hljs-keyword">if</span> (stream) {
      stream.getTracks().forEach(<span class="hljs-function">(<span class="hljs-params">track</span>) =&gt;</span> track.stop());
      videoRef.current.srcObject = <span class="hljs-literal">null</span>;
      setCameraActive(<span class="hljs-literal">false</span>);
    }
  };

  <span class="hljs-keyword">const</span> deleteImage = <span class="hljs-function">() =&gt;</span> {
    setSnapshot(<span class="hljs-literal">null</span>);
    setDescriptionValue(<span class="hljs-literal">null</span>);
    setFaceDetected(<span class="hljs-literal">false</span>);
    setCameraActive(<span class="hljs-literal">true</span>);
    startVideo();
  };

  <span class="hljs-keyword">const</span> captureSnapshot = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> canvas = snapshotRef.current;
    <span class="hljs-keyword">const</span> context = canvas.getContext(<span class="hljs-string">'2d'</span>);
    context.drawImage(videoRef.current, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height);

    <span class="hljs-keyword">const</span> dataUrl = canvas.toDataURL(<span class="hljs-string">'image/jpeg'</span>);
    setSnapshot(dataUrl);
    stopVid();

    <span class="hljs-keyword">const</span> detection = <span class="hljs-keyword">await</span> faceapi
      .detectSingleFace(canvas, <span class="hljs-keyword">new</span> faceapi.TinyFaceDetectorOptions())
      .withFaceLandmarks()
      .withFaceDescriptor();

    <span class="hljs-keyword">if</span> (detection) {
      <span class="hljs-keyword">const</span> newDescriptor = detection.descriptor;
      setDescriptionValue(newDescriptor);
      <span class="hljs-built_in">console</span>.log(newDescriptor);
    }
  };
</code></pre>
<p>After initializing all the necessary dependencies, we also imported our models as we did in the registration page to detect the user’s face and then generate a face description. We also allowed for the user to delete the snapshot and retake the image as many times as possible.</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">const</span> FaceAuthenticate = <span class="hljs-keyword">async</span> (e) =&gt; {
    e.preventDefault();

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.post(
        <span class="hljs-string">'http://localhost:5000/v1/auth/face-auth'</span>,
        { <span class="hljs-attr">faceDescriptor</span>: descriptionValue },
        { <span class="hljs-attr">withCredentials</span>: <span class="hljs-literal">true</span> }
      );

      <span class="hljs-built_in">console</span>.log(res?.data);
      navigate(<span class="hljs-string">'/chat'</span>);
    } <span class="hljs-keyword">catch</span> (err) {
      <span class="hljs-built_in">console</span>.log(err);
    }
  };
</code></pre>
<p>After the face descriptor object gets generated, we then sent it to the backend to compare it with the stored face descriptor obtained at the point of registration. If they match, we get redirected to the chat application. Otherwise, an appropriate error message denying us access to the chat application is displayed.</p>
<p>Here is the code to the <code>FaceAuth</code> page:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
<span class="hljs-keyword">import</span> React, { useRef, useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> faceapi <span class="hljs-keyword">from</span> <span class="hljs-string">'face-api.js'</span>;
<span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">const</span> FaceAuth = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> navigate = useNavigate(<span class="hljs-string">"/"</span>);

  <span class="hljs-keyword">const</span> videoRef = useRef(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> canvasRef = useRef(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> snapshotRef = useRef(<span class="hljs-literal">null</span>);

  <span class="hljs-keyword">const</span> [cameraActive, setCameraActive] = useState(<span class="hljs-literal">true</span>);
  <span class="hljs-keyword">const</span> [snapshot, setSnapshot] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [descriptionValue, setDescriptionValue] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [faceDetected, setFaceDetected] = useState(<span class="hljs-literal">false</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> loadModels = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">await</span> faceapi.nets.tinyFaceDetector.loadFromUri(<span class="hljs-string">'/models'</span>);
      <span class="hljs-keyword">await</span> faceapi.nets.faceLandmark68Net.loadFromUri(<span class="hljs-string">'/models'</span>);
      <span class="hljs-keyword">await</span> faceapi.nets.faceRecognitionNet.loadFromUri(<span class="hljs-string">'/models'</span>);
      <span class="hljs-keyword">await</span> faceapi.nets.faceExpressionNet.loadFromUri(<span class="hljs-string">'/models'</span>);
    };

    loadModels();
  }, []);

  <span class="hljs-keyword">const</span> handleVideoPlay = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> video = videoRef.current;
    <span class="hljs-keyword">const</span> canvas = canvasRef.current;

    <span class="hljs-keyword">const</span> displaySize = { <span class="hljs-attr">width</span>: video.width, <span class="hljs-attr">height</span>: video.height };
    faceapi.matchDimensions(canvas, displaySize);

    <span class="hljs-built_in">setInterval</span>(<span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">if</span> (!cameraActive) <span class="hljs-keyword">return</span>;

      <span class="hljs-keyword">const</span> detections = <span class="hljs-keyword">await</span> faceapi.detectAllFaces(
        video,
        <span class="hljs-keyword">new</span> faceapi.TinyFaceDetectorOptions()
      );

      <span class="hljs-keyword">const</span> resizedDetections = faceapi.resizeResults(detections, displaySize);
      canvas.getContext(<span class="hljs-string">'2d'</span>).clearRect(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height);
      faceapi.draw.drawDetections(canvas, resizedDetections);

      <span class="hljs-keyword">const</span> detected = detections.length &gt; <span class="hljs-number">0</span>;
      <span class="hljs-keyword">if</span> (detected &amp;&amp; !faceDetected) {
        captureSnapshot();
      }

      setFaceDetected(detected);
    }, <span class="hljs-number">100</span>);
  };

  <span class="hljs-keyword">const</span> startVideo = <span class="hljs-function">() =&gt;</span> {
    navigator.mediaDevices
      .getUserMedia({ <span class="hljs-attr">video</span>: <span class="hljs-literal">true</span> })
      .then(<span class="hljs-function">(<span class="hljs-params">stream</span>) =&gt;</span> {
        videoRef.current.srcObject = stream;
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error accessing webcam: "</span>, err));
  };

  <span class="hljs-keyword">const</span> stopVid = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> stream = videoRef.current.srcObject;
    <span class="hljs-keyword">if</span> (stream) {
      stream.getTracks().forEach(<span class="hljs-function">(<span class="hljs-params">track</span>) =&gt;</span> track.stop());
      videoRef.current.srcObject = <span class="hljs-literal">null</span>;
      setCameraActive(<span class="hljs-literal">false</span>);
    }
  };

  <span class="hljs-keyword">const</span> deleteImage = <span class="hljs-function">() =&gt;</span> {
    setSnapshot(<span class="hljs-literal">null</span>);
    setDescriptionValue(<span class="hljs-literal">null</span>);
    setFaceDetected(<span class="hljs-literal">false</span>);
    setCameraActive(<span class="hljs-literal">true</span>);
    startVideo();
  };

  <span class="hljs-keyword">const</span> captureSnapshot = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> canvas = snapshotRef.current;
    <span class="hljs-keyword">const</span> context = canvas.getContext(<span class="hljs-string">'2d'</span>);
    context.drawImage(videoRef.current, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height);

    <span class="hljs-keyword">const</span> dataUrl = canvas.toDataURL(<span class="hljs-string">'image/jpeg'</span>);
    setSnapshot(dataUrl);
    stopVid();

    <span class="hljs-keyword">const</span> detection = <span class="hljs-keyword">await</span> faceapi
      .detectSingleFace(canvas, <span class="hljs-keyword">new</span> faceapi.TinyFaceDetectorOptions())
      .withFaceLandmarks()
      .withFaceDescriptor();

    <span class="hljs-keyword">if</span> (detection) {
      <span class="hljs-keyword">const</span> newDescriptor = detection.descriptor;
      setDescriptionValue(newDescriptor);
      <span class="hljs-built_in">console</span>.log(newDescriptor);
    }
  };

  <span class="hljs-keyword">const</span> FaceAuthenticate = <span class="hljs-keyword">async</span> (e) =&gt; {
    e.preventDefault();

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.post(
        <span class="hljs-string">'http://localhost:5000/v1/auth/face-auth'</span>,
        { <span class="hljs-attr">faceDescriptor</span>: descriptionValue },
        { <span class="hljs-attr">withCredentials</span>: <span class="hljs-literal">true</span> }
      );

      <span class="hljs-built_in">console</span>.log(res?.data);
      navigate(<span class="hljs-string">'/chat'</span>);
    } <span class="hljs-keyword">catch</span> (err) {
      <span class="hljs-built_in">console</span>.log(err);
    }
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex w-full h-screen flex-col justify-center"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col mx-auto items-center text-lg font-semibold mb-3"</span>&gt;</span>
          Take a snapshot to confirm your identity
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-center mb-4"</span>&gt;</span>Ensure that the picture is taken in a bright area<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
          <span class="hljs-attr">onClick</span>=<span class="hljs-string">{startVideo}</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">"flex w-[30%] mx-auto text-center items-center justify-center mb-5 h-[40px] bg-blue-600 rounded-md text-white"</span>
        &gt;</span>
          Turn on Webcam
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

        {!snapshot ? (
          <span class="hljs-tag">&lt;&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">video</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"flex mx-auto items-center rounded-md"</span>
              <span class="hljs-attr">ref</span>=<span class="hljs-string">{videoRef}</span>
              <span class="hljs-attr">width</span>=<span class="hljs-string">"240"</span>
              <span class="hljs-attr">height</span>=<span class="hljs-string">"180"</span>
              <span class="hljs-attr">onPlay</span>=<span class="hljs-string">{handleVideoPlay}</span>
              <span class="hljs-attr">autoPlay</span>
              <span class="hljs-attr">muted</span>
            /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">canvas</span>
              <span class="hljs-attr">ref</span>=<span class="hljs-string">{snapshotRef}</span>
              <span class="hljs-attr">width</span>=<span class="hljs-string">"240"</span>
              <span class="hljs-attr">height</span>=<span class="hljs-string">"180"</span>
              <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">position:</span> '<span class="hljs-attr">absolute</span>', <span class="hljs-attr">top:</span> <span class="hljs-attr">0</span>, <span class="hljs-attr">left:</span> <span class="hljs-attr">0</span> }}
            /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{captureSnapshot}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-4 mx-auto block text-sm text-blue-600 underline"</span>&gt;</span>
              Take a snapshot
            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;/&gt;</span>
        ) : (
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex w-full justify-center"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
              <span class="hljs-attr">src</span>=<span class="hljs-string">{snapshot}</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"rounded-lg"</span>
              <span class="hljs-attr">width</span>=<span class="hljs-string">"240"</span>
              <span class="hljs-attr">height</span>=<span class="hljs-string">"180"</span>
              <span class="hljs-attr">alt</span>=<span class="hljs-string">"Face Snapshot"</span>
            /&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        )}

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-row w-full justify-evenly mt-5"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
            <span class="hljs-attr">onClick</span>=<span class="hljs-string">{deleteImage}</span>
            <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-purple-500 text-white p-2 h-[35px] rounded-lg"</span>
          &gt;</span>
            Delete Image
          <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
            <span class="hljs-attr">onClick</span>=<span class="hljs-string">{FaceAuthenticate}</span>
            <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-purple-500 text-white p-2 h-[35px] rounded-lg"</span>
          &gt;</span>
            Upload Image
          <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> FaceAuth;
</code></pre>
<p>Displayed below is how the face authentication page should look like.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcPFsPVo9dymrTmMCyskCszbMf_SdG2n_j5gd7ayT1nQ6jOlhX8a_KFRG51cnqMCxUqFaVgTR2hrdGipmudd9B2TQpNfm4FrFMlYRo7bbu1gtRq1bKB5FmPi4QcbEPTLyDtAPbNEA?key=bLpVfispbJQQ4phtxWLC7w" alt="facial authentication page " width="685" height="512" loading="lazy"></p>
<p>Having set up the frontend, let's head to the backend and configure the registration and login endpoint for our project. The entire code to the backend project can be gotten <a target="_blank" href="http://github.com/oluwatobi2001/stream-backend.git">here</a>. We will only be highlighting the <code>faceAuth</code> backend function in this article.</p>
<p>To verify authentication, we will be using the sessions option instead of the JWT option. Important user information will be stored and accessed in the session cookies attached to the requests and responses to the frontend. Here is the <code>faceAuth</code> function:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> faceAuth = <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-built_in">console</span>.log(req.session);

    <span class="hljs-keyword">const</span> id = req.session.passport?.user;
    <span class="hljs-built_in">console</span>.log(id);


    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findById(id);
    <span class="hljs-built_in">console</span>.log(user);

    <span class="hljs-keyword">if</span> (user == <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">err</span>: <span class="hljs-string">"User not found"</span> });
    }


  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.error(err);
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">err</span>: <span class="hljs-string">"Internal Server Error"</span> });
  }
};
</code></pre>
<p>First, we defined an asynchronous function named <code>faceAuth</code>. We then obtained the unique ID of the user who had successfully scaled over the initial login process from the request session.</p>
<p>To confirm the similarity of the user's stored face descriptor and the picture sent from the frontend, we utilized the matching face function based on the Euclidean algorithm to confirm the user's identity as done below.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> isMatchingFace = <span class="hljs-function">(<span class="hljs-params">descriptor1, descriptor2, threshold = <span class="hljs-number">0.6</span></span>) =&gt;</span> {
  <span class="hljs-comment">// Convert the stored descriptors to Float32Array if they aren't already</span>
  <span class="hljs-keyword">if</span> (!(descriptor1 <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Float32Array</span>)) {
    descriptor1 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Float32Array</span>(<span class="hljs-built_in">Object</span>.values(descriptor1));
  }

  <span class="hljs-keyword">if</span> (!(descriptor2 <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Float32Array</span>)) {
    descriptor2 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Float32Array</span>(<span class="hljs-built_in">Object</span>.values(descriptor2));
  }

  <span class="hljs-keyword">const</span> distance = faceapi.euclideanDistance(descriptor1, descriptor2);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Euclidean Distance:"</span>, distance);

  <span class="hljs-keyword">return</span> distance &lt; threshold;
};
</code></pre>
<p>As stated in the code above, the threshold of similarity of comparison used was 0.6. This is flexible and can be modified to suit the user's preference, as a higher threshold will provide better accuracy overall.<br>If the function returns true, then the user has been successfully authenticated and can then have access to our chat application. Here is the full code snippet.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> faceAuth = <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-built_in">console</span>.log(req.session);

    <span class="hljs-keyword">const</span> id = req.session.passport?.user;
    <span class="hljs-built_in">console</span>.log(id);

    <span class="hljs-keyword">const</span> { faceDescriptor } = req.body;
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findById(id);
    <span class="hljs-built_in">console</span>.log(user);

    <span class="hljs-keyword">if</span> (user == <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">err</span>: <span class="hljs-string">"User not found"</span> });
    }

    <span class="hljs-keyword">const</span> isMatchingFace = <span class="hljs-function">(<span class="hljs-params">descriptor1, descriptor2, threshold = <span class="hljs-number">0.6</span></span>) =&gt;</span> {
      <span class="hljs-comment">// Convert the stored descriptor (object) to a Float32Array</span>
      <span class="hljs-keyword">if</span> (!(descriptor1 <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Float32Array</span>)) {
        descriptor1 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Float32Array</span>(<span class="hljs-built_in">Object</span>.values(descriptor1));
      }

      <span class="hljs-keyword">if</span> (!(descriptor2 <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Float32Array</span>)) {
        descriptor2 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Float32Array</span>(<span class="hljs-built_in">Object</span>.values(descriptor2));
      }

      <span class="hljs-keyword">const</span> distance = faceapi.euclideanDistance(descriptor1, descriptor2);
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Euclidean Distance:"</span>, distance);

      <span class="hljs-keyword">return</span> distance &lt; threshold;
    };

    <span class="hljs-keyword">if</span> (isMatchingFace(faceDescriptor, user.faceDescriptor)) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Face match successful"</span>);
      req.session.mfa = <span class="hljs-literal">true</span>;

      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).json({
        <span class="hljs-attr">msg</span>: <span class="hljs-string">"User authentication was successful. Proceed to the chat app."</span>,
      });
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">msg</span>: <span class="hljs-string">"Face does not match. Access denied."</span> });
    }
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.log(err);
    res.status(<span class="hljs-number">500</span>).json({
      <span class="hljs-attr">err</span>: <span class="hljs-string">"User face couldn't be authenticated. Please try again later"</span>,
    });
  }
};
</code></pre>
<p>With the main hurdle completed, we can then navigate to our application and have a seamless chat experience.</p>
<p>Additionally, as a safety measure, a rate limiter is also in place to minimize the use of brute-force techniques by malicious individuals to gain access to the chat application.</p>
<h2 id="heading-additional-information-and-tips">Additional Information and Tips</h2>
<p>The overall aim of these efforts is to achieve a more scalable and secure method of user validation. The threshold can easily be modified and tweaked to improve application accuracy. Alternatively, the <a target="_blank" href="https://aws.amazon.com/rekognition/">AWS Rekognition</a> tool can sufficiently replace the Face API tool with efficient cloud-powered models. The limitations of facial recognition can also be overcome by exploring biometric authentication, as it’s a known fact that each individual's fingerprint is unique, greatly reducing the risk of user compromise.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>So far, we have walked through the process of creating an efficient multi-factor facial authentication-based tool to prevent intruder access to our chat application, ensuring and prioritizing the highest level of user privacy. Need an SDK that assures you of a seamless and secure chat experience? Try Stream.io today.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Clerk vs Kinde vs Better Auth: How to Choose the Right Next.js Authentication Library ]]>
                </title>
                <description>
                    <![CDATA[ Authentication is an important aspect when building applications, especially if they hold financial information or require users to sign into accounts. Building an auth library can be a lot of work, and there is no need to reinvent the wheel when so ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-choose-the-right-nextjs-authentication-library/</link>
                <guid isPermaLink="false">68d2074ea281a4364a16d324</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Andrew Baisden ]]>
                </dc:creator>
                <pubDate>Tue, 23 Sep 2025 02:34:54 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758594098828/8b2e3142-9067-4a02-b1e5-63319dde45de.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Authentication is an important aspect when building applications, especially if they hold financial information or require users to sign into accounts. Building an auth library can be a lot of work, and there is no need to reinvent the wheel when so many efficient libraries already exist.</p>
<p>In this article, we’ll compare some libraries that you can use for authentication in your Next app. They include: <a target="_blank" href="https://clerk.com/">Clerk</a>, <a target="_blank" href="https://kinde.com/">Kinde</a> and <a target="_blank" href="https://www.better-auth.com/">Better Auth</a>. You’ll learn how to set up these tools in a Next.js application, with the goal of creating at least one authenticated, protected page route.</p>
<p>The aim here is simply to see how each tool works when it comes to speed of setup and ease of use.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-are-clerk-kinde-and-better-auth">What are Clerk, Kinde and Better Auth?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-authentication-using-clerk">How to Set Up Authentication Using Clerk</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-authentication-using-kinde">How to Set Up Authentication Using Kinde</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-authentication-using-better-auth">How to Set Up Authentication Using Better Auth</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-to-use-each-library">When To Use Each Library</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-are-clerk-kinde-and-better-auth">What are Clerk, Kinde and Better Auth?</h2>
<p>Clerk, Kinde and Better Auth are basically modern authentication providers, much like <a target="_blank" href="https://authjs.dev/">Auth.js</a>, which have been built with developers in mind. Although they share similarities, they have certain aspects that differentiate them from each other.</p>
<p>To begin with, Clerk is more full-featured. It's more of a hosted solution that offers components which are ready-made, as well as user management and other integrations, which allow you to get up and running pretty quickly.</p>
<p>Kinde, on the other hand, is more of a developer platform, which has authentication, feature flags and team management all in one place.</p>
<p>Better Auth is more of an open-source and code-first place that gives developers the building blocks to create authentication without having to be locked into an ecosystem.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>The prerequisites for this tutorial are minimal, and the databases and Prisma ORM are only required for Better Auth. Alternatively, you can use any of the databases inside a Docker container instead of installing them locally, but that is outside of the scope of this tutorial.</p>
<p>You’ll need these to follow along:</p>
<ul>
<li><p>Node and npm installed</p>
</li>
<li><p><a target="_blank" href="https://www.prisma.io/">Prisma ORM</a></p>
</li>
<li><p>SQLite, PostgreSQL, or MySQL database set up locally</p>
</li>
<li><p>Code editor/IDE</p>
</li>
</ul>
<p>Let's see how to set up authentication with all three auth platforms in a Next.js application. We’ll use separate Next.js applications to set up each library so that the codebase will remain clean, and you can experience what it's like to set them up from scratch.</p>
<p>First, decide on a location for your project, like on the desktop and then use the command <code>npx create-next-app@latest</code> to set up a Next.js project. You can just use the default configuration. These are the settings I used:</p>
<p>✔ What is your project named? … my-app<br>✔ Would you like to use TypeScript? … No / <strong>Yes</strong><br>✔ Which linter would you like to use? › ESLint<br>✔ Would you like to use Tailwind CSS? … No / <strong>Yes</strong><br>✔ Would you like your code inside a <code>src/</code> directory? … No / <strong>Yes</strong><br>✔ Would you like to use App Router? (recommended) … No / <strong>Yes</strong><br>✔ Would you like to use Turbopack? (recommended) … No / <strong>Yes</strong><br>✔ Would you like to customize the import alias (<code>@/*</code> by default)? … <strong>No</strong> / Yes</p>
<p>We are creating three apps, so it's up to you if you want to duplicate the codebases now and give them different names like <code>my-app</code>, <code>my-app2</code> and <code>my-app3</code> or do them later when we reach each section.</p>
<h2 id="heading-how-to-set-up-authentication-using-clerk">How to Set Up Authentication Using Clerk</h2>
<p>With your Next.js project set up, <code>cd</code> into the <code>my-app</code> folder or whatever name you gave the project and run the following command to install the Next.js SDK for Clerk:</p>
<pre><code class="lang-shell">npm install @clerk/nextjs
</code></pre>
<p>Now we need to create a middleware file that will grant us access to user authentication throughout our entire app.</p>
<p>Create a <code>middleware.ts</code> file with this code inside the <code>/src</code> folder:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { clerkMiddleware } <span class="hljs-keyword">from</span> <span class="hljs-string">'@clerk/nextjs/server'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> clerkMiddleware()

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = {
  matcher: [
    <span class="hljs-comment">// Skip Next.js internals and all static files, unless found in search params</span>
    <span class="hljs-string">'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)'</span>,
    <span class="hljs-comment">// Always run for API routes</span>
    <span class="hljs-string">'/(api|trpc)(.*)'</span>,
  ],
}
</code></pre>
<p>With this file, authentication is set up for different page routes.</p>
<p>All that's left is to add the <code>&lt;ClerkProvider&gt;</code> component to your app's <code>layout.tsx</code> file so that authentication is available throughout your entire app.</p>
<p>Just replace all of the code inside of <code>src/app/layout.tsx</code> with this code here:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Metadata } <span class="hljs-keyword">from</span> <span class="hljs-string">'next'</span>;
<span class="hljs-keyword">import</span> { Geist, Geist_Mono } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/font/google'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'./globals.css'</span>;
<span class="hljs-keyword">import</span> {
  ClerkProvider,
  SignInButton,
  SignUpButton,
  SignedIn,
  SignedOut,
  UserButton,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'@clerk/nextjs'</span>;

<span class="hljs-keyword">const</span> geistSans = Geist({
  variable: <span class="hljs-string">'--font-geist-sans'</span>,
  subsets: [<span class="hljs-string">'latin'</span>],
});

<span class="hljs-keyword">const</span> geistMono = Geist_Mono({
  variable: <span class="hljs-string">'--font-geist-mono'</span>,
  subsets: [<span class="hljs-string">'latin'</span>],
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> metadata: Metadata = {
  title: <span class="hljs-string">'Create Next App'</span>,
  description: <span class="hljs-string">'Generated by create next app'</span>,
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{
  children,
}: Readonly&lt;{
  children: React.ReactNode;
}&gt;</span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;ClerkProvider&gt;
      &lt;html lang=<span class="hljs-string">"en"</span>&gt;
        &lt;body
          className={<span class="hljs-string">`<span class="hljs-subst">${geistSans.variable}</span> <span class="hljs-subst">${geistMono.variable}</span> antialiased`</span>}
        &gt;
          &lt;header className=<span class="hljs-string">"flex justify-end items-center p-4 gap-4 h-16"</span>&gt;
            &lt;SignedOut&gt;
              &lt;SignInButton /&gt;
              &lt;SignUpButton&gt;
                &lt;button className=<span class="hljs-string">"bg-[#6c47ff] text-white rounded-full font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 cursor-pointer"</span>&gt;
                  Sign Up
                &lt;/button&gt;
              &lt;/SignUpButton&gt;
            &lt;/SignedOut&gt;
            &lt;SignedIn&gt;
              &lt;UserButton /&gt;
            &lt;/SignedIn&gt;
          &lt;/header&gt;
          {children}
        &lt;/body&gt;
      &lt;/html&gt;
    &lt;/ClerkProvider&gt;
  );
}
</code></pre>
<p>What we did was to import the <code>ClerkProvider</code>, as well as the buttons for signing in and out using Clerk authentication. These additions have been added to the <code>layout.tsx</code> file which means that they are available throughout our entire application. So every page should display the sign in flow at the top of the page.</p>
<p>The <code>ClerkProvider</code> component is needed for integrating Clerk inside of our application, so now we can use session and user context with Clerks hooks and components.</p>
<p>Now you can run your Next.js app with <code>npm run dev</code>, and you should see the homepage, as well as a sign-in and sign-up button at the top of the page, as shown here:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757428533274/31f43e8f-7826-4d4c-bf10-99b5ea7d8a76.png" alt="Next.js homepage with Clerk authentication setup" class="image--center mx-auto" width="2482" height="2620" loading="lazy"></p>
<p>Clicking the signup button will take you to a sign-up form where you can use an email address or sign in with Google, which is pretty easy.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757428605871/94e754e7-897f-42f2-84df-361d97226617.png" alt="Clerk Sign up form" class="image--center mx-auto" width="856" height="1184" loading="lazy"></p>
<p>When you have signed in, you should see your profile picture and account information in the top right-hand corner of the screen. That's the hard part done - all that's left is to create a page and then make the route protected so that only a signed-in user can access it.</p>
<p>To begin with, lets update our <code>middleware.ts</code> file with some code which lets us protect a route:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { clerkMiddleware, createRouteMatcher } <span class="hljs-keyword">from</span> <span class="hljs-string">'@clerk/nextjs/server'</span>

<span class="hljs-keyword">const</span> isProtectedRoute = createRouteMatcher([<span class="hljs-string">'/dashboard(.*)'</span>])

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> clerkMiddleware(<span class="hljs-keyword">async</span> (auth, req) =&gt; {
  <span class="hljs-keyword">if</span> (isProtectedRoute(req)) <span class="hljs-keyword">await</span> auth.protect()
})

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = {
  matcher: [
    <span class="hljs-comment">// Skip Next.js internals and all static files, unless found in search params</span>
    <span class="hljs-string">'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)'</span>,
    <span class="hljs-comment">// Always run for API routes</span>
    <span class="hljs-string">'/(api|trpc)(.*)'</span>,
  ],
}
</code></pre>
<p>We added some new imports for <code>createRouteMatcher</code>, which is a Clerk helper function that gives us the power to protect multiple routes. In this case, the dashboard page route in our application requires a user to be signed in to access the route. Now we need create a dashboard page. Create this folder and file inside the <code>app</code> folder: <code>dashboard/page.tsx</code>. Then complete the page by giving it some code like below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dashboard</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h1&gt;Dashboard Page&lt;/h1&gt;
    &lt;/&gt;
  );
}
</code></pre>
<p>We created a simple page which has a heading that says Dashboard Page.</p>
<p>Congratulations, you have successfully added authentication to your Next app and protected a page route, and it only took a few steps! When you navigate to <a target="_blank" href="http://localhost:3000/dashboard">http://localhost:3000/dashboard</a> as a non-signed-in user, you should be redirected to a sign-in form as shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757428660606/5ac6984d-7f8c-46e2-bd0b-732920d5743e.png" alt="Clerk sign in form page" class="image--center mx-auto" width="848" height="1010" loading="lazy"></p>
<p>If you are already signed in, then you should see the Dashboard page. You can learn more using the <a target="_blank" href="https://clerk.com/docs">Clerk official documentation</a>.</p>
<h2 id="heading-how-to-set-up-authentication-using-kinde">How to Set Up Authentication Using Kinde</h2>
<p>Create another Next.js application for this project if you have not done so already. Kinde will require you to create an account on their platform before using their authentication library. Let's go through the sign-up process.</p>
<p>Firstly, go to the <a target="_blank" href="https://kinde.com/">Kinde</a> website, and you should see a button that says "Start for free" or similar.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757428704023/dd6bf59e-2858-4f0a-8aa4-90645641ebe8.png" alt="Kinde website homepage" class="image--center mx-auto" width="1840" height="1052" loading="lazy"></p>
<p>Clicking that button should take you to a page where you can create an account:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757428737190/0cd172f6-ed9a-4a54-b599-182a8becfcfa.png" alt="Kinde create your account" class="image--center mx-auto" width="3006" height="1938" loading="lazy"></p>
<p>An email code verification may be required:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757428803166/8a883160-6e95-4611-91d3-efbabd74653c.png" alt="Kinde email code verification" class="image--center mx-auto" width="454" height="544" loading="lazy"></p>
<p>On the next screen, you should be able to enter your business details, which can be anything you want. Every time you set up authentication for an app, you will have to create an application for it on your account. Give it any name you want, like <code>app-clerk-test3272346214</code>. The same name will be used for the business and the domain.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757428845977/7d365554-9c15-41e0-b317-a12d58f3e7a6.png" alt="Kinde form business and domain" class="image--center mx-auto" width="472" height="821" loading="lazy"></p>
<p>On the next screen, we’ll choose to use an existing codebase because we have a local project:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757428896749/cd468f14-1b36-4025-8459-b51509519fa0.png" alt="Kinde existing codebase select" class="image--center mx-auto" width="1352" height="762" loading="lazy"></p>
<p>The codebase is in Next.js, so select it from the list:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757428936691/cf13041f-bc74-4432-a7ba-4234daf7c2a3.png" alt="Kinde select tech stack" class="image--center mx-auto" width="1368" height="1954" loading="lazy"></p>
<p>The next important step is choosing how users are going to sign in. I chose email and Google. You can select whichever options you desire:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757428976433/2885ed25-d4d0-496e-ac46-2f8b96c35a20.png" alt="Kinde user sign in form" class="image--center mx-auto" width="2312" height="1644" loading="lazy"></p>
<p>Now, on the last screen, choose to explore at your own pace.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757429011937/23a9d95f-2935-4a9c-947f-91d7adaf452e.png" alt="Kinde explore at own pace screen" class="image--center mx-auto" width="1530" height="996" loading="lazy"></p>
<p>And finally, we’ve reach the dashboard screen.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757429051925/2250e252-e84d-4ae9-bff8-a9e687692f70.png" alt="Kinde dashboard screen" class="image--center mx-auto" width="1342" height="2442" loading="lazy"></p>
<p>Viewing details lets you see your app keys and environment variables, among other useful information.</p>
<p>That's the long part out of the way, let's get to some code. Navigate to your project folder and then install the package for Kinde:</p>
<pre><code class="lang-shell">npm i @kinde-oss/kinde-auth-nextjs
</code></pre>
<p>Now, create a <code>.env.local</code> file and put it in the root folder of your project with your environment variables. You can find your environment variables in the Quick Start page of your application.</p>
<p>Here's an example:</p>
<pre><code class="lang-shell">KINDE_CLIENT_ID=&lt;your_kinde_client_id&gt;
KINDE_CLIENT_SECRET=&lt;your_kinde_client_secret&gt;
KINDE_ISSUER_URL=https://&lt;your_kinde_subdomain&gt;.kinde.com
KINDE_SITE_URL=http://localhost:3000
KINDE_POST_LOGOUT_REDIRECT_URL=http://localhost:3000
KINDE_POST_LOGIN_REDIRECT_URL=http://localhost:3000/dashboard
</code></pre>
<p>Next, you need to create the following API endpoint and folder structure and files as shown here <code>src/app/api/auth/[kindeAuth]/route.ts</code>.</p>
<p>This is the code needed for the <code>route.ts</code> file:</p>
<pre><code class="lang-shell">import {handleAuth} from "@kinde-oss/kinde-auth-nextjs/server";

export const GET = handleAuth();
</code></pre>
<p>With this code, Kinde can now handle auth endpoints inside our application.</p>
<p>Once again, you’ll need a <code>middleware.ts</code> file so that authentication can be set up properly in your app. The file should be in the root directory and needs this code added to it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { withAuth } <span class="hljs-keyword">from</span> <span class="hljs-string">"@kinde-oss/kinde-auth-nextjs/middleware"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">middleware</span>(<span class="hljs-params">req</span>) </span>{
  <span class="hljs-keyword">return</span> withAuth(req);
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = {
  matcher: [
    <span class="hljs-comment">// Run on everything but Next internals and static files</span>
    <span class="hljs-string">'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)'</span>,
  ]
};
</code></pre>
<p>Like before, we can now protect page routes with this file. Your app has to be wrapped in a Kinde Auth Provider so that you can access the data throughout your app.</p>
<p>Create an <code>AuthProvider.tsx</code> file inside the <code>app</code> directory with this code:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> {KindeProvider} <span class="hljs-keyword">from</span> <span class="hljs-string">"@kinde-oss/kinde-auth-nextjs"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> AuthProvider = <span class="hljs-function">(<span class="hljs-params">{children}</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> &lt;KindeProvider&gt;{children}&lt;/KindeProvider&gt;;
};
</code></pre>
<p>Kinde uses a React Context Provider to maintain its internal state throughout our application by using the <code>KindeProvider</code> component.</p>
<p>Next, replace and update the <code>layout.tsx</code> file, so it is wrapped in the Auth Provider:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Metadata } <span class="hljs-keyword">from</span> <span class="hljs-string">'next'</span>;
<span class="hljs-keyword">import</span> { Geist, Geist_Mono } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/font/google'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'./globals.css'</span>;
<span class="hljs-keyword">import</span> { AuthProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'./AuthProvider'</span>;
<span class="hljs-keyword">import</span> {
  RegisterLink,
  LoginLink,
  LogoutLink,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'@kinde-oss/kinde-auth-nextjs/components'</span>;

<span class="hljs-keyword">const</span> geistSans = Geist({
  variable: <span class="hljs-string">'--font-geist-sans'</span>,
  subsets: [<span class="hljs-string">'latin'</span>],
});

<span class="hljs-keyword">const</span> geistMono = Geist_Mono({
  variable: <span class="hljs-string">'--font-geist-mono'</span>,
  subsets: [<span class="hljs-string">'latin'</span>],
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> metadata: Metadata = {
  title: <span class="hljs-string">'Create Next App'</span>,
  description: <span class="hljs-string">'Generated by create next app'</span>,
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{
  children,
}: Readonly&lt;{
  children: React.ReactNode;
}&gt;</span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;AuthProvider&gt;
      &lt;div className=<span class="hljs-string">"grid grid-flow-col gap2"</span>&gt;
        &lt;LoginLink&gt;Sign <span class="hljs-keyword">in</span>&lt;/LoginLink&gt;
        &lt;RegisterLink&gt;Sign up&lt;/RegisterLink&gt;
        &lt;LogoutLink&gt;Log out&lt;/LogoutLink&gt;
      &lt;/div&gt;
      &lt;html lang=<span class="hljs-string">"en"</span>&gt;
        &lt;body
          className={<span class="hljs-string">`<span class="hljs-subst">${geistSans.variable}</span> <span class="hljs-subst">${geistMono.variable}</span> antialiased`</span>}
        &gt;
          {children}
        &lt;/body&gt;
      &lt;/html&gt;
    &lt;/AuthProvider&gt;
  );
}
</code></pre>
<p>In this file, we also added buttons for signing up, signing in, and logging out which will be displayed at the top of every page as this is the main <code>layout.tsx</code> file. Our <code>AuthProvider</code> component is wrapped around our application which means we can now use Kinde throughout it.</p>
<p>The basic setup is now complete! Run the usual command to start the Next.js app, and you should see the sign-in flow buttons at the top of the screen.</p>
<p>You should be able to sign up and create an account. Doing so will redirect you to the dashboard screen, which shows a 404 error page. This is because we have not created a dashboard page yet.</p>
<p>This is what the Kinde register form looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757429157871/c7344f32-cf4e-48d3-b43e-225f81912988.png" alt="Kinde Register form" class="image--center mx-auto" width="886" height="1388" loading="lazy"></p>
<p>And this is what the Kinde sign-in form looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757429447577/3d30476a-9269-49a9-811c-dff0c16225d9.png" alt="Kinde sign in form" class="image--center mx-auto" width="862" height="950" loading="lazy"></p>
<p>Only one step remains now: creating a protected route for your authentication.</p>
<p>Create the following file structure and file for your dashboard page: <code>src/app/dashboard/page.tsx</code>.</p>
<p>Then add this code to the file:</p>
<pre><code class="lang-typescript"><span class="hljs-string">'use client'</span>;

<span class="hljs-keyword">import</span> { useKindeBrowserClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@kinde-oss/kinde-auth-nextjs'</span>;
<span class="hljs-keyword">import</span> { LoginLink } <span class="hljs-keyword">from</span> <span class="hljs-string">'@kinde-oss/kinde-auth-nextjs/components'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dashboard</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { isAuthenticated, isLoading } = useKindeBrowserClient();

  <span class="hljs-keyword">if</span> (isLoading) <span class="hljs-keyword">return</span> &lt;div&gt;Loading...&lt;/div&gt;;

  <span class="hljs-keyword">return</span> isAuthenticated ? (
    &lt;div&gt;
      &lt;p&gt;
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ut
        ante enim. Maecenas ut eros nec diam vulputate sollicitudin. Cras ut
        quam leo. Pellentesque semper, lacus sodales gravida suscipit, metus
        quam congue eros, nec sagittis est dolor eu turpis. Nulla congue
        tristique venenatis. Donec ac venenatis mauris. Donec commodo cursus
        magna, vitae tincidunt magna vestibulum eget.
      &lt;/p&gt;
    &lt;/div&gt;
  ) : (
    &lt;div&gt;
      You have to &lt;LoginLink&gt;Login&lt;/LoginLink&gt; to see <span class="hljs-built_in">this</span> page
    &lt;/div&gt;
  );
}
</code></pre>
<p>This page file checks to see if the user is authenticated and signed in. If they are signed in, they see the Lorem ipsum text, and if they are not signed in, they will see a message telling them that they have to log in to see the page.</p>
<p>All you have to do is go to the <a target="_blank" href="http://localhost:3000/dashboard">dashboard route</a> as a signed-in or signed-out user to see it for yourself. And that's pretty much the basics of authentication using the Kinde platform. See the <a target="_blank" href="https://docs.kinde.com/">online documentation</a> to learn everything there is to know about it.</p>
<h2 id="heading-how-to-set-up-authentication-using-better-auth">How to Set Up Authentication Using Better Auth</h2>
<p>And lastly, let's create a project that uses Better Auth. Better Auth requires a database to store user data, so the setup will require a few more steps. You can find the installation guide <a target="_blank" href="https://www.better-auth.com/docs/installation">here</a>, but, we’ll also go through it here.</p>
<p>Ok, just like before, create a Next.js project if you have not done so yet and then install the <code>better-auth</code> package with this command:</p>
<pre><code class="lang-shell">npm install better-auth
</code></pre>
<p>Next, you have to set up your environment variables, so create a <code>.env</code> file with these values:</p>
<pre><code class="lang-shell">BETTER_AUTH_SECRET= #Create your own secret key!
BETTER_AUTH_URL=http://localhost:3000 # Base URL of your app
</code></pre>
<p>Make sure that you create a secret key, as if you were generating a secure password with uppercase and lowercase letters and numbers.</p>
<p>Let's get our Prisma package and PostgreSQL database set up, so run these scripts to initialise them:</p>
<pre><code class="lang-shell">npm install prisma --save-dev
npx prisma init
npm install @prisma/client
</code></pre>
<p>In the next step, we have to create a better auth instance. In this case, we will put the file in the <code>src/lib/auth.ts</code>.</p>
<p>So create an <code>auth.ts</code> file with this code:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { betterAuth } <span class="hljs-keyword">from</span> <span class="hljs-string">'better-auth'</span>;
<span class="hljs-keyword">import</span> { anonymous } <span class="hljs-keyword">from</span> <span class="hljs-string">'better-auth/plugins'</span>;
<span class="hljs-keyword">import</span> { prismaAdapter } <span class="hljs-keyword">from</span> <span class="hljs-string">'better-auth/adapters/prisma'</span>;
<span class="hljs-comment">// If your Prisma file is located elsewhere, you can change the path</span>
<span class="hljs-keyword">import</span> { PrismaClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/generated/prisma'</span>;

<span class="hljs-keyword">const</span> prisma = <span class="hljs-keyword">new</span> PrismaClient();
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> auth = betterAuth({
  database: prismaAdapter(prisma, {
    provider: <span class="hljs-string">'postgresql'</span>, <span class="hljs-comment">// or "mysql", "postgresql", ...etc</span>
  }),
  plugins: [anonymous()],
});
</code></pre>
<p>In this file, we have configured Better Auth to use Prisma ORM for our database connection, and we will be connecting to a PostgreSQL database. If you want, you can change it to a different database, but the setup might be different, so bear that in mind. You could also use Docker if you know how to set it up. Anonymous sign-in is the default for users.</p>
<p>Now we need to create our database tables for saving user information, so use this command in the terminal to do that:</p>
<pre><code class="lang-shell">npx @better-auth/cli generate
</code></pre>
<p>You might see this warning, just select yes with "y":</p>
<pre><code class="lang-shell">prisma:warn In production, we recommend using `prisma generate --no-engine` (See: `prisma generate --help`)
✔ The file ./prisma/schema.prisma already exists. Do you want to overwrite the schema to the file? … yes
</code></pre>
<p>For handling API requests, we must have a route handler set up on our server. Create a folder structure and file for the <code>route.ts</code> file, like shown here <code>/app/api/auth/[...all]/route.ts</code>.</p>
<p>Add this code to the file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/lib/auth'</span>; <span class="hljs-comment">// path to your auth file</span>
<span class="hljs-keyword">import</span> { toNextJsHandler } <span class="hljs-keyword">from</span> <span class="hljs-string">'better-auth/next-js'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> { POST, GET } = toNextJsHandler(auth);
</code></pre>
<p>This file lets you handle POST and GET requests for your auth file.</p>
<p>Lastly, we have to create a <code>lib/auth-client.ts</code> file. This file allows you to interact with the auth server, and it has a plugin so users can sign in anonymously.</p>
<p>And here is the code to put inside this file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { createAuthClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'better-auth/react'</span>;
<span class="hljs-keyword">import</span> { anonymousClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'better-auth/client/plugins'</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> authClient = createAuthClient({
  <span class="hljs-comment">/** The base URL of the server (optional if you're using the same domain) */</span>
  baseURL: <span class="hljs-string">'http://localhost:3000'</span>,
  plugins: [anonymousClient()],
});
</code></pre>
<p>With this file, it's possible for users to sign in anonymously without having to even create an account or use social sign-in, thanks to the plugin.</p>
<p>All that remains is to create another dashboard page, which has authentication like before. Create another dashboard page with this structure: <code>app/dashboard/page.tsx</code>, and then add this code to the file:</p>
<pre><code class="lang-typescript"><span class="hljs-string">'use client'</span>;

<span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { authClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/lib/auth-client'</span>;

<span class="hljs-keyword">type</span> User = {
  id: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
  emailVerified: <span class="hljs-built_in">boolean</span>;
  name: <span class="hljs-built_in">string</span>;
  createdAt: <span class="hljs-built_in">Date</span>;
  updatedAt: <span class="hljs-built_in">Date</span>;
  image?: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>;
  isAnonymous?: <span class="hljs-built_in">boolean</span> | <span class="hljs-literal">null</span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dashboard</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [user, setUser] = useState&lt;User | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [isLoading, setIsLoading] = useState(<span class="hljs-literal">true</span>);
  <span class="hljs-keyword">const</span> [isSigningIn, setIsSigningIn] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [error, setError] = useState&lt;<span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> checkAuth = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> session = <span class="hljs-keyword">await</span> authClient.getSession();
        <span class="hljs-keyword">if</span> (session.data?.user) {
          setUser(session.data.user);
        }
      } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Auth check error:'</span>, err);
      } <span class="hljs-keyword">finally</span> {
        setIsLoading(<span class="hljs-literal">false</span>);
      }
    };

    checkAuth();
  }, []);

  <span class="hljs-keyword">const</span> handleAnonymousSignIn = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">try</span> {
      setIsSigningIn(<span class="hljs-literal">true</span>);
      setError(<span class="hljs-literal">null</span>);

      <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> authClient.signIn.anonymous();

      <span class="hljs-keyword">if</span> (result.data) {
        setUser(result.data.user);
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Anonymous user signed in:'</span>, result.data.user);
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (result.error) {
        setError(result.error.message || <span class="hljs-string">'Failed to sign in anonymously'</span>);
      }
    } <span class="hljs-keyword">catch</span> (err) {
      setError(<span class="hljs-string">'An unexpected error occurred'</span>);
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Anonymous sign-in error:'</span>, err);
    } <span class="hljs-keyword">finally</span> {
      setIsSigningIn(<span class="hljs-literal">false</span>);
    }
  };

  <span class="hljs-keyword">const</span> handleSignOut = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> authClient.signOut();
      setUser(<span class="hljs-literal">null</span>);
    } <span class="hljs-keyword">catch</span> (err) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Sign out error:'</span>, err);
    }
  };

  <span class="hljs-keyword">if</span> (isLoading) {
    <span class="hljs-keyword">return</span> (
      &lt;div className=<span class="hljs-string">"max-w-4xl mx-auto p-6"</span>&gt;
        &lt;div className=<span class="hljs-string">"flex items-center justify-center min-h-[400px]"</span>&gt;
          &lt;div className=<span class="hljs-string">"text-center"</span>&gt;
            &lt;div className=<span class="hljs-string">"animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"</span>&gt;&lt;/div&gt;
            &lt;p className=<span class="hljs-string">"text-gray-600"</span>&gt;Checking authentication...&lt;/p&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    );
  }

  <span class="hljs-keyword">if</span> (!user) {
    <span class="hljs-keyword">return</span> (
      &lt;div className=<span class="hljs-string">"max-w-4xl mx-auto p-6"</span>&gt;
        &lt;div className=<span class="hljs-string">"text-center"</span>&gt;
          &lt;h1 className=<span class="hljs-string">"text-3xl font-bold mb-6"</span>&gt;Access Required&lt;/h1&gt;
          &lt;p className=<span class="hljs-string">"text-gray-600 mb-8"</span>&gt;
            You need to be signed <span class="hljs-keyword">in</span> to access our dashboard. Choose an option
            below to <span class="hljs-keyword">continue</span>.
          &lt;/p&gt;

          {error &amp;&amp; (
            &lt;div className=<span class="hljs-string">"bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-6 max-w-md mx-auto"</span>&gt;
              {error}
            &lt;/div&gt;
          )}

          &lt;div className=<span class="hljs-string">"bg-gray-50 p-8 rounded-lg border max-w-md mx-auto"</span>&gt;
            &lt;h2 className=<span class="hljs-string">"text-xl font-semibold mb-4 text-black"</span>&gt;
              Sign In Options
            &lt;/h2&gt;

            &lt;button
              onClick={handleAnonymousSignIn}
              disabled={isSigningIn}
              className=<span class="hljs-string">"w-full bg-blue-500 hover:bg-blue-600 disabled:bg-blue-300 text-white px-6 py-3 rounded font-medium mb-4"</span>
            &gt;
              {isSigningIn ? <span class="hljs-string">'Signing in...'</span> : <span class="hljs-string">'Sign In Anonymously'</span>}
            &lt;/button&gt;

            &lt;p className=<span class="hljs-string">"text-sm text-gray-500 mb-4"</span>&gt;
              Anonymous access allows you to use our dashboard without creating
              an account. You can always link your account later.
            &lt;/p&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    );
  }

  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"max-w-4xl mx-auto p-6"</span>&gt;
      &lt;div className=<span class="hljs-string">"bg-green-50 border border-green-200 rounded-lg p-4 mb-6"</span>&gt;
        &lt;div className=<span class="hljs-string">"flex items-center justify-between"</span>&gt;
          &lt;div&gt;
            &lt;h2 className=<span class="hljs-string">"text-lg font-semibold text-green-800"</span>&gt;
              Welcome, {user.isAnonymous ? <span class="hljs-string">'Anonymous User'</span> : user.name}!
            &lt;/h2&gt;
            &lt;p className=<span class="hljs-string">"text-sm text-green-600"</span>&gt;
              {user.isAnonymous
                ? <span class="hljs-string">'You are signed in anonymously'</span>
                : <span class="hljs-string">`Signed in as <span class="hljs-subst">${user.email}</span>`</span>}
            &lt;/p&gt;
          &lt;/div&gt;
          &lt;button
            onClick={handleSignOut}
            className=<span class="hljs-string">"bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded text-sm"</span>
          &gt;
            Sign Out
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;h1 className=<span class="hljs-string">"text-3xl font-bold mb-6"</span>&gt;Dashboard&lt;/h1&gt;

      {process.env.NODE_ENV === <span class="hljs-string">'development'</span> &amp;&amp; (
        &lt;div className=<span class="hljs-string">"mt-8 bg-gray-100 p-4 rounded-lg"</span>&gt;
          &lt;h3 className=<span class="hljs-string">"font-semibold mb-2 text-black"</span>&gt;Debug Info:&lt;/h3&gt;
          &lt;pre className=<span class="hljs-string">"text-xs text-gray-600 overflow-auto"</span>&gt;
            {<span class="hljs-built_in">JSON</span>.stringify(user, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>)}
          &lt;/pre&gt;
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  );
}
</code></pre>
<p>This code creates a dashboard page which requires a user to be authenticated and signed in to use it. After a user signs in anonymously, they can view some debug info about their profile. So basically, this page is an authentication flow for our dashboard view which integrates with our custom <code>authClient</code>. This page also handles loading error state and sign out for anonymous users.</p>
<p>Ok, now we probably need to reset our Prisma database again, or we could get schema and table errors.</p>
<p>First, make sure that your Prisma development database is running with this command:</p>
<pre><code class="lang-shell">npx prisma dev
</code></pre>
<p>And then run these commands to reset the database and apply the new migrations for the schema:</p>
<pre><code class="lang-shell">npx prisma migrate reset
npx prisma migrate dev
</code></pre>
<p>You might need to restart the Prisma development server and then run it with the command <code>npx prisma dev</code>.</p>
<p>Our Better Auth app needs two servers to be running.</p>
<ol>
<li><p>The Prisma development server</p>
</li>
<li><p>The Next.js application</p>
</li>
</ol>
<p>With the Prisma development server running, you can now start the Next.js app with the usual command <code>npm run dev</code>.</p>
<p>If you encounter any problems with the Prisma ORM or database, like tables missing or schema mismatches, then here are some useful commands which could hopefully resolve them.</p>
<pre><code class="lang-shell"># ⚠️ WARNING: This will drop the database, recreate it, and apply all migrations from scratch.
npx prisma migrate reset

# Applies any new migrations that haven’t been run yet (or creates a new one if your schema changed).
npx prisma migrate dev

# Starts Prisma Studio (a GUI for exploring and editing your database).
npx prisma dev

# Runs Better Auth CLI migrations (sets up any database tables/changes required for authentication).
npx @better-auth/cli migrate
</code></pre>
<p>These commands are self-explanatory and let us run migrations on our database when there are changes, and we can see our database inside of Prisma Studio.</p>
<p>This is what the dashboard page looks like when a user is not signed in:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757429500439/9c0846fc-cfc5-4232-9a02-4e313c2c4698.png" alt="Better Auth app Dashboard page" class="image--center mx-auto" width="1310" height="792" loading="lazy"></p>
<p>Signing in will show the dashboard screen. To learn more about Better Auth, you can read their <a target="_blank" href="https://www.better-auth.com/">official documentation</a>.</p>
<h2 id="heading-when-to-use-each-library">When To Use Each Library</h2>
<p>Each of these authentication libraries have their pros and cons, and can suit various needs depending on your project. Knowing when to use each one can better prepare you for real-world conditions and when building for production, so let's go through them and see how they compare.</p>
<h3 id="heading-when-to-use-clerk">When to Use Clerk</h3>
<p>Clerk excels when you need to use auth quickly without worrying about managing multiple servers, and when you need to have pre-made interfaces and management systems. It's great for startups and teams that want to prioritize developer experience and fast implementation.</p>
<p>It has a streamlined and user-friendly setup, which is still able to support the bare minimum essentials, so it's a really good option for small teams and projects that must have an easy implementation. If you are building a SaaS that needs a good interface and components from the start, or if you require social logins without a ton of code, then it's a very good solution, especially if you want speed and cost effectiveness in a smaller project.</p>
<h3 id="heading-when-to-use-kinde">When to Use Kinde</h3>
<p>Kinde is a fantastic choice when you are keen on having transparent pricing and quick auth integration in more frameworks. Kinde has been designed to be a cost-effective alternative to Clerk and offers more transparent pricing and a more generous, free tier.</p>
<p>It's great for teams that need to have a reliable authentication option but want lower costs and better framework support. Kinde is effective when used in medium-sized projects that need more than a basic authentication system, but also don't have the need to have enterprise-grade tooling.</p>
<h3 id="heading-when-to-use-better-auth">When to Use Better Auth</h3>
<p>Better Auth is a great solution when you have a need for an expansive set of features out of the box. It is also worth noting that Better Auth has a plugin ecosystem that can simplify adding more advanced functionalities with only a few lines of code. Of all the options discussed in this article, Better Auth is by far the cleanest; however, it requires more coding knowledge and skills.</p>
<p>It's a good option if you are building a TypeScript application and want to have full control over the customization and auth data flows. The framework is agnostic and has features such as 2FA, multi-tenant support and other complex features so developers can get the best out of the tool, as there is no vendor lock-in. Functionality can easily be expanded with the plugin ecosystem, so developers can tailor it to their needs.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>All three platforms are fairly good at their job, and I do not doubt that they are going to remain popular options for adding authentication to our applications. Auth.js is one of the most well-known out there; however, Clerk, Kinde and Better Auth also appear to have growing followings, and judging by conversations on socials, they appear to be the first choice for many developers at the moment.</p>
<p>After experiencing what it's like to set them up for the first time, I would have to say that Clerk is the easiest to set up because you don't have to create an account, and you can get the authentication working fairly quickly with little troubleshooting. Kinde would be the second easiest to set up, in my opinion. You do have to register for an account to use the platform; however, the setup was also pretty easy and did not need any troubleshooting.</p>
<p>Better Auth is a great platform, but the setup requires a bit more work because a database is required for storing users' information, which makes the process slightly more difficult. I also found it easier to create authenticated routes with the other two auth options. However, the fact that the platform is open-source with no vendor lock-in works in its favour because developers can self-host and it's completely free, which means no paid plans.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Postman Scripts to Simplify Your API Authentication Process ]]>
                </title>
                <description>
                    <![CDATA[ Postman is a platform used by developers, API testers, technical writers and DevOps teams for testing, documenting and collaborating on API development. It provides a user-friendly interface for making different types of API requests (HTTP, GraphQL, ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-postman-scripts/</link>
                <guid isPermaLink="false">68bee731d2147595571c5b44</guid>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Mon, 08 Sep 2025 14:24:49 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1757341168209/dc77dc00-a0a6-40f7-b766-ce07d0d8a637.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Postman is a platform used by developers, API testers, technical writers and DevOps teams for testing, documenting and collaborating on API development. It provides a user-friendly interface for making different types of API requests (HTTP, GraphQL, gRPC), inspecting responses, and organizing API calls into collections for collaboration and automation.</p>
<p>Performing repetitive tasks while testing APIs is stressful and time-wasting. For example, the process of retrieving, copying and pasting new authentication tokens for use in Postman is repetitive. You can simplify this process by using Postman scripts to store auth tokens and then reuse them without repeating the copy and paste steps.</p>
<p>To practice along with this guide, you should have:</p>
<ul>
<li><p>The <a target="_blank" href="https://www.postman.com/downloads/">Postman API client</a> installed on your computer</p>
</li>
<li><p>Experience in making API requests with Postman</p>
</li>
<li><p>A backend application that uses JWT authentication and has its documentation in your Postman client</p>
</li>
</ul>
<p>If you don’t have a backend application setup, I created one that you can clone from GitHub at <a target="_blank" href="https://github.com/orimdominic/freeCodeCamp-postman-api-jwt">orimdominic/freeCodeCamp-postman-api-jwt</a>.</p>
<p>By the end of this article, you should be able to simplify the process of obtaining and reusing authentication tokens across your API requests. You should also have a practical understanding of some scripts necessary for use in other areas of software testing with Postman.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-table-of-contents">Table of Contents</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-postman-scripts">What are Postman Scripts?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-simplify-your-jwt-authentication-process">How to Simplify Your JWT Authentication Process</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-authenticate-to-get-the-token">Authenticate to Get the Token</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-save-the-token-in-a-variable-with-a-postman-script">How to Save the Token in a Variable with a Postman Script</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-the-variable-in-a-request">How to Use the Variable in a Request</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-next-steps">Next Steps</a></p>
</li>
</ul>
<h2 id="heading-what-are-postman-scripts">What are Postman Scripts?</h2>
<p><a target="_blank" href="https://learning.postman.com/docs/tests-and-scripts/tests-and-scripts/">Postman scripts</a> are blocks of JavaScript code that you can write and run within the Postman API client to automate and enhance API testing workflows. You can use Postman scripts to add code to run before and after API requests. These scripts can be used to:</p>
<ul>
<li><p>Add logic and process data from API requests</p>
</li>
<li><p>Write test assertions for API responses</p>
</li>
<li><p>Run automated tests on API endpoints</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756577771526/161bd327-fbf7-48cb-acab-317ab1cad4c5.jpeg" alt="The Postman scripts tab" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>You can find Postman scripts under the <strong>Scripts</strong> tab of an API request. Code written in the <strong>Pre-request</strong> tab runs before the request is made and code written in the <strong>Post-response</strong> tab runs after the response is made.</p>
<h2 id="heading-how-to-simplify-your-jwt-authentication-process">How to Simplify Your JWT Authentication Process</h2>
<p>In summary, you will carry out the following steps to achieve the objective of this tutorial:</p>
<ol>
<li><p>Authenticate to get the token</p>
</li>
<li><p>Save the token in a collection variable with Postman scripts</p>
</li>
<li><p>Use the variable in an API request</p>
</li>
</ol>
<h3 id="heading-authenticate-to-get-the-token">Authenticate to Get the Token</h3>
<p>To get started, carry out the following steps:</p>
<ol>
<li><p>Start your backend application and make sure it is running successfully.</p>
</li>
<li><p>Open up your Postman application and go to the API request for signing in to get a JWT.</p>
</li>
<li><p>Make an API request to the sign in endpoint and take note of the JSON response schema.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756573137191/b5aad14c-5094-4a84-8876-1bbbb869064c.jpeg" alt="Authentication request response" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>The highlighted part of the image above shows the JSON response from a successful sign in request. In the response schema, the auth token to be used for authorization is in the <code>data.token</code> field. You will use Postman scripts to store this token in a variable and then use the variable in the <code>Authorization</code> header of requests that require authorization.</p>
<h3 id="heading-how-to-save-the-token-in-a-variable-with-a-postman-script">How to Save the Token in a Variable with a Postman Script</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756575948975/2b43493d-2803-45cd-aefe-0ca5694f75e8.jpeg" alt="Add logic in Post-response Postman script" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>In Postman, click on the <strong>Scripts</strong> tab next to the <strong>Body</strong> tab. If the Postman application window is small, you may need to click a dropdown to see it. Next, click on the <strong>Post-response</strong> tab. In the text area to the right, you will write the script to capture the auth token from the response and store it in a Postman variable. Copy the JavaScript code below and paste it into the text area.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (pm.response.code == <span class="hljs-number">200</span>) {
    <span class="hljs-keyword">const</span> token = pm.response.json().data.token
    pm.collectionVariables.set(<span class="hljs-string">"auth_token"</span>, token)
}
</code></pre>
<p>Postman scripts use the <a target="_blank" href="https://learning.postman.com/docs/tests-and-scripts/write-scripts/postman-sandbox-api-reference/"><code>pm</code> identifier</a> to access and modify information in the Postman environment. The script above uses <code>pm</code> to first ensure that the request was successful by checking if the response status code is <code>200</code>.</p>
<p>Inside the conditional statement, <code>pm.response.json().data.token</code> is used to get the authentication token from the JSON response and store it in a collection variable called <code>auth_token</code>. If <code>auth_token</code> doesn’t exist already, it is created and its value is set to the value of <code>token</code>. If it exists already, its value is replaced.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756581970294/bed1fe89-9c00-4b94-9f71-173ea3bf1cd1.png" alt="Postman collection variable set by a script" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>To confirm that <code>auth_token</code> has been set, click on the name of the collection (labelled 1 in the screenshot above) and then click on the <strong>Variables</strong> tab (labelled 2 in the screenshot above). Next, instead of repeatedly copying the token and pasting it in the <code>Authorization</code> header of your requests, you will use <code>auth_token</code> in the <code>Authorization</code> header of your requests.</p>
<h3 id="heading-how-to-use-the-variable-in-a-request">How to Use the Variable in a Request</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756583915681/d3bf0f56-c406-4d3e-b7f1-df4cbc2a3cfc.png" alt="Use the Variable in a Request" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Reference the collection variable in the <code>Authorization</code> header by surrounding it with double curly braces <code>{{auth_token}}</code>. When you make an API request, Postman will use the value referenced by <code>{{auth_token}}</code> as the <code>Authorization</code> header.</p>
<p>If another authentication request causes the value of <code>auth_token</code> to be updated, you no longer need to copy the new auth token. The script in the post-response tab will update the <code>auth_token</code> value and you can go on with making API requests smoothly. No need for repeatedly copying and pasting - <strong>Don’t Repeat Yourself (DRY)</strong>.</p>
<h2 id="heading-next-steps">Next Steps</h2>
<p>In this tutorial, you have learnt how to use Postman scripts to set environment variables in Postman. You have also learnt how to eliminate the process of repeatedly copying and pasting auth tokens for use in API requests.</p>
<p>For guides on writing assertion tests for your APIs, check out the <a target="_blank" href="https://learning.postman.com/docs/tests-and-scripts/test-apis/test-apis/">Test API Functionality and Performance in Postman</a> guide by Postman.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Implement Zero-Trust Authentication in Your Web Apps ]]>
                </title>
                <description>
                    <![CDATA[ Your biggest security problem might be inside your own network. Hackers don't break in anymore - they just log in with stolen passwords. Old security systems trusted anyone who got inside the network. But now there's no clear "inside" or "outside." P... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-implement-zero-trust-authentication-in-your-web-apps/</link>
                <guid isPermaLink="false">6893afcc4ff769448b46934a</guid>
                
                    <category>
                        <![CDATA[ zerotrust ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #cybersecurity ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tope Fasasi ]]>
                </dc:creator>
                <pubDate>Wed, 06 Aug 2025 19:41:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754503273007/1b04e262-05de-4fac-be47-56c01eb44446.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Your biggest security problem might be inside your own network. Hackers don't break in anymore - they just log in with stolen passwords. Old security systems trusted anyone who got inside the network. But now there's no clear "inside" or "outside." People work from home, use cloud services, and fall for fake emails. Attackers can pretend to be real users for weeks without being caught.</p>
<p>Zero-Trust Authentication fixes this. Instead of trusting people once they log in, it checks every person, every device, and every request, every single time. The rule is simple: "Trust no one, verify everything."</p>
<p>This isn't just theory – it works. Companies using zero-trust security have smaller breaches, meet compliance rules easier, and control who sees what data. This matters because <a target="_blank" href="https://www.securityweek.com/cost-of-data-breach-in-2024-4-88-million-says-latest-ibm-study/">95% of data breaches happen due to human mistakes, and the average breach now costs $4.88 million</a>.</p>
<p>In this article, you will learn how to build a complete Zero-Trust Authentication system into your web app step by step. From multi-factor authentication (MFA) to behavioral anomaly detection, we will discuss the architecture decisions, code examples, and some real-world approaches you are likely able to implement right away.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-zero-trust-authentication">What Is Zero-Trust Authentication?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-architecture-overview">Architecture Overview</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-multi-factor-authentication-mfa">Multi-factor Authentication (MFA)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-jwt-token-management">JWT Token Management</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-session-security">Session Security</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-role-based-access-control-rbac">Role-Based Access Control (RBAC)</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-using-middleware-to-enforce-rbac">Using Middleware to Enforce RBAC</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-access-control-logic">Testing Access Control Logic</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-continuous-verification">Continuous Verification</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-behavioral-analysis">Behavioral Analysis</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-up-authentication">Step-Up Authentication</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-security-monitoring">Security Monitoring</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-automating-threat-response">Automating Threat Response</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before implementing zero-trust, make sure your stack aligns with frequent calls for token checks, volumes of logging, and the additional auth step, all without impairing system performance on the users' end.</p>
<p>You should at least have knowledge of:</p>
<ul>
<li><p>JWT and secure session handling</p>
</li>
<li><p>MFA, specifically understanding TOTP</p>
</li>
<li><p>Basic understanding of middleware design</p>
</li>
</ul>
<p>Audit your system: examine login flows, token handling, protected routes, session termination, and identify weak spots like long sessions or unprotected routes.</p>
<h2 id="heading-what-is-zero-trust-authentication">What Is Zero-Trust Authentication?</h2>
<p><a target="_blank" href="https://www.civilsdaily.com/news/what-is-zero-trust-authentication-zta/">Zero-Trust Authentication</a> (ZTA) redefines how access is granted in contemporary applications. It doesn't take network location or a single login event into account – it demands the continuous validation of an identity, context, and intent.</p>
<p>Whereas perimeter-based models consider anyone inside a network "safe," zero-trust presumes every request can be compromised. This means that access decisions are made in real time over verified identity, device posture, and behavioral signals. In short, it’s a "security-first" approach designed for a cloud-native, threat-aware world.</p>
<h2 id="heading-architecture-overview">Architecture Overview</h2>
<p>Building a ZTA system means checking everyone and everything, all the time. The architecture you can see below demonstrates this "never trust, always verify" approach in action:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752183554393/4cfda450-14d8-49e3-944b-a0e4654a3dcc.png" alt="Zero Trust Security architecture diagram showing trust boundary encompassing internal network components, with external cloud services and internet connections, illustrating key zero trust principles" class="image--center mx-auto" width="779" height="401" loading="lazy"></p>
<p>Image source: <a target="_blank" href="https://www.civilsdaily.com/news/what-is-zero-trust-authentication-zta/">civilsdaily</a></p>
<p>Here's how it works:</p>
<ul>
<li><p>Every request gets checked: When anyone tries to access your network (from office, home, or mobile), they hit the authentication layer first. No exceptions.</p>
</li>
<li><p>Identity + context verification: The system doesn't just check passwords. It looks at who you are, what device you're using, where you're connecting from, and what you're trying to access.</p>
</li>
<li><p>Continuous protection: Once inside, the system keeps watching. It protects your data, devices, networks, people, and workloads through constant monitoring and access controls.</p>
</li>
<li><p>The big change: Traditional security created a "trusted inside" and "untrusted outside." Zero-trust eliminates this boundary. Whether you're connecting to cloud services (AWS, Office 365) or internal systems, every request goes through the same verification process.</p>
</li>
</ul>
<h2 id="heading-multi-factor-authentication-mfa">Multi-factor Authentication (MFA)</h2>
<p><a target="_blank" href="https://support.microsoft.com/en-gb/topic/what-is-multifactor-authentication-e5e39437-121c-be60-d123-eda06bddf661">MFA</a> is the foundation of zero-trust security. It requires users to prove who they are with multiple pieces of evidence before getting access. In ZTA, even the strongest password isn't enough on its own.</p>
<p>To begin, start with a strong password, then add a second factor. For example, <a target="_blank" href="https://en.wikipedia.org/wiki/Time-based_one-time_password">Time-based One-Time Password (TOTP)</a> is the most secure. TOTP is the best second factor because it works offline and doesn't rely on SMS or email (which can be intercepted). Apps like Google Authenticator generate a new code every 30 seconds.</p>
<p>Here’s an example of what that would look like:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> speakeasy = <span class="hljs-built_in">require</span>(<span class="hljs-string">'speakeasy'</span>);
<span class="hljs-keyword">const</span> QRCode = <span class="hljs-built_in">require</span>(<span class="hljs-string">'qrcode'</span>);

<span class="hljs-comment">// Generate TOTP secret for new user</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateTOTPSecret</span>(<span class="hljs-params">userEmail</span>) </span>{
  <span class="hljs-keyword">const</span> secret = speakeasy.generateSecret({
    <span class="hljs-attr">name</span>: userEmail,
    <span class="hljs-attr">issuer</span>: <span class="hljs-string">'YourApp'</span>,
    <span class="hljs-attr">length</span>: <span class="hljs-number">32</span>
  });

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">secret</span>: secret.base32,
    <span class="hljs-attr">qrCodeUrl</span>: secret.otpauth_url
  };
}
</code></pre>
<p>When a new user signs up, this function creates a unique secret key just for them. The <code>name</code> is their email, <code>issuer</code> is your app name, and <code>length: 32</code> makes it extra secure. It returns two things: the secret key (in base32 format) and a special URL that creates a QR code for easy setup.</p>
<p>To verify the code from their app, you check it against the stored secret:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Verify TOTP token</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verifyTOTP</span>(<span class="hljs-params">token, secret</span>) </span>{
  <span class="hljs-keyword">return</span> speakeasy.totp.verify({
    <span class="hljs-attr">secret</span>: secret,
    <span class="hljs-attr">token</span>: token,
    <span class="hljs-attr">window</span>: <span class="hljs-number">2</span>,
    <span class="hljs-attr">encoding</span>: <span class="hljs-string">'base32'</span>
  });
}
</code></pre>
<p>When the user enters their 6-digit code, this function checks if it's correct. The <code>window: 2</code> is smart – it allows for timing differences (like if their phone clock is slightly off). It returns true if the code is valid, false if not.</p>
<p>SMS verification can serve as a backup option. It’s less secure than TOTP but can work as a backup. Always limit how many SMS codes someone can request to prevent abuse:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// SMS verification with rate limiting</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendSMSVerification</span>(<span class="hljs-params">phoneNumber, userId</span>) </span>{
  <span class="hljs-keyword">const</span> attempts = <span class="hljs-keyword">await</span> getRecentSMSAttempts(userId);
  <span class="hljs-keyword">if</span> (attempts &gt;= <span class="hljs-number">3</span>) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Too many SMS attempts. Please try again later.'</span>);
  }

  <span class="hljs-keyword">const</span> code = generateRandomCode(<span class="hljs-number">6</span>);
  <span class="hljs-keyword">await</span> storeSMSCode(userId, code, <span class="hljs-number">300</span>); <span class="hljs-comment">// 5-minute expiry</span>

  <span class="hljs-keyword">await</span> smsProvider.send(phoneNumber, <span class="hljs-string">`Your verification code: <span class="hljs-subst">${code}</span>`</span>);
}
</code></pre>
<p>Before sending an SMS, it checks how many times this user has already requested codes. If they've tried 3 times, it blocks them (prevents spam/abuse). If they're under the limit, it creates a random 6-digit code, saves it for 5 minutes (300 seconds), then sends it via SMS.</p>
<p>But what happens if a user loses their phone or authenticator app? Backup codes provide emergency access:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Generate backup codes</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateBackupCodes</span>(<span class="hljs-params">userId</span>) </span>{
  <span class="hljs-keyword">const</span> codes = [];
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) {
    codes.push(generateRandomCode(<span class="hljs-number">8</span>));
  }

  <span class="hljs-keyword">const</span> hashedCodes = codes.map(<span class="hljs-function"><span class="hljs-params">code</span> =&gt;</span> hashCode(code));
  storeBackupCodes(userId, hashedCodes);

  <span class="hljs-keyword">return</span> codes; <span class="hljs-comment">// Only show to user once</span>
}
</code></pre>
<p>This creates 10 emergency backup codes (each 8 characters long). The <code>for</code> loop runs 10 times, creating a new random code each time. Before storing them in the database, it "hashes" them (scrambles them for security). Then it returns the original codes to show the user once, but stores the scrambled versions so even if someone hacks your database, they can't see the real codes.</p>
<h2 id="heading-jwt-token-management">JWT Token Management</h2>
<p>JSON Web Tokens (JWTs) are stateless authentication in a zero-trust system. Using them safely is critical because you need to carefully think through payload design, implement short expiration policies, and implement token rotation and blocklisting that could prevent token theft, token reuse, or privilege escalation.</p>
<p>Let's walk through how to securely implement and manage JWTs in your web application.</p>
<p>First, define a minimal and secure structure for your access tokens. Only add information that’s necessary for making authorization decisions, and never put anything sensitive even if it is encrypted.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// JWT payload structure</span>
<span class="hljs-keyword">const</span> tokenPayload = {
  <span class="hljs-attr">sub</span>: userId,           <span class="hljs-comment">// Subject (user ID)</span>
  <span class="hljs-attr">email</span>: userEmail,      <span class="hljs-comment">// User identifier</span>
  <span class="hljs-attr">roles</span>: userRoles,      <span class="hljs-comment">// User roles array</span>
  <span class="hljs-attr">permissions</span>: userPermissions, <span class="hljs-comment">// Specific permissions</span>
  <span class="hljs-attr">iat</span>: <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Date</span>.now() / <span class="hljs-number">1000</span>), <span class="hljs-comment">// Issued at</span>
  <span class="hljs-attr">exp</span>: <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Date</span>.now() / <span class="hljs-number">1000</span>) + <span class="hljs-number">900</span>, <span class="hljs-comment">// Expires in 15 minutes</span>
  <span class="hljs-attr">jti</span>: generateUniqueId(), <span class="hljs-comment">// JWT ID for blocklisting</span>
  <span class="hljs-attr">aud</span>: <span class="hljs-string">'your-app'</span>,       <span class="hljs-comment">// Audience</span>
  <span class="hljs-attr">iss</span>: <span class="hljs-string">'your-auth-service'</span> <span class="hljs-comment">// Issuer</span>
};
</code></pre>
<p>In the code above, the payload consists of the user identity, roles, permissions, and metadata such as the issued time (<code>iat</code>), expiration (<code>exp</code>), and unique token ID (<code>jti</code>). While <code>aud</code> and <code>iss</code> describe the token's origin and audience for validation, <code>jti</code> is used for revocation. Thus, it keeps the payload as lean as possible to minimize exposure and overhead.</p>
<p>For security and usability, it’s better to use access tokens with a short lifespan and refresh tokens with a considerably longer duration, which minimizes the window for potential utilization of compromised tokens while providing a smooth user session.</p>
<p>Let's take this example:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Token generation service</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TokenService</span> </span>{
  generateTokenPair(user) {
    <span class="hljs-keyword">const</span> accessToken = jwt.sign(
      <span class="hljs-built_in">this</span>.createAccessTokenPayload(user),
      process.env.JWT_SECRET,
      { <span class="hljs-attr">expiresIn</span>: <span class="hljs-string">'15m'</span>, <span class="hljs-attr">algorithm</span>: <span class="hljs-string">'HS256'</span> }
    );

    <span class="hljs-keyword">const</span> refreshToken = jwt.sign(
      { <span class="hljs-attr">sub</span>: user.id, <span class="hljs-attr">type</span>: <span class="hljs-string">'refresh'</span> },
      process.env.REFRESH_SECRET,
      { <span class="hljs-attr">expiresIn</span>: <span class="hljs-string">'7d'</span>, <span class="hljs-attr">algorithm</span>: <span class="hljs-string">'HS256'</span> }
    );

    <span class="hljs-keyword">return</span> { accessToken, refreshToken };
  }

  <span class="hljs-keyword">async</span> refreshAccessToken(refreshToken) {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET);

      <span class="hljs-comment">// Check if refresh token is blocklisted</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.isTokenBlocklisted(decoded.jti)) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Token has been revoked'</span>);
      }

      <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> getUserById(decoded.sub);
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.generateTokenPair(user);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Invalid refresh token'</span>);
    }
  }
}
</code></pre>
<p><code>generateTokenPair</code> will generate two signed JWTs – that is, an access token with a 15-minute expiration and a refresh token with a validity of 7 days. The refresh tokens are verified to grant new ones and are checked against a blocklist. This ensures that revoked tokens can’t be reused, even if they’re still technically valid.</p>
<p>If you choose, a sliding session can be implemented to reduce friction by renewing tokens for an active user without violating your expiration strategy.</p>
<p>Now, let's implement a <a target="_blank" href="https://stackoverflow.com/questions/48189866/sliding-session-on-web-api-request">sliding session</a> that automatically refreshes JWTs when they're close to expiring and the user is still active.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Sliding session implementation</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">extendSessionIfActive</span>(<span class="hljs-params">token</span>) </span>{
  <span class="hljs-keyword">const</span> decoded = jwt.decode(token);
  <span class="hljs-keyword">const</span> timeUntilExpiry = decoded.exp - <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Date</span>.now() / <span class="hljs-number">1000</span>);

  <span class="hljs-comment">// If token expires within 5 minutes and user is active, refresh</span>
  <span class="hljs-keyword">if</span> (timeUntilExpiry &lt; <span class="hljs-number">300</span> &amp;&amp; <span class="hljs-keyword">await</span> isUserActive(decoded.sub)) {
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> getUserById(decoded.sub);
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.generateTokenPair(user);
  }

  <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
}
</code></pre>
<p>The above function checks for token expiration. If the token expires within 5 minutes and the user continues to interact, a new access token pair is issued. This way, the session is kept alive during real activity but still forces expiration for idle users.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Token blocklist service</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TokenBlocklistService</span> </span>{
  <span class="hljs-keyword">async</span> blocklistToken(token) {
    <span class="hljs-keyword">const</span> decoded = jwt.decode(token);
    <span class="hljs-keyword">const</span> expiresAt = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(decoded.exp * <span class="hljs-number">1000</span>);

    <span class="hljs-comment">// Store in Redis with automatic expiry</span>
    <span class="hljs-keyword">await</span> redis.setex(
      <span class="hljs-string">`blocklist:<span class="hljs-subst">${decoded.jti}</span>`</span>,
      <span class="hljs-built_in">Math</span>.max(<span class="hljs-number">0</span>, <span class="hljs-built_in">Math</span>.floor((expiresAt - <span class="hljs-built_in">Date</span>.now()) / <span class="hljs-number">1000</span>)),
      <span class="hljs-string">'revoked'</span>
    );
  }

  <span class="hljs-keyword">async</span> isTokenBlocklisted(jti) {
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> redis.get(<span class="hljs-string">`blocklist:<span class="hljs-subst">${jti}</span>`</span>);
    <span class="hljs-keyword">return</span> result !== <span class="hljs-literal">null</span>;
  }
}
</code></pre>
<p>In the above code, when users log out or tokens are compromised, the <code>jti</code> is stored in <a target="_blank" href="https://redis.io/docs/latest/">Redis</a> with an expiration time of the remaining life of the token. You can block future uses of a token by checking if its ID exists on the blocklist. This allows for instant invalidation, even though JWTs are stateless.</p>
<h2 id="heading-session-security">Session Security</h2>
<p>In zero-trust environments, <a target="_blank" href="https://www.descope.com/learn/post/session-management">session management</a> goes far beyond keeping users logged in. A session must be treated as a constantly evaluated contract between the user, their device, and the system – and should be revoked the moment trust breaks down.</p>
<p>Here, we’ll build a session system that incorporates adaptive <a target="_blank" href="https://www.prove.com/blog/trust-score">trust scoring</a>, dynamic timeouts, real-time visibility, and <a target="_blank" href="https://www.researchgate.net/publication/354720916_Revocation_Mechanisms_for_Blockchain_Applications_A_Review">revocation mechanisms</a> – all aligned with zero-trust principles.</p>
<p>For example, when a user successfully authenticates, you don’t just store a session ID. Instead, you collect contextual metadata to evaluate ongoing risk. The function below demonstrates how to initialize a session that’s both secure and context-aware.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Comprehensive session creation</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createSecureSession</span>(<span class="hljs-params">userId, deviceInfo, clientInfo</span>) </span>{
  <span class="hljs-keyword">const</span> sessionId = generateSecureSessionId();

  <span class="hljs-keyword">const</span> session = {
    <span class="hljs-attr">id</span>: sessionId,
    <span class="hljs-attr">userId</span>: userId,
    <span class="hljs-attr">deviceFingerprint</span>: generateDeviceFingerprint(deviceInfo),
    <span class="hljs-attr">ipAddress</span>: clientInfo.ipAddress,
    <span class="hljs-attr">userAgent</span>: clientInfo.userAgent,
    <span class="hljs-attr">location</span>: <span class="hljs-keyword">await</span> resolveLocation(clientInfo.ipAddress),
    <span class="hljs-attr">createdAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
    <span class="hljs-attr">lastActivity</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
    <span class="hljs-attr">trustScore</span>: calculateInitialTrustScore(deviceInfo, clientInfo),
    <span class="hljs-attr">securityLevel</span>: determineSecurityLevel(userId, deviceInfo)
  };

  <span class="hljs-keyword">await</span> storeSession(session);
  <span class="hljs-keyword">return</span> session;
}
</code></pre>
<p>Many other tools are tracking concerning details during session creation. The device fingerprint, IP address, geolocation, and browser agent data are collected. These metadata are used to compute a trust score, and finally, a security level is assigned to the session to be used for dynamically adjusting policies later.</p>
<p>With this contextual information captured during session creation, the system can spot suspicious behavior during the sessions and, in turn, adapt policies like re-authentication of users or termination of the session.</p>
<p>Not all sessions should be treated equally. If a user logs in via an unfamiliar device or risky location, they should have less time for their session lifespan compared to a trusted setup's time. The following implementation changes timeout periods on the basis of trust and risk factors:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Adaptive session timeout</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SessionTimeoutManager</span> </span>{
  calculateTimeoutPeriod(session) {
    <span class="hljs-keyword">const</span> baseTimeout = <span class="hljs-number">30</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>; <span class="hljs-comment">// 30 minutes</span>
    <span class="hljs-keyword">const</span> trustMultiplier = session.trustScore / <span class="hljs-number">100</span>;
    <span class="hljs-keyword">const</span> securityMultiplier = <span class="hljs-built_in">this</span>.getSecurityMultiplier(session.securityLevel);

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.max(
      <span class="hljs-number">5</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>, <span class="hljs-comment">// Minimum 5 minutes</span>
      baseTimeout * trustMultiplier * securityMultiplier
    );
  }

  <span class="hljs-keyword">async</span> checkSessionValidity(sessionId) {
    <span class="hljs-keyword">const</span> session = <span class="hljs-keyword">await</span> getSession(sessionId);
    <span class="hljs-keyword">if</span> (!session) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;

    <span class="hljs-keyword">const</span> now = <span class="hljs-built_in">Date</span>.now();
    <span class="hljs-keyword">const</span> timeout = <span class="hljs-built_in">this</span>.calculateTimeoutPeriod(session);

    <span class="hljs-comment">// Check both idle timeout and absolute timeout</span>
    <span class="hljs-keyword">const</span> idleExpired = (now - session.lastActivity) &gt; timeout;
    <span class="hljs-keyword">const</span> absoluteExpired = (now - session.createdAt) &gt; <span class="hljs-number">8</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>; <span class="hljs-comment">// 8 hours max</span>

    <span class="hljs-keyword">return</span> !idleExpired &amp;&amp; !absoluteExpired;
  }
}
</code></pre>
<p>The above code keeps session duration adaptable to the risk context at hand. The timeout is calculated by adjusting the base value according to trust and security level, while imposing minimum and maximum bounds.</p>
<p>The system then periodically intervenes to see if the session has become invalid due to inactivity (idle timeout) or simply outlives its initial duration (absolute timeout). This provides a more flexible yet enforceable way of mitigating the risk behind stale or hijacked sessions.</p>
<p>Zero-trust should also mean visibility across all access points. The user should be able to view all active sessions associated with their account, and security systems should also allow them to control these sessions in fine-grained detail. The following code lets you manage those active sessions across devices.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Cross-device session management</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SessionManager</span> </span>{
  <span class="hljs-keyword">async</span> getUserSessions(userId) {
    <span class="hljs-keyword">const</span> sessions = <span class="hljs-keyword">await</span> getActiveSessionsForUser(userId);

    <span class="hljs-keyword">return</span> sessions.map(<span class="hljs-function"><span class="hljs-params">session</span> =&gt;</span> ({
      <span class="hljs-attr">id</span>: session.id,
      <span class="hljs-attr">deviceType</span>: <span class="hljs-built_in">this</span>.identifyDeviceType(session.userAgent),
      <span class="hljs-attr">location</span>: session.location,
      <span class="hljs-attr">lastActivity</span>: session.lastActivity,
      <span class="hljs-attr">current</span>: session.id === currentSessionId
    }));
  }

  <span class="hljs-keyword">async</span> revokeSession(sessionId, requestingSessionId) {
    <span class="hljs-keyword">const</span> session = <span class="hljs-keyword">await</span> getSession(sessionId);
    <span class="hljs-keyword">if</span> (!session) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Session not found'</span>);

    <span class="hljs-comment">// Verify requesting session has permission</span>
    <span class="hljs-keyword">const</span> requestingSession = <span class="hljs-keyword">await</span> getSession(requestingSessionId);
    <span class="hljs-keyword">if</span> (requestingSession.userId !== session.userId) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Unauthorized'</span>);
    }

    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.terminateSession(sessionId);
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.logSecurityEvent(<span class="hljs-string">'session_revoked'</span>, session);
  }
}
</code></pre>
<p>Here, users fetch a list of their active sessions along with identifying information such as device type and location. Any session can be securely revoked by the user who owns it, preventing unauthorized access if the session ID is compromised.</p>
<p>This also allows the user to detect suspicious activities in time. All revocations are logged for auditing purposes to enable post-incident investigations as well as compliance reports.</p>
<p>When a trust breaks due to credential theft, suspicious activity, or user-level actions such as password reset, all sessions have to be immediately revoked. This example guarantees a full revocation, promptly applied to all devices:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Real-time session revocation</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SessionRevocationService</span> </span>{
  <span class="hljs-keyword">async</span> revokeAllUserSessions(userId, reason) {
    <span class="hljs-keyword">const</span> sessions = <span class="hljs-keyword">await</span> getActiveSessionsForUser(userId);

    <span class="hljs-comment">// Blocklist all tokens for this user</span>
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all(sessions.map(<span class="hljs-function"><span class="hljs-params">session</span> =&gt;</span> 
      <span class="hljs-built_in">this</span>.blocklistSessionTokens(session.id)
    ));

    <span class="hljs-comment">// Notify all active clients</span>
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all(sessions.map(<span class="hljs-function"><span class="hljs-params">session</span> =&gt;</span> 
      <span class="hljs-built_in">this</span>.notifySessionTermination(session.id, reason)
    ));

    <span class="hljs-comment">// Clear session data</span>
    <span class="hljs-keyword">await</span> clearUserSessions(userId);

    <span class="hljs-comment">// Log security event</span>
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.logSecurityEvent(<span class="hljs-string">'all_sessions_revoked'</span>, {
      userId,
      reason,
      <span class="hljs-attr">sessionCount</span>: sessions.length
    });
  }
}
</code></pre>
<p>The above code permits full-scale revocation. It blocklists all session tokens, sends out termination notices to active clients (for example, through WebSockets), clears the session records on the server-side, and logs the event for auditing. It is an instantaneous and complete response to compromised accounts or states where user risk is very high. It is the foremost component of real-time zero-trust enforcement in any serious authentication system.</p>
<h2 id="heading-role-based-access-control-rbac">Role-Based Access Control (RBAC)</h2>
<p>Identity verification determines what users can access once they’re logged in. As the basis for any system that is aware of permissions and follows least privilege, <a target="_blank" href="https://en.wikipedia.org/wiki/Role-based_access_control">RBAC</a> doesn’t grant access on an individual basis – it groups users into roles that define the operations they are permitted to perform.</p>
<p>Before assigning roles to users, you need a structured system to define what each role can do. A set of granular permissions is first identified and then aggregated under these roles, optionally allowing inheritance and hierarchy. The code below shows how to build a basic permission system:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// RBAC permission system</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PermissionSystem</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>.permissions = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
    <span class="hljs-built_in">this</span>.roles = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
    <span class="hljs-built_in">this</span>.roleHierarchy = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
  }

  <span class="hljs-comment">// Define granular permissions</span>
  definePermission(name, description, resource, action) {
    <span class="hljs-built_in">this</span>.permissions.set(name, {
      name,
      description,
      resource,
      action,
      <span class="hljs-attr">createdAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
    });
  }

  <span class="hljs-comment">// Create role with inherited permissions</span>
  createRole(name, description, parentRole = <span class="hljs-literal">null</span>) {
    <span class="hljs-keyword">const</span> role = {
      name,
      description,
      <span class="hljs-attr">permissions</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(),
      <span class="hljs-attr">createdAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
    };

    <span class="hljs-comment">// Inherit permissions from parent role</span>
    <span class="hljs-keyword">if</span> (parentRole &amp;&amp; <span class="hljs-built_in">this</span>.roles.has(parentRole)) {
      <span class="hljs-keyword">const</span> parent = <span class="hljs-built_in">this</span>.roles.get(parentRole);
      role.permissions = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(parent.permissions);
      <span class="hljs-built_in">this</span>.roleHierarchy.set(name, parentRole);
    }

    <span class="hljs-built_in">this</span>.roles.set(name, role);
    <span class="hljs-keyword">return</span> role;
  }

  <span class="hljs-comment">// Add permission to role</span>
  addPermissionToRole(roleName, permissionName) {
    <span class="hljs-keyword">const</span> role = <span class="hljs-built_in">this</span>.roles.get(roleName);
    <span class="hljs-keyword">if</span> (!role) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Role not found'</span>);

    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.permissions.has(permissionName)) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Permission not found'</span>);
    }

    role.permissions.add(permissionName);
  }
}
</code></pre>
<p>The code above lets you specify fine-grained permissions like <code>documents.read.own</code> and organizes them into roles such as <code>employee</code> or <code>manager</code> that you can independently reuse. You can define roles to inherit from other roles, which avoids redundancy and promotes a consistent, scalable access control logic.</p>
<p>As a general rule to avoid privilege creep, permissions should always be as fine-grained as possible. This lets the application refine access decisions to specific actions or scopes: for example, allowing users to read only their documents versus reading all documents for their team.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Fine-grained permission definitions</span>
<span class="hljs-keyword">const</span> permissions = {
  <span class="hljs-comment">// User management</span>
  <span class="hljs-string">'users.read'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'users'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'read'</span> },
  <span class="hljs-string">'users.create'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'users'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'create'</span> },
  <span class="hljs-string">'users.update'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'users'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'update'</span> },
  <span class="hljs-string">'users.delete'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'users'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'delete'</span> },

  <span class="hljs-comment">// Document management</span>
  <span class="hljs-string">'documents.read.own'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'documents'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'read'</span>, <span class="hljs-attr">scope</span>: <span class="hljs-string">'own'</span> },
  <span class="hljs-string">'documents.read.team'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'documents'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'read'</span>, <span class="hljs-attr">scope</span>: <span class="hljs-string">'team'</span> },
  <span class="hljs-string">'documents.read.all'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'documents'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'read'</span>, <span class="hljs-attr">scope</span>: <span class="hljs-string">'all'</span> },
  <span class="hljs-string">'documents.create'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'documents'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'create'</span> },
  <span class="hljs-string">'documents.update.own'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'documents'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'update'</span>, <span class="hljs-attr">scope</span>: <span class="hljs-string">'own'</span> },
  <span class="hljs-string">'documents.delete.own'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'documents'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'delete'</span>, <span class="hljs-attr">scope</span>: <span class="hljs-string">'own'</span> },

  <span class="hljs-comment">// System administration</span>
  <span class="hljs-string">'system.logs.read'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'system'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'read'</span>, <span class="hljs-attr">subresource</span>: <span class="hljs-string">'logs'</span> },
  <span class="hljs-string">'system.config.update'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'system'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'update'</span>, <span class="hljs-attr">subresource</span>: <span class="hljs-string">'config'</span> }
};
</code></pre>
<p>With an array of permissions at its disposal, the app can undertake very precise access control decisions. Instead of merely addressing the binary "is admin" question, this capability enables the system to answer questions such as "can this user delete their own document but not others?"</p>
<p>Static roles are often insufficient. You may want to give people temporary or conditional access, for example, when the team lead takes over for a manager or when a user approves a higher access level for the sake of incident response.</p>
<p>To support these cases, the RBAC system must allow dynamic role assignment – that is, the ability to assign roles on the basis of time, context, or an external trigger such as a security workflow.</p>
<p>The code below assigns a temporary role to a user, notes the exact time at which the role was assigned to the user, and periodically revokes the right after some fixed amount of time. Also, it has a method to calculate a user's complete set of active rights, depending on their permanent rights, temporary rights, and role-based contextual rights.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Dynamic role assignment system</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DynamicRoleAssignment</span> </span>{
  <span class="hljs-keyword">async</span> assignTemporaryRole(userId, roleName, duration, reason) {
    <span class="hljs-keyword">const</span> assignment = {
      userId,
      roleName,
      <span class="hljs-attr">assignedAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
      <span class="hljs-attr">expiresAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now() + duration * <span class="hljs-number">1000</span>),
      reason,
      <span class="hljs-attr">active</span>: <span class="hljs-literal">true</span>
    };

    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.storeRoleAssignment(assignment);
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.logRoleAssignment(assignment);

    <span class="hljs-comment">// Schedule automatic revocation</span>
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">this</span>.revokeExpiredAssignment(assignment.id);
    }, duration * <span class="hljs-number">1000</span>);

    <span class="hljs-keyword">return</span> assignment;
  }

  <span class="hljs-keyword">async</span> getUserEffectivePermissions(userId, context = {}) {
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> getUserById(userId);
    <span class="hljs-keyword">const</span> permanentRoles = user.roles || [];
    <span class="hljs-keyword">const</span> temporaryRoles = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getActiveTemporaryRoles(userId);
    <span class="hljs-keyword">const</span> contextualRoles = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getContextualRoles(userId, context);

    <span class="hljs-keyword">const</span> allRoles = [...permanentRoles, ...temporaryRoles, ...contextualRoles];
    <span class="hljs-keyword">const</span> permissions = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>();

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> roleName <span class="hljs-keyword">of</span> allRoles) {
      <span class="hljs-keyword">const</span> rolePermissions = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getRolePermissions(roleName);
      rolePermissions.forEach(<span class="hljs-function"><span class="hljs-params">permission</span> =&gt;</span> permissions.add(permission));
    }

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>.from(permissions);
  }
}
</code></pre>
<p>This allows for more flexible security configurations. Temporary roles that are granted have an automatic expiration. The context roles may be added dynamically depending on contextual factors such as location or type of device. Permanent roles are combined with temporary and context roles to compute the aggregate permission set for the user on a per-request basis, which maintains flexibility without compromising control.</p>
<h3 id="heading-using-middleware-to-enforce-rbac">Using Middleware to Enforce RBAC</h3>
<p>The RBAC policies have to be enforced before any request reaches a protected route or protected data. <a target="_blank" href="https://aws.amazon.com/what-is/middleware/">Middleware</a> is a good place to run such checks in the scope of a web application. We’ll now look into how the reusable middleware function for authorization works.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Authorization middleware</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createAuthorizationMiddleware</span>(<span class="hljs-params">requiredPermission</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">async</span> (req, res, next) =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Extract user from validated JWT</span>
      <span class="hljs-keyword">const</span> user = req.user;
      <span class="hljs-keyword">if</span> (!user) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Authentication required'</span> });
      }

      <span class="hljs-comment">// Get user's effective permissions</span>
      <span class="hljs-keyword">const</span> context = {
        <span class="hljs-attr">ipAddress</span>: req.ip,
        <span class="hljs-attr">userAgent</span>: req.get(<span class="hljs-string">'User-Agent'</span>),
        <span class="hljs-attr">resourceId</span>: req.params.id,
        <span class="hljs-attr">timestamp</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
      };

      <span class="hljs-keyword">const</span> permissions = <span class="hljs-keyword">await</span> roleSystem.getUserEffectivePermissions(
        user.id,
        context
      );

      <span class="hljs-comment">// Check if user has required permission</span>
      <span class="hljs-keyword">if</span> (!permissions.includes(requiredPermission)) {
        <span class="hljs-keyword">await</span> logUnauthorizedAccess(user.id, requiredPermission, context);
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">403</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Insufficient permissions'</span> });
      }

      <span class="hljs-comment">// Add permissions to request for downstream use</span>
      req.userPermissions = permissions;
      next();
    } <span class="hljs-keyword">catch</span> (error) {
      res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Authorization check failed'</span> });
    }
  };
}

<span class="hljs-comment">// Usage in routes</span>
app.get(<span class="hljs-string">'/api/users'</span>, 
  authenticateToken,
  createAuthorizationMiddleware(<span class="hljs-string">'users.read'</span>),
  getUsersController
);
</code></pre>
<p>In the code above, the middleware will validate user identities in real-time, check if adequate permissions are granted, and allow or deny access accordingly. It’s a central mechanism for enforcing access rules in a uniform way across your routes, and it even records unauthorized attempts for auditing.</p>
<h3 id="heading-testing-access-control-logic">Testing Access Control Logic</h3>
<p>Once you’ve implemented the RBAC system, testing becomes a must. You want to guarantee that permissions are inherited properly, that access is actually denied when a user isn’t authorized, and that your roles behave as designed in the real world as well as in edge-case scenarios.</p>
<p>The following example uses a testing framework to demonstrate the verification of two fundamental behaviors: inheritance of permissions from parent roles and rejection of unauthorized access.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// RBAC testing suite</span>
describe(<span class="hljs-string">'RBAC System'</span>, <span class="hljs-function">() =&gt;</span> {
  test(<span class="hljs-string">'should inherit permissions from parent roles'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> manager = <span class="hljs-keyword">await</span> roleSystem.createRole(<span class="hljs-string">'manager'</span>, <span class="hljs-string">'Team Manager'</span>, <span class="hljs-string">'employee'</span>);
    <span class="hljs-keyword">await</span> roleSystem.addPermissionToRole(<span class="hljs-string">'manager'</span>, <span class="hljs-string">'team.manage'</span>);

    <span class="hljs-keyword">const</span> permissions = <span class="hljs-keyword">await</span> roleSystem.getRolePermissions(<span class="hljs-string">'manager'</span>);
    expect(permissions).toContain(<span class="hljs-string">'documents.read.own'</span>); <span class="hljs-comment">// From employee</span>
    expect(permissions).toContain(<span class="hljs-string">'team.manage'</span>); <span class="hljs-comment">// Manager-specific</span>
  });

  test(<span class="hljs-string">'should deny access without proper permissions'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> user = { <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">roles</span>: [<span class="hljs-string">'employee'</span>] };
    <span class="hljs-keyword">const</span> req = { user, <span class="hljs-attr">params</span>: { <span class="hljs-attr">id</span>: <span class="hljs-string">'doc123'</span> } };
    <span class="hljs-keyword">const</span> res = { <span class="hljs-attr">status</span>: jest.fn().mockReturnThis(), <span class="hljs-attr">json</span>: jest.fn() };

    <span class="hljs-keyword">const</span> middleware = createAuthorizationMiddleware(<span class="hljs-string">'documents.delete.all'</span>);
    <span class="hljs-keyword">await</span> middleware(req, res, <span class="hljs-function">() =&gt;</span> {}); <span class="hljs-comment">// Middleware call simulating request</span>

    expect(res.status).toHaveBeenCalledWith(<span class="hljs-number">403</span>);
  });
});
</code></pre>
<p>The tests represent the positive and negative validations of the access rules. The first test determines whether inherited permissions flow freely from the parent to child roles. The second test blocks any user without the required permission, returning a status code appropriately.</p>
<p>Over time, you can enrich test coverage to include temporary role assignments, contextual conditions, and session-aware behavior to alert you to any regressions before they start affecting production access.</p>
<h2 id="heading-continuous-verification">Continuous Verification</h2>
<p>Modern access security is not a one-shot check but an ongoing process. A strong system must continuously verify user identity and context throughout the ongoing session while adapting to newly emerging risk signals.</p>
<p>In <a target="_blank" href="https://spot.io/resources/gitops/continuous-verification/">continuous verification</a>, it’s an assurance that access stays appropriate while the user behavior, device posture, or environment changes mid-session.</p>
<p>To uniquely identify a device, you can combine subtle traits like browser settings, hardware specs, and plugin data. This forms a device “fingerprint,” which helps flag new or suspicious devices attempting access.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Advanced device fingerprinting</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DeviceFingerprintService</span> </span>{
  generateFingerprint(deviceInfo) {
    <span class="hljs-keyword">const</span> components = [
      deviceInfo.userAgent,
      deviceInfo.screenResolution,
      deviceInfo.timezone,
      deviceInfo.language,
      deviceInfo.platform,
      deviceInfo.hardwareConcurrency,
      deviceInfo.memorySize,
      deviceInfo.availableFonts?.join(<span class="hljs-string">','</span>),
      deviceInfo.plugins?.map(<span class="hljs-function"><span class="hljs-params">p</span> =&gt;</span> p.name).join(<span class="hljs-string">','</span>),
      deviceInfo.webglRenderer,
      deviceInfo.audioContext
    ];

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.hashComponents(components);
  }

  calculateTrustScore(currentFingerprint, knownFingerprints) {
    <span class="hljs-keyword">if</span> (knownFingerprints.length === <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">50</span>; <span class="hljs-comment">// Neutral for new device</span>
    <span class="hljs-keyword">const</span> similarities = knownFingerprints.map(<span class="hljs-function"><span class="hljs-params">known</span> =&gt;</span>
      <span class="hljs-built_in">this</span>.calculateSimilarity(currentFingerprint, known)
    );
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.min(<span class="hljs-number">100</span>, <span class="hljs-built_in">Math</span>.max(...similarities) * <span class="hljs-number">100</span>);
  }

  <span class="hljs-keyword">async</span> updateDeviceTrust(userId, deviceFingerprint, securityEvents) {
    <span class="hljs-keyword">const</span> device = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getOrCreateDevice(userId, deviceFingerprint);
    <span class="hljs-keyword">let</span> trustAdjustment = <span class="hljs-number">0</span>;

    securityEvents.forEach(<span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
      <span class="hljs-keyword">switch</span> (event.type) {
        <span class="hljs-keyword">case</span> <span class="hljs-string">'successful_login'</span>: trustAdjustment += <span class="hljs-number">5</span>; <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">case</span> <span class="hljs-string">'failed_login'</span>: trustAdjustment -= <span class="hljs-number">10</span>; <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">case</span> <span class="hljs-string">'suspicious_activity'</span>: trustAdjustment -= <span class="hljs-number">25</span>; <span class="hljs-keyword">break</span>;
      }
    });

    device.trustScore = <span class="hljs-built_in">Math</span>.max(<span class="hljs-number">0</span>, <span class="hljs-built_in">Math</span>.min(<span class="hljs-number">100</span>, device.trustScore + trustAdjustment));
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.updateDevice(device);
    <span class="hljs-keyword">return</span> device.trustScore;
  }
}
</code></pre>
<p>Generating a fingerprint hash from device traits, this service uses historical events to dynamically adjust the device's trust score. Step-up authentication may be prompted by low scores, or access may be denied altogether.</p>
<h3 id="heading-behavioral-analysis">Behavioral Analysis</h3>
<p>People tend to use apps rather consistently – they type a certain way, move the mouse in a particular manner, or browse varied content. <a target="_blank" href="https://zimperium.com/glossary/behavioral-analysis">Behavioral analysis</a> tries to detect that anomaly by comparing ongoing activities to known ones.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Behavioral analysis system</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BehaviorAnalysisService</span> </span>{
  <span class="hljs-keyword">async</span> analyzeUserBehavior(userId, currentSession) {
    <span class="hljs-keyword">const</span> historicalBehavior = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getUserBehaviorProfile(userId);
    <span class="hljs-keyword">const</span> anomalies = [];

    <span class="hljs-keyword">const</span> typingAnomaly = <span class="hljs-built_in">this</span>.analyzeTypingPatterns(
      currentSession.typingData,
      historicalBehavior.typingProfile
    );
    <span class="hljs-keyword">if</span> (typingAnomaly.score &gt; <span class="hljs-number">0.7</span>) {
      anomalies.push({ <span class="hljs-attr">type</span>: <span class="hljs-string">'typing_pattern'</span>, <span class="hljs-attr">score</span>: typingAnomaly.score, <span class="hljs-attr">details</span>: typingAnomaly.details });
    }

    <span class="hljs-keyword">const</span> navigationAnomaly = <span class="hljs-built_in">this</span>.analyzeNavigationPatterns(
      currentSession.navigationData,
      historicalBehavior.navigationProfile
    );
    <span class="hljs-keyword">if</span> (navigationAnomaly.score &gt; <span class="hljs-number">0.6</span>) {
      anomalies.push({ <span class="hljs-attr">type</span>: <span class="hljs-string">'navigation_pattern'</span>, <span class="hljs-attr">score</span>: navigationAnomaly.score, <span class="hljs-attr">details</span>: navigationAnomaly.details });
    }

    <span class="hljs-keyword">const</span> timeAnomaly = <span class="hljs-built_in">this</span>.analyzeTimePatterns(
      currentSession.timestamp,
      historicalBehavior.timeProfile
    );
    <span class="hljs-keyword">if</span> (timeAnomaly.score &gt; <span class="hljs-number">0.5</span>) {
      anomalies.push({ <span class="hljs-attr">type</span>: <span class="hljs-string">'time_pattern'</span>, <span class="hljs-attr">score</span>: timeAnomaly.score, <span class="hljs-attr">details</span>: timeAnomaly.details });
    }

    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">overallRiskScore</span>: <span class="hljs-built_in">this</span>.calculateOverallRisk(anomalies),
      anomalies,
      <span class="hljs-attr">recommendations</span>: <span class="hljs-built_in">this</span>.generateRecommendations(anomalies)
    };
  }

  analyzeTypingPatterns(currentData, historicalProfile) {
    <span class="hljs-keyword">if</span> (!currentData || !historicalProfile) <span class="hljs-keyword">return</span> { <span class="hljs-attr">score</span>: <span class="hljs-number">0</span> };
    <span class="hljs-keyword">const</span> dwellTimeVariance = <span class="hljs-built_in">this</span>.calculateVariance(currentData.dwellTimes, historicalProfile.averageDwellTime);
    <span class="hljs-keyword">const</span> flightTimeVariance = <span class="hljs-built_in">this</span>.calculateVariance(currentData.flightTimes, historicalProfile.averageFlightTime);
    <span class="hljs-keyword">const</span> score = <span class="hljs-built_in">Math</span>.max(dwellTimeVariance, flightTimeVariance);
    <span class="hljs-keyword">return</span> { score, <span class="hljs-attr">details</span>: { dwellTimeVariance, flightTimeVariance, <span class="hljs-attr">sampleSize</span>: currentData.keystrokes.length } };
  }
}
</code></pre>
<p>This will detect suspicious changes in user behavior and typing characteristics as early warning indicators of session hijacking or insider threat.</p>
<p>Access from a new country or city can either be harmless or highly suspicious. Comparing login geography against historical patterns helps flag impossible travel or access from banned regions.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Location-based access control</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LocationAccessControl</span> </span>{
  <span class="hljs-keyword">async</span> validateLocationAccess(userId, ipAddress, session) {
    <span class="hljs-keyword">const</span> location = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.resolveLocation(ipAddress);
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> getUserById(userId);
    <span class="hljs-keyword">const</span> historicalLocations = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getUserLocations(userId);
    <span class="hljs-keyword">const</span> locationRisk = <span class="hljs-built_in">this</span>.assessLocationRisk(location, historicalLocations);

    <span class="hljs-keyword">const</span> lastLocation = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getLastKnownLocation(userId);
    <span class="hljs-keyword">if</span> (lastLocation) {
      <span class="hljs-keyword">const</span> impossibleTravel = <span class="hljs-built_in">this</span>.checkImpossibleTravel(lastLocation, location, session.lastActivity);
      <span class="hljs-keyword">if</span> (impossibleTravel.detected) {
        <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.logSecurityEvent(<span class="hljs-string">'impossible_travel'</span>, {
          userId, <span class="hljs-attr">fromLocation</span>: lastLocation, <span class="hljs-attr">toLocation</span>: location,
          <span class="hljs-attr">timeWindow</span>: impossibleTravel.timeWindow,
          <span class="hljs-attr">minimumTravelTime</span>: impossibleTravel.minimumTravelTime
        });
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">allowed</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">reason</span>: <span class="hljs-string">'impossible_travel'</span>, <span class="hljs-attr">requiresStepUp</span>: <span class="hljs-literal">true</span> };
      }
    }

    <span class="hljs-keyword">if</span> (user.allowedCountries &amp;&amp; !user.allowedCountries.includes(location.country)) {
      <span class="hljs-keyword">return</span> { <span class="hljs-attr">allowed</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">reason</span>: <span class="hljs-string">'country_restriction'</span>, <span class="hljs-attr">requiresStepUp</span>: <span class="hljs-literal">true</span> };
    }

    <span class="hljs-keyword">const</span> highRiskCountries = [<span class="hljs-string">'XX'</span>, <span class="hljs-string">'YY'</span>, <span class="hljs-string">'ZZ'</span>];
    <span class="hljs-keyword">if</span> (highRiskCountries.includes(location.country)) {
      <span class="hljs-keyword">return</span> { <span class="hljs-attr">allowed</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">reason</span>: <span class="hljs-string">'high_risk_location'</span>, <span class="hljs-attr">requiresStepUp</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">additionalVerification</span>: [<span class="hljs-string">'sms'</span>, <span class="hljs-string">'email'</span>] };
    }

    <span class="hljs-keyword">return</span> { <span class="hljs-attr">allowed</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">riskScore</span>: locationRisk, location };
  }

  checkImpossibleTravel(fromLocation, toLocation, lastActivity) {
    <span class="hljs-keyword">const</span> distance = <span class="hljs-built_in">this</span>.calculateDistance(fromLocation, toLocation);
    <span class="hljs-keyword">const</span> timeElapsed = <span class="hljs-built_in">Date</span>.now() - lastActivity;
    <span class="hljs-keyword">const</span> maximumSpeed = <span class="hljs-number">900</span>; <span class="hljs-comment">// km/h</span>
    <span class="hljs-keyword">const</span> minimumTravelTime = (distance / maximumSpeed) * <span class="hljs-number">3600000</span>;
    <span class="hljs-keyword">return</span> { <span class="hljs-attr">detected</span>: timeElapsed &lt; minimumTravelTime, <span class="hljs-attr">timeWindow</span>: timeElapsed, minimumTravelTime, distance };
  }
}
</code></pre>
<p>This logic prevents abuse via VPNs or stolen credentials by requiring step-up verification when impossible travel or unusual locations are detected.</p>
<h3 id="heading-step-up-authentication">Step-Up Authentication</h3>
<p><a target="_blank" href="https://doubleoctopus.com/security-wiki/authentication/step-up-authentication/">Step-up security</a> introduces friction only when truly needed. With lower risk considered, users move freely. When risk levels rises, they're asked for stronger proofs, such as biometrics or hardware tokens.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Step-up authentication system</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StepUpAuthenticationService</span> </span>{
  <span class="hljs-keyword">async</span> evaluateStepUpRequirement(userId, requestContext, resourceSensitivity) {
    <span class="hljs-keyword">const</span> riskFactors = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.calculateRiskFactors(userId, requestContext);
    <span class="hljs-keyword">const</span> stepUpRequired = <span class="hljs-built_in">this</span>.shouldRequireStepUp(riskFactors, resourceSensitivity);

    <span class="hljs-keyword">if</span> (stepUpRequired.required) {
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">methods</span>: <span class="hljs-built_in">this</span>.selectAuthenticationMethods(riskFactors, stepUpRequired.level),
        <span class="hljs-attr">expiresIn</span>: <span class="hljs-built_in">this</span>.calculateStepUpDuration(stepUpRequired.level),
        <span class="hljs-attr">reason</span>: stepUpRequired.reason
      };
    }

    <span class="hljs-keyword">return</span> { <span class="hljs-attr">required</span>: <span class="hljs-literal">false</span> };
  }

  <span class="hljs-keyword">async</span> calculateRiskFactors(userId, context) {
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">deviceTrust</span>: <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getDeviceTrustScore(userId, context.deviceFingerprint),
      <span class="hljs-attr">locationRisk</span>: <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getLocationRiskScore(userId, context.ipAddress),
      <span class="hljs-attr">behaviorAnomaly</span>: <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getBehaviorAnomalyScore(userId, context.sessionData),
      <span class="hljs-attr">timeSinceLastAuth</span>: <span class="hljs-built_in">Date</span>.now() - context.lastAuthTime,
      <span class="hljs-attr">resourceSensitivity</span>: context.resourceSensitivity || <span class="hljs-string">'medium'</span>
    };
  }

  shouldRequireStepUp(riskFactors, sensitivity) {
    <span class="hljs-keyword">let</span> score = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">if</span> (riskFactors.deviceTrust &lt; <span class="hljs-number">70</span>) score += <span class="hljs-number">30</span>;
    <span class="hljs-keyword">if</span> (riskFactors.deviceTrust &lt; <span class="hljs-number">40</span>) score += <span class="hljs-number">20</span>;
    <span class="hljs-keyword">if</span> (riskFactors.locationRisk &gt; <span class="hljs-number">0.6</span>) score += <span class="hljs-number">25</span>;
    <span class="hljs-keyword">if</span> (riskFactors.locationRisk &gt; <span class="hljs-number">0.8</span>) score += <span class="hljs-number">15</span>;
    <span class="hljs-keyword">if</span> (riskFactors.behaviorAnomaly &gt; <span class="hljs-number">0.5</span>) score += <span class="hljs-number">20</span>;
    <span class="hljs-keyword">if</span> (riskFactors.behaviorAnomaly &gt; <span class="hljs-number">0.7</span>) score += <span class="hljs-number">10</span>;
    <span class="hljs-keyword">const</span> hours = riskFactors.timeSinceLastAuth / (<span class="hljs-number">1000</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span>);
    <span class="hljs-keyword">if</span> (hours &gt; <span class="hljs-number">8</span>) score += <span class="hljs-number">10</span>;
    <span class="hljs-keyword">if</span> (hours &gt; <span class="hljs-number">24</span>) score += <span class="hljs-number">15</span>;

    score *= { <span class="hljs-attr">low</span>: <span class="hljs-number">0.7</span>, <span class="hljs-attr">medium</span>: <span class="hljs-number">1.0</span>, <span class="hljs-attr">high</span>: <span class="hljs-number">1.3</span>, <span class="hljs-attr">critical</span>: <span class="hljs-number">1.6</span> }[sensitivity] || <span class="hljs-number">1.0</span>;

    <span class="hljs-keyword">if</span> (score &gt;= <span class="hljs-number">80</span>) <span class="hljs-keyword">return</span> { <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">level</span>: <span class="hljs-string">'high'</span>, <span class="hljs-attr">reason</span>: <span class="hljs-string">'high_risk_detected'</span> };
    <span class="hljs-keyword">if</span> (score &gt;= <span class="hljs-number">50</span>) <span class="hljs-keyword">return</span> { <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">level</span>: <span class="hljs-string">'medium'</span>, <span class="hljs-attr">reason</span>: <span class="hljs-string">'moderate_risk_detected'</span> };
    <span class="hljs-keyword">if</span> (score &gt;= <span class="hljs-number">25</span>) <span class="hljs-keyword">return</span> { <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">level</span>: <span class="hljs-string">'low'</span>, <span class="hljs-attr">reason</span>: <span class="hljs-string">'low_risk_detected'</span> };
    <span class="hljs-keyword">return</span> { <span class="hljs-attr">required</span>: <span class="hljs-literal">false</span> };
  }

  selectAuthenticationMethods(riskFactors, level) {
    <span class="hljs-keyword">const</span> methods = [];
    <span class="hljs-keyword">if</span> (level === <span class="hljs-string">'high'</span>) {
      methods.push(<span class="hljs-string">'hardware_token'</span>, <span class="hljs-string">'biometric'</span>);
      <span class="hljs-keyword">if</span> (riskFactors.deviceTrust &lt; <span class="hljs-number">30</span>) methods.push(<span class="hljs-string">'admin_approval'</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (level === <span class="hljs-string">'medium'</span>) {
      methods.push(<span class="hljs-string">'totp'</span>, <span class="hljs-string">'sms'</span>);
      <span class="hljs-keyword">if</span> (riskFactors.locationRisk &gt; <span class="hljs-number">0.7</span>) methods.push(<span class="hljs-string">'email_verification'</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (level === <span class="hljs-string">'low'</span>) {
      methods.push(<span class="hljs-string">'totp'</span>);
    }
    <span class="hljs-keyword">return</span> methods;
  }
}
</code></pre>
<p>The service uses this balancing technique between critical resources and risks while keeping normal workflows intact when things look safe.</p>
<h2 id="heading-security-monitoring">Security Monitoring</h2>
<p>Security monitoring provides the observability layer that’s essential for detecting, analyzing, and responding to threats in real time. A strong system must log every authentication event, highlight anomalies, and allow for rapid and automated response to threats. This phase further builds trust by constantly evaluating access patterns and acting on them when signals of risk emerge.</p>
<p>Logging is visibility at its base. These days, every authentication attempt, be it successful, failed, or suspicious, needs to be logged with exhaustive context. This very information helps forensic analysis, alerting, and compliance reporting.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Comprehensive authentication event logging</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthenticationLogger</span> </span>{
  <span class="hljs-keyword">async</span> logAuthenticationEvent(eventType, userId, context, result) {
    <span class="hljs-keyword">const</span> logEntry = {
      <span class="hljs-attr">timestamp</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString(),
      eventType,
      userId,
      <span class="hljs-attr">sessionId</span>: context.sessionId,
      <span class="hljs-attr">ipAddress</span>: context.ipAddress,
      <span class="hljs-attr">userAgent</span>: context.userAgent,
      <span class="hljs-attr">deviceFingerprint</span>: context.deviceFingerprint,
      <span class="hljs-attr">location</span>: context.location,
      <span class="hljs-attr">authenticationMethod</span>: context.authMethod,
      <span class="hljs-attr">result</span>: result.success ? <span class="hljs-string">'success'</span> : <span class="hljs-string">'failure'</span>,
      <span class="hljs-attr">failureReason</span>: result.failureReason,
      <span class="hljs-attr">riskScore</span>: result.riskScore,
      <span class="hljs-attr">additionalFactorsRequired</span>: result.stepUpRequired,
      <span class="hljs-attr">processingTime</span>: result.processingTime,
      <span class="hljs-attr">correlationId</span>: context.correlationId
    };

    <span class="hljs-comment">// Store in multiple destinations for redundancy</span>
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all([
      <span class="hljs-built_in">this</span>.writeToDatabase(logEntry),
      <span class="hljs-built_in">this</span>.sendToLogAggregator(logEntry),
      <span class="hljs-built_in">this</span>.updateRealTimeMetrics(logEntry)
    ]);

    <span class="hljs-comment">// Trigger real-time alerts for critical events</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.isCriticalEvent(logEntry)) {
      <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.triggerSecurityAlert(logEntry);
    }
  }

  isCriticalEvent(logEntry) {
    <span class="hljs-keyword">const</span> criticalConditions = [
      logEntry.result === <span class="hljs-string">'failure'</span> &amp;&amp; logEntry.failureReason === <span class="hljs-string">'brute_force_detected'</span>,
      logEntry.riskScore &gt; <span class="hljs-number">80</span>,
      logEntry.eventType === <span class="hljs-string">'impossible_travel_detected'</span>,
      logEntry.eventType === <span class="hljs-string">'account_takeover_suspected'</span>
    ];

    <span class="hljs-keyword">return</span> criticalConditions.some(<span class="hljs-function"><span class="hljs-params">condition</span> =&gt;</span> condition);
  }

  <span class="hljs-keyword">async</span> generateSecurityReport(userId, timeRange) {
    <span class="hljs-keyword">const</span> events = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getAuthenticationEvents(userId, timeRange);

    <span class="hljs-keyword">const</span> analysis = {
      <span class="hljs-attr">totalEvents</span>: events.length,
      <span class="hljs-attr">successfulLogins</span>: events.filter(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> e.result === <span class="hljs-string">'success'</span>).length,
      <span class="hljs-attr">failedAttempts</span>: events.filter(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> e.result === <span class="hljs-string">'failure'</span>).length,
      <span class="hljs-attr">uniqueDevices</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(events.map(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> e.deviceFingerprint)).size,
      <span class="hljs-attr">uniqueLocations</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(events.map(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> e.location?.country)).size,
      <span class="hljs-attr">averageRiskScore</span>: events.reduce(<span class="hljs-function">(<span class="hljs-params">sum, e</span>) =&gt;</span> sum + e.riskScore, <span class="hljs-number">0</span>) / events.length,
      <span class="hljs-attr">timePatterns</span>: <span class="hljs-built_in">this</span>.analyzeTimePatterns(events),
      <span class="hljs-attr">locationPatterns</span>: <span class="hljs-built_in">this</span>.analyzeLocationPatterns(events),
      <span class="hljs-attr">devicePatterns</span>: <span class="hljs-built_in">this</span>.analyzeDevicePatterns(events)
    };

    <span class="hljs-keyword">return</span> analysis;
  }
}
</code></pre>
<p>In the above code, the class logs detailed authentication events such as the approximate device and location from which it was initiated, the authentication methods used, and the risk score.</p>
<p>From a security perspective, it’s envisaged to generate security reports with the advantage of flagging critical events such as brute-force attempts or logins from suspicious geographies that can send real-time alerts.</p>
<p>Monitoring authentication events isn’t enough – the system must be able to interpret patterns and flag suspicious behavior. This detection system combines static rule-based checks with dynamic anomaly detection powered by machine learning. It identifies threats like brute-force attacks, credential stuffing, and unusual geographic access, then escalates them automatically for further action.</p>
<p>The following code performs real-time threat detection by analyzing recent authentication events and contextual data. Here's what it does, broken down clearly:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Suspicious activity detection system</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SuspiciousActivityDetector</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>.detectionRules = <span class="hljs-built_in">this</span>.initializeDetectionRules();
    <span class="hljs-built_in">this</span>.mlModel = <span class="hljs-built_in">this</span>.loadAnomalyDetectionModel();
  }

  <span class="hljs-keyword">async</span> analyzeActivity(userId, recentEvents, context) {
    <span class="hljs-keyword">const</span> suspiciousPatterns = [];

    <span class="hljs-comment">// Rule-based detection</span>
    <span class="hljs-keyword">const</span> ruleViolations = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.checkDetectionRules(userId, recentEvents);
    suspiciousPatterns.push(...ruleViolations);

    <span class="hljs-comment">// ML-based anomaly detection</span>
    <span class="hljs-keyword">const</span> anomalies = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.detectAnomalies(userId, recentEvents, context);
    suspiciousPatterns.push(...anomalies);

    <span class="hljs-comment">// Threat intelligence correlation</span>
    <span class="hljs-keyword">const</span> threatMatches = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.correlateThreatIntelligence(context);
    suspiciousPatterns.push(...threatMatches);

    <span class="hljs-keyword">if</span> (suspiciousPatterns.length &gt; <span class="hljs-number">0</span>) {
      <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.escalateSuspiciousActivity(userId, suspiciousPatterns);
    }

    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">suspicious</span>: suspiciousPatterns.length &gt; <span class="hljs-number">0</span>,
      <span class="hljs-attr">patterns</span>: suspiciousPatterns,
      <span class="hljs-attr">riskScore</span>: <span class="hljs-built_in">this</span>.calculateSuspiciousActivityRisk(suspiciousPatterns)
    };
  }

  initializeDetectionRules() {
    <span class="hljs-keyword">return</span> [
      {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'brute_force_detection'</span>,
        <span class="hljs-attr">condition</span>: <span class="hljs-function">(<span class="hljs-params">events</span>) =&gt;</span> {
          <span class="hljs-keyword">const</span> failedAttempts = events.filter(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span>
            e.result === <span class="hljs-string">'failure'</span> &amp;&amp;
            <span class="hljs-built_in">Date</span>.now() - <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(e.timestamp).getTime() &lt; <span class="hljs-number">300000</span> <span class="hljs-comment">// 5 minutes</span>
          );
          <span class="hljs-keyword">return</span> failedAttempts.length &gt;= <span class="hljs-number">5</span>;
        },
        <span class="hljs-attr">severity</span>: <span class="hljs-string">'high'</span>,
        <span class="hljs-attr">action</span>: <span class="hljs-string">'temporary_lockout'</span>
      },
      {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'credential_stuffing'</span>,
        <span class="hljs-attr">condition</span>: <span class="hljs-function">(<span class="hljs-params">events</span>) =&gt;</span> {
          <span class="hljs-keyword">const</span> recentFailures = events.filter(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span>
            e.result === <span class="hljs-string">'failure'</span> &amp;&amp;
            <span class="hljs-built_in">Date</span>.now() - <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(e.timestamp).getTime() &lt; <span class="hljs-number">3600000</span> <span class="hljs-comment">// 1 hour</span>
          );
          <span class="hljs-keyword">const</span> uniqueUsernames = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(recentFailures.map(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> e.username));
          <span class="hljs-keyword">return</span> uniqueUsernames.size &gt;= <span class="hljs-number">10</span>;
        },
        <span class="hljs-attr">severity</span>: <span class="hljs-string">'medium'</span>,
        <span class="hljs-attr">action</span>: <span class="hljs-string">'rate_limiting'</span>
      },
      {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'suspicious_location_pattern'</span>,
        <span class="hljs-attr">condition</span>: <span class="hljs-function">(<span class="hljs-params">events</span>) =&gt;</span> {
          <span class="hljs-keyword">const</span> locations = events.map(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> e.location?.country).filter(<span class="hljs-built_in">Boolean</span>);
          <span class="hljs-keyword">const</span> uniqueCountries = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(locations);
          <span class="hljs-keyword">return</span> uniqueCountries.size &gt;= <span class="hljs-number">3</span> &amp;&amp; events.length &gt;= <span class="hljs-number">5</span>;
        },
        <span class="hljs-attr">severity</span>: <span class="hljs-string">'medium'</span>,
        <span class="hljs-attr">action</span>: <span class="hljs-string">'enhanced_verification'</span>
      }
    ];
  }

  <span class="hljs-keyword">async</span> detectAnomalies(userId, events, context) {
    <span class="hljs-keyword">const</span> features = <span class="hljs-built_in">this</span>.extractFeatures(events, context);
    <span class="hljs-keyword">const</span> anomalyScore = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.mlModel.predict(features);

    <span class="hljs-keyword">if</span> (anomalyScore &gt; <span class="hljs-number">0.7</span>) {
      <span class="hljs-keyword">return</span> [{
        <span class="hljs-attr">type</span>: <span class="hljs-string">'ml_anomaly'</span>,
        <span class="hljs-attr">score</span>: anomalyScore,
        <span class="hljs-attr">features</span>: features,
        <span class="hljs-attr">description</span>: <span class="hljs-string">'Machine learning model detected anomalous behavior pattern'</span>
      }];
    }

    <span class="hljs-keyword">return</span> [];
  }
}
</code></pre>
<p>This class applies multiple techniques to detect threats. It first evaluates authentication history using static rules for brute-force attempts, large-scale credential reuse, or location anomalies. It then passes <a target="_blank" href="https://www.fullstory.com/blog/behavioral-data/">behavioral data</a> through a trained ML model to spot subtle patterns missed by rules. If any suspicious pattern is detected, it returns a structured risk report and initiates escalation.</p>
<h3 id="heading-automating-threat-response">Automating Threat Response</h3>
<p>Most times, systems respond in real-time. Automated threat response follows predefined actions and includes locking an account, alerting users, or blocking an IP, among others, when a high-risk event occurs.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Automated threat response system</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AutomatedThreatResponse</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>.responsePlaybooks = <span class="hljs-built_in">this</span>.initializeResponsePlaybooks();
    <span class="hljs-built_in">this</span>.escalationPolicies = <span class="hljs-built_in">this</span>.loadEscalationPolicies();
  }

  <span class="hljs-keyword">async</span> processSecurityEvent(event) {
    <span class="hljs-keyword">const</span> threatLevel = <span class="hljs-built_in">this</span>.assessThreatLevel(event);
    <span class="hljs-keyword">const</span> applicablePlaybooks = <span class="hljs-built_in">this</span>.selectPlaybooks(event, threatLevel);

    <span class="hljs-keyword">const</span> responses = [];
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> playbook <span class="hljs-keyword">of</span> applicablePlaybooks) {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.executePlaybook(playbook, event);
      responses.push(response);
    }

    <span class="hljs-keyword">if</span> (threatLevel === <span class="hljs-string">'critical'</span> || responses.some(<span class="hljs-function"><span class="hljs-params">r</span> =&gt;</span> !r.success)) {
      <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.escalateToHuman(event, responses);
    }

    <span class="hljs-keyword">return</span> {
      event,
      threatLevel,
      responses,
      <span class="hljs-attr">timestamp</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
    };
  }

  initializeResponsePlaybooks() {
    <span class="hljs-keyword">return</span> [
      {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'brute_force_response'</span>,
        <span class="hljs-attr">triggers</span>: [<span class="hljs-string">'brute_force_detected'</span>],
        <span class="hljs-attr">actions</span>: [
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'temporary_lockout'</span>, <span class="hljs-attr">duration</span>: <span class="hljs-number">900</span> },
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'rate_limiting'</span>, <span class="hljs-attr">factor</span>: <span class="hljs-number">10</span> },
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'notify_user'</span>, <span class="hljs-attr">method</span>: <span class="hljs-string">'email'</span> },
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'log_security_event'</span>, <span class="hljs-attr">level</span>: <span class="hljs-string">'high'</span> }
        ]
      },
      {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'account_takeover_response'</span>,
        <span class="hljs-attr">triggers</span>: [<span class="hljs-string">'impossible_travel'</span>, <span class="hljs-string">'behavior_anomaly_high'</span>],
        <span class="hljs-attr">actions</span>: [
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'terminate_all_sessions'</span> },
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'require_password_reset'</span> },
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'notify_user'</span>, <span class="hljs-attr">method</span>: <span class="hljs-string">'multiple'</span> },
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'freeze_account'</span>, <span class="hljs-attr">duration</span>: <span class="hljs-number">7200</span> }
        ]
      }
    ];
  }

  <span class="hljs-keyword">async</span> executePlaybook(playbook, event) {
    <span class="hljs-keyword">const</span> execution = {
      <span class="hljs-attr">playbookName</span>: playbook.name,
      <span class="hljs-attr">eventId</span>: event.id,
      <span class="hljs-attr">actions</span>: [],
      <span class="hljs-attr">success</span>: <span class="hljs-literal">true</span>
    };

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> action <span class="hljs-keyword">of</span> playbook.actions) {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.executeAction(action, event);
        execution.actions.push(result);
        <span class="hljs-keyword">if</span> (!result.success) {
          execution.success = <span class="hljs-literal">false</span>;
          <span class="hljs-keyword">break</span>;
        }
      } <span class="hljs-keyword">catch</span> (err) {
        execution.success = <span class="hljs-literal">false</span>;
        execution.error = err.message;
      }
    }

    <span class="hljs-keyword">return</span> execution;
  }

  <span class="hljs-keyword">async</span> executeAction(action, event) {
    <span class="hljs-keyword">switch</span> (action.type) {
      <span class="hljs-keyword">case</span> <span class="hljs-string">'temporary_lockout'</span>:
        <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.lockoutUser(event.userId, action.duration);
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">success</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">type</span>: action.type };
      <span class="hljs-keyword">case</span> <span class="hljs-string">'notify_user'</span>:
        <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.notifyUser(event.userId, action.method, event);
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">success</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">type</span>: action.type };
      <span class="hljs-keyword">default</span>:
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">success</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">type</span>: action.type, <span class="hljs-attr">error</span>: <span class="hljs-string">'Unknown action'</span> };
    }
  }
}
</code></pre>
<p>Here, the system uses playbooks – predefined actions to be taken in response to threats. For example, locks user from further brute-force attempts for some time and sends them an email notification. Freezing the account and ending all sessions are some reactive measures you can take if suspicious behavior indicates a takeover. These measures ensure fast and consistent action to mitigate damage even before humans can get involved.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Zero-trust authentication creates a strong line of distinction going against classic perimeter-based security. It must be painstakingly planned, implemented in layers, and constantly improved. This article offers a structured path, from basic MFA to intelligent behavioral monitoring and automated threat response.</p>
<p>Complementing the improvement of security, zero-trust promises better user experience, compliance readiness, and decreased incident risk. When organizations maintain a perpetual position of zero trust, we can see an actual positive impact on their ability to detect, prevent, and respond to threats in real time.</p>
<p>To have long-term success with this approach, you’ll need to continuously monitor your setup, perform periodic assessments, and be responsive to evolving attack patterns. Feedback loops and performance data are essential to keep the system secure yet user-friendly.</p>
<p>As threats grow more sophisticated, so must our defenses. ZTA provides a durable foundation – ready to evolve with emerging technologies like adaptive biometrics and AI-driven risk engines. Organizations investing in it today will be better equipped to meet tomorrow’s security and usability demands.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What Are JSON Web Tokens (JWT)? ]]>
                </title>
                <description>
                    <![CDATA[ When you’re working with any website, application, or API, you'll inevitably need to log in and authenticate your user base. One of the more commonly used methods of passing around authentication credentials from one system to another is using a JSON... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-are-json-web-tokens-jwt/</link>
                <guid isPermaLink="false">686c430b1a4ac707c0692874</guid>
                
                    <category>
                        <![CDATA[ JWT ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authorization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tutorial ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Grant Riordan ]]>
                </dc:creator>
                <pubDate>Mon, 07 Jul 2025 21:58:35 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751819356361/352ef68a-fa20-4a69-b666-393f7a17fa40.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you’re working with any website, application, or API, you'll inevitably need to log in and authenticate your user base. One of the more commonly used methods of passing around authentication credentials from one system to another is using a JSON Web Token (JWT).</p>
<p>In this article, you'll learn about:</p>
<ul>
<li><p>What a JSON Web Token (JWT) is</p>
</li>
<li><p>How JWTs are structured and created</p>
</li>
<li><p>Different JWT signing techniques and algorithms (Symmetric vs. Asymmetric)</p>
</li>
<li><p>How JWTs are used in real-world authentication flows</p>
</li>
<li><p>Important security best practices for using JWTs</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-a-jwt">What is a JWT?</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-json-web-tokens-are-made-of-three-elements">JSON Web Tokens Are Made Of Three Elements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-asymmetric-signing-rs256-explained">Asymmetric Signing (RS256) Explained</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-analogy-the-lock-and-key-for-asymmetric-signatures">Analogy: The Lock and Key for Asymmetric Signatures</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-symmetric-signing-hs256-hmac-with-sha-256">Symmetric Signing: HS256 (HMAC with SHA-256)</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-how-hs256-verification-works">How HS256 Verification Works</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-jwts-in-action-a-typical-authentication-flow">JWTs in Action: A Typical Authentication Flow</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-jwt-security-best-practices-amp-considerations">JWT Security Best Practices &amp; Considerations</a></p>
</li>
</ul>
<h2 id="heading-what-is-a-jwt">What Is a JWT?</h2>
<blockquote>
<p>JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.<br>— Introduction to JWT by jwt.io</p>
</blockquote>
<p>While accurate, this definition can be a bit dense at first glance. Imagine you want to send someone a sealed, tamper-proof message. That's essentially what a JSON Web Token (JWT) is. It's a secure message, a special kind of message designed to be sent between two parties which can be assured it came from an expected sender.</p>
<p>Each JWT is digitally signed using either a <strong>secret code</strong> (for symmetric algorithms like HMAC) or a <strong>private key</strong> (for asymmetric algorithms like RSA or ECDSA).</p>
<p>This secret code or private key is known only to the system that issues the JWT (often called an <em>authentication provider</em>, like Auth0, AWS Cognito, or Firebase Auth, which handles user logins and identity).</p>
<p>This signature proves two things:</p>
<ol>
<li><p><strong>Authenticity:</strong> It proves the message really came from who it claims to be from.</p>
</li>
<li><p><strong>Integrity:</strong> It proves that the message hasn't been changed or tampered with since it was signed. If even one character is altered, the signature won't match, and you'll know something is wrong, meaning the contents of the JWT can't be trusted.</p>
</li>
</ol>
<h3 id="heading-json-web-tokens-are-made-of-three-elements">JSON Web Tokens Are Made of Three Elements</h3>
<p>JWTs are made up of 3 key parts:</p>
<ol>
<li><p>Header</p>
</li>
<li><p>Payload</p>
</li>
<li><p>Signature</p>
</li>
</ol>
<h4 id="heading-header">Header</h4>
<p>The header contains metadata information about the token. Think of it like a label on a package – it tells you what’s inside and how it was prepared.</p>
<p>Typically, the header contains:</p>
<p><code>alg</code>: This specifies the <strong>algorithm</strong> used to sign the JWT. Common algorithms are <code>HS256</code> (HMAC with SHA-256) or <code>RS256</code> (RSA with SHA-256).</p>
<p><code>typ</code>: This specifies the <strong>type</strong> of token, which is almost always <code>JWT</code> for standard JSON Web Tokens.</p>
<p><strong>Example (decoded):</strong></p>
<pre><code class="lang-json">{ 
  <span class="hljs-attr">"alg"</span>: <span class="hljs-string">"RS256"</span>,
  <span class="hljs-attr">"typ"</span>: <span class="hljs-string">"JWT"</span> 
}
</code></pre>
<h4 id="heading-payload">Payload</h4>
<p>This is the second part of the JWT, and it's where the real data or "claims" are stored. Claims are statements about an entity (usually a user) and any other additional data. Using the previous analogy of a package, think of it as the “contents” of the package.</p>
<p>There are three types of claims:</p>
<p><strong>1. Registered Claims:</strong> These are predefined claims that are recommended for common use cases. They are not mandatory but are very useful for interoperability. These include:</p>
<ul>
<li><p><code>iss</code> – issuer, who issued the token (for example, your application’s domain)</p>
</li>
<li><p><code>sub</code> – subject, the subject of the token (for example, a User’s ID)</p>
</li>
<li><p><code>aud</code> – audience, the audience of the token (that is, who the token is intended for – for example, a specific API)</p>
</li>
<li><p><code>exp</code> – <strong>expiration</strong>, the expiry date as a timestamp</p>
</li>
<li><p><code>iat</code> – issued at, when the token was issued as a timestamp</p>
</li>
<li><p><code>nbf</code> – not before, when the token becomes valid (that is, the token cannot be used or deemed valid before this timestamp)</p>
</li>
<li><p><code>jti</code> – JWT ID, a unique identifier for the token, useful for preventing replay attacks or blacklisting</p>
</li>
</ul>
<p><strong>2. Public Claims:</strong> These can be defined by anyone using JWTs. To avoid naming conflicts, it's a good practice to register them or define them using a unique identifier like a URI.</p>
<p><strong>3. Private Claims:</strong> These are custom claims created to share specific information between parties who agree on using them. They are entirely up to you and your application's needs.</p>
<p><strong>Example payload:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"sub"</span>: <span class="hljs-string">"1234567890"</span>, <span class="hljs-comment">//  subject</span>
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"John Doe"</span>, <span class="hljs-comment">// private claim</span>
  <span class="hljs-attr">"admin"</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// private claim / role</span>
  <span class="hljs-attr">"iat"</span>: <span class="hljs-number">1678886400</span>, <span class="hljs-comment">// Issued at a specific timestamp</span>
  <span class="hljs-attr">"exp"</span>: <span class="hljs-number">1678890000</span>  <span class="hljs-comment">// Expires at a specific timestamp</span>
}
</code></pre>
<p>Like the header, this JSON object is also <strong>Base64Url encoded</strong> (a URL-safe variant of Base64 encoding) to form the second part of the JWT string.</p>
<p><strong>Important Note:</strong> <em>The payload is</em> <strong><em>encoded*</em></strong>, not encrypted.* This means that anyone can easily decode the JWT and read its contents. Never put sensitive information (like passwords) directly into the payload unless the entire JWT itself is encrypted (which is a separate process called JWE - JSON Web Encryption). The security of a standard JWT comes entirely from the signature, which prevents tampering.</p>
<h4 id="heading-signature">Signature</h4>
<p>The signature, as we've already discussed, is the most important part of the JWT. Without it, there's no protection applied to the JWT, meaning no way to validate the origin of the token or its integrity.</p>
<p>The signature is created by taking the <strong>encoded header</strong>, the <strong>encoded payload</strong>, and a <strong>secret key</strong> (or a private key if using asymmetric algorithms like RSA). These are then run through the cryptographic algorithm specified in the header (<code>alg</code> field). For HS256, a shared secret key is used. For RS256, a private key is used to sign, and a corresponding public key is used to verify. We’ll get on to verification soon.</p>
<p>Think of it like a tamper-proof seal on your package, or even better, a wax seal on a letter. If you receive your letter and the wax seal has been broken, you'd naturally believe the contents of the letter may not be original and therefore can't be trusted.</p>
<p>In pseudo-code it would look like this:</p>
<p><code>Signature = Algorithm( Base64Url(Header) + "." + Base64Url(Payload), SecretKey )</code></p>
<p>The result of this signing process is the signature, which is also Base64Url encoded to form the third part of the JWT string.</p>
<p>At the end of the whole process your JWT would look like this:</p>
<p><code>base64EncodedHeader.base64EncodedPayload.base64EncodedSignature</code></p>
<h3 id="heading-asymmetric-signing-rs256-explained">Asymmetric Signing (RS256) Explained</h3>
<p>When a JWT uses an algorithm like RS256 (RSA Signature with SHA-256), it employs an <strong>asymmetric cryptographic</strong> process involving a <strong>public</strong> and <strong>private</strong> key pair. This is where the core magic of proving authenticity and integrity happens without needing to share a secret.</p>
<h4 id="heading-the-signing-process-by-the-issuer">The Signing Process (by the Issuer)</h4>
<p>The <strong>sender</strong> (the server that issues the JWT, like Auth0) possesses the <strong>private key</strong>. This key is kept absolutely secret and secure. Here are the steps:</p>
<ol>
<li><p><strong>Prepare the data:</strong> The server takes the header (which includes the algorithm) and the payload. It Base64Url-encodes them, and then concatenates them with a dot: <code>Base64Url(Header) + "." + Base64Url(Payload)</code>.</p>
<p> For example, with this header:</p>
<pre><code class="lang-json"> {
   <span class="hljs-attr">"typ"</span>: <span class="hljs-string">"JWT"</span>,
   <span class="hljs-attr">"alg"</span>: <span class="hljs-string">"RS256"</span>
 }
</code></pre>
<p> And this payload:</p>
<pre><code class="lang-json"> {
   <span class="hljs-attr">"sub"</span>: <span class="hljs-string">"1234567890"</span>,
   <span class="hljs-attr">"name"</span>: <span class="hljs-string">"John Doe"</span>,
   <span class="hljs-attr">"admin"</span>: <span class="hljs-literal">true</span>,
   <span class="hljs-attr">"iat"</span>: <span class="hljs-number">1751494086</span>,
   <span class="hljs-attr">"exp"</span>: <span class="hljs-number">1751497686</span>
 }
</code></pre>
<p> This would create a Base64Url-encoded <code>header.payload</code> string like the animated example below:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751810671075/128fa652-fe2a-4413-a238-71531bfe67ae.gif" alt="Animated gif of the process converting header and payload to base64 encoded string" class="image--center mx-auto" width="800" height="587" loading="lazy"></p>
</li>
<li><p><strong>Calculate the hash:</strong> It then calculates a <strong>hash</strong> (using SHA-256 in this case) of this combined header and payload string.</p>
</li>
<li><p><strong>Sign the hash:</strong> Finally, it signs this hash using its private key. This cryptographically transformed hash is the signature part of the JWT.</p>
</li>
</ol>
<p>The JWT is then formed by concatenating the Base64Url-encoded header, the Base64Url-encoded payload, and the Base64Url-encoded signature, separated by dots: <code>header.payload.signature</code>.</p>
<p>The top segment below shows the full JWT token (header.payload.signature):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751830473890/31da21a0-50db-4a48-b4c3-378c2ac1616a.png" alt="Image displaying the fully formed JWT token along with original header and payload" class="image--center mx-auto" width="2630" height="1100" loading="lazy"></p>
<h3 id="heading-the-verification-process">The Verification Process</h3>
<p>This is where the magic happens, and it's often a point of confusion. The public key doesn't "decrypt" the original data like a symmetric key does. Instead, it performs a unique <strong>verification</strong> process.</p>
<p>The receiver (the client or another server that needs to verify the JWT) possesses the <strong>public key</strong>. This key does <strong>not</strong> need to be kept secret – it can be freely distributed.</p>
<p>Here's a step-by-step explanation:</p>
<ol>
<li><p><strong>Separate the parts:</strong> The first thing the receiver does is split the incoming JWT string into its three Base64Url-encoded components: the Header, the Payload, and the Signature.</p>
</li>
<li><p><strong>Obtain the public key:</strong> The verifier needs the <strong>public key</strong> that corresponds to the private key used by the issuer. Public keys are often available via a <strong>JWKS (JSON Web Key Set) endpoint</strong> (for example, <code>your-domain.com/.well-known/jwks.json</code>).</p>
</li>
<li><p><strong>Re-create the data to be hashed:</strong> The receiver takes the received, Base64Url-encoded Header and the received, Base64Url-encoded Payload. It then combines them exactly as the issuer did: <code>EncodedHeader.EncodedPayload</code>.</p>
</li>
<li><p><strong>Compute a local hash (Hash A):</strong> This combined string is then put through the same hashing algorithm (for example, SHA-256) that was specified in the JWT's header. This produces a new, locally computed hash (let's call this <strong>"Hash A"</strong>). This local hash represents what the content <em>should</em> look like if it hasn't been tampered with.</p>
</li>
<li><p><strong>"Unsign" the received signature with the public key to get the original signed hash (Hash B):</strong> This is the core cryptographic step. The verifier uses the public key (obtained in step 2) to perform a mathematical operation on the received signature. This operation does <em>not</em> create a new signature for comparison. Instead, it effectively "unsigns" or "decrypts" the signature to reveal the <strong>original hash ("Hash B")</strong> that was produced by the issuer's private key.</p>
<ul>
<li><strong>Crucial Point:</strong> This process is for <strong>verifying authenticity</strong>, not decrypting confidential data. The public key confirms that the signature was indeed created by the corresponding private key, and as part of that confirmation, it returns the original hash that <em>was signed</em>.</li>
</ul>
</li>
<li><p><strong>Compare the hashes:</strong> The verifier now has two hashes:</p>
<ul>
<li><p><strong>Hash A:</strong> The hash it <strong>computed locally</strong> from the received header and payload (from step 4).</p>
</li>
<li><p><strong>Hash B:</strong> The original hash that was extracted from the received Signature using the public key (from step 5)</p>
</li>
</ul>
</li>
<li><p><strong>If Hash A matches Hash B:</strong> It proves two critical things:</p>
<ol>
<li><p><strong>Authenticity:</strong> The token was indeed signed by the legitimate holder of the corresponding private key (for example, Auth0).</p>
</li>
<li><p><strong>Integrity:</strong> The content of the header and payload has <strong>not been tampered with</strong> since it was originally signed. If even a single character in the header or payload were changed, Hash A would be different, and it would not match Hash B. In this case, the JWT is considered valid and its contents can be trusted.</p>
</li>
</ol>
</li>
<li><p><strong>If the hashes do NOT match:</strong> The token is considered invalid and <strong>must be rejected</strong>. This indicates either that the JWT was signed by an unauthorised party (a forged token) or that its header or payload has been altered after it was signed.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751813812291/de32c7b0-1f3c-4f26-995e-b5c618b104b3.png" alt=" Flow diagram of asymmetric verification process" class="image--center mx-auto" width="1649" height="2947" loading="lazy"></p>
<h3 id="heading-analogy-the-lock-and-key-for-asymmetric-signatures">Analogy: The Lock and Key for Asymmetric Signatures</h3>
<p><strong>Private Key:</strong> A special, unique key that can <strong>lock</strong> a box (create a signature). Only the owner has this key.</p>
<p><strong>Public Key:</strong> A widely distributed key that can <strong>test</strong> if a box was locked by the corresponding private key. It can't lock a new box, but it can confirm if an existing lock is authentic.</p>
<p>You don't re-lock the box with the public key. You use the public key to check if the existing lock (the signature) is genuine and corresponds to the contents of the box.</p>
<h2 id="heading-symmetric-signing-hs256-hmac-with-sha-256">Symmetric Signing: HS256 (HMAC With SHA-256)</h2>
<p>While RS256 uses a pair of keys (private for signing, public for verifying), many JWTs you'll encounter are signed symmetrically, most commonly with the HS256 algorithm. HS256 stands for <strong>HMAC (Hash-based Message Authentication Code) with SHA-256</strong>.</p>
<p>The fundamental difference here is the use of a single, shared secret key for <em>both</em> signing and verification.</p>
<h3 id="heading-how-hs256-signing-works">How HS256 Signing Works</h3>
<ol>
<li><p><strong>Shared secret key:</strong> The issuer (for example, your authentication provider) possesses a single, confidential secret key. This key is known <em>only</em> to the issuer and any parties (like your API) that need to verify the token.</p>
</li>
<li><p><strong>Combine header and payload:</strong> Just like with asymmetric signing, the issuer takes the Base64Url-encoded Header (which specifies <code>"alg": "HS256"</code>) and the Base64Url-encoded Payload, and <strong>joins</strong> them with a dot.</p>
</li>
<li><p><strong>Apply HMAC-SHA256:</strong> This combined string is then fed into the HMAC-SHA256 algorithm along with the secret key. The HMAC algorithm uses the secret key to create a unique hash (the signature) of the data. In pseudo-code, it looks like this:</p>
<p> <code>Signature = HMAC-SHA256( Base64Url(Header) + "." + Base64Url(Payload), SecretKey )</code></p>
</li>
<li><p><strong>Form the JWT:</strong> The resulting signature (which is also Base64Url-encoded) is appended to the header and payload with a dot, forming the complete JWT: <code>base64EncodedHeader.base64EncodedPayload.base64EncodedSignature</code>.</p>
</li>
</ol>
<h3 id="heading-how-hs256-verification-works">How HS256 Verification Works</h3>
<p>When a receiver gets an HS256-signed JWT, it goes through a verification process.</p>
<p>First, it separates the parts. The JWT is split into its three Base64Url-encoded components: Header, Payload, and Signature, as we did with asymmetric JWTs.</p>
<p>Then, it obtains the shared secret key. The receiver must also possess the <strong>exact same secret key</strong> that the issuer used to sign the token. This key is <em>not</em> publicly distributed like a public key – it must be securely provisioned to any entity that needs to verify tokens.</p>
<p>Next, it re-calculates the signature. The receiver does this by taking the received Base64Url-encoded Header and Payload, combining them, and then re-applying the HMAC-SHA256 algorithm using the <em>same secret key</em>. This produces a new, locally computed signature.</p>
<p>Finally, the receiver compares the signature it just calculated locally with the signature it received as part of the JWT.</p>
<ul>
<li><p><strong>If the two signatures match:</strong> The token is considered valid. This confirms its authenticity (it came from someone who knows the secret) and integrity (it hasn't been tampered with).</p>
</li>
<li><p><strong>If the signatures do NOT match:</strong> The token is invalid and must be rejected. This indicates either tampering or that it was signed with a different, unknown secret key.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751814851136/a73b7af6-e92d-40f3-b1e3-c4bd2406ede9.png" alt="Flow diagram of symmetric verification process" class="image--center mx-auto" width="1715" height="2977" loading="lazy"></p>
<h3 id="heading-key-differences-and-considerations">Key Differences and Considerations:</h3>
<ul>
<li><p><strong>Key management:</strong> With HS256, the secret key must be securely shared and kept confidential by <em>all</em> parties involved in both signing and verifying. This can be more challenging to manage securely at scale compared to the public/private key model, where only the private key needs strict secrecy.</p>
</li>
<li><p><strong>Performance:</strong> HS256 is generally faster to compute than asymmetric algorithms like RS256, making it suitable for high-volume scenarios where the secret key can be securely distributed.</p>
</li>
</ul>
<h2 id="heading-jwts-in-action-a-typical-authentication-flow">JWTs in Action: A Typical Authentication Flow</h2>
<p>Now that you understand how JWTs are structured and signed, let's look at how they're typically used in a real-world web application. This authentication flow is a common pattern you'd encounter.</p>
<h3 id="heading-step-1-user-logs-in"><strong>Step 1: User Logs In:</strong></h3>
<p>A user opens a client application (for example, a web browser, mobile app) and enters their login credentials (username and password).</p>
<p>The client sends these credentials securely (always over HTTPS!) to an <strong>authentication server</strong> (like Auth0, AWS Cognito, or your own backend's authentication endpoint).</p>
<h3 id="heading-step-2-authentication-server-issues-jwt"><strong>Step 2: Authentication Server Issues JWT:</strong></h3>
<p>Then the authentication server verifies the user's credentials. If valid, it generates a new JWT. This JWT contains claims (like the user's ID, roles, expiration time) in its payload and is digitally signed by the server's <strong>private key</strong> (for asymmetric algorithms like RS256) or <strong>secret key</strong> (for symmetric algorithms like HS256).</p>
<p>The server then sends this signed JWT back to the client.</p>
<h3 id="heading-step-3-client-stores-jwt"><strong>Step 3: Client Stores JWT:</strong></h3>
<p>The client receives the JWT and typically stores it in a secure location, such as browser memory storage, session storage, or an HTTP-only cookie. The method of storage depends on the client type and security considerations.</p>
<h3 id="heading-step-4-client-makes-api-calls"><strong>Step 4: Client Makes API Calls:</strong></h3>
<p>When the user wants to access a protected resource on a backend API (for example, their profile data, a private feed), the client includes the JWT in the request.</p>
<p>The standard way to do this is by sending the token in the <code>Authorization</code> header of the HTTP request, prefixed with the word <code>Bearer</code>:</p>
<p><code>Authorization: Bearer &lt;your_jwt_here&gt;</code></p>
<h3 id="heading-step-5-api-verifies-jwt-amp-authorises-request"><strong>Step 5: API Verifies JWT &amp; Authorises Request:</strong></h3>
<p>Now, the backend API receives the request and extracts the JWT from the <code>Authorization</code> header. The API then performs the JWT verification process depending on the algorithm:</p>
<ul>
<li><p>It checks the token's claims, especially the <code>exp</code> (expiration) claim, to ensure it's still valid.</p>
</li>
<li><p>If the token is valid, the API trusts the claims within the payload (for example, the user's ID) and proceeds to fulfill the request, potentially using the user's roles to determine if they have permission to access the requested resource.</p>
</li>
<li><p>If the token is invalid (bad signature, expired, and so on), the API rejects the request, typically with an HTTP 401 Unauthorised status.</p>
</li>
</ul>
<p>This flow is powerful because JWTs are <strong>stateless</strong>: once issued, the authentication server doesn't need to keep a record of active sessions. The API can verify the token independently, which simplifies scaling and reduces server load.</p>
<h2 id="heading-jwt-security-best-practices-and-considerations">JWT Security Best Practices and Considerations</h2>
<p>While JWTs offer powerful authentication capabilities, using them securely requires careful attention to best practices. Misconfigurations or oversight can lead to significant vulnerabilities.</p>
<h3 id="heading-always-use-httpstls"><strong>Always Use HTTPS/TLS:</strong></h3>
<p><strong>Crucial:</strong> JWTs are <strong>encoded, not encrypted, by default</strong>. This means anyone who intercepts the token during transmission can easily read its payload. Therefore, JWTs (and all authentication traffic) <strong>must always be transmitted over HTTPS (TLS)</strong> to encrypt the communication channel itself and prevent eavesdropping.</p>
<h3 id="heading-protect-your-signing-keys"><strong>Protect Your Signing Keys:</strong></h3>
<p>Whether it's a private key (for RS256) or a shared secret key (for HS256), these keys are paramount. If an attacker gains access to your signing key, they can forge valid JWTs, impersonate users, and compromise your system. Store these keys securely, preferably in dedicated key management services.</p>
<h3 id="heading-keep-access-tokens-short-lived-exp-claim"><strong>Keep Access Tokens Short-Lived (</strong><code>exp</code> claim):</h3>
<p>You should always set short expiration times (for example, 5-15 minutes) for your JWTs used as access tokens. This minimises the window of opportunity for an attacker if a token is compromised.</p>
<p>Since JWTs are stateless, they are hard to revoke immediately once issued. A short lifespan is your primary defense against compromised tokens.</p>
<h3 id="heading-implement-refresh-tokens-for-longer-sessions"><strong>Implement Refresh Tokens (for Longer Sessions):</strong></h3>
<p>To maintain user experience with short-lived access tokens, use <strong>refresh tokens</strong>. A refresh token is a separate, longer-lived token (usually stored more securely) that can be exchanged for a new, short-lived access token when the current one expires, without requiring the user to re-authenticate. Refresh tokens <em>can</em> be revoked by the server, offering better control.</p>
<h3 id="heading-never-put-sensitive-data-in-the-payload"><strong>Never Put Sensitive Data in the Payload:</strong></h3>
<p>Reiterating this crucial point: the JWT payload is Base64Url encoded, which is easily reversible. Do not put passwords, highly sensitive PII (Personally Identifiable Information), or confidential business data directly into the JWT payload. Only include non-sensitive or publicly available information, or data that's already encrypted by other means.</p>
<h3 id="heading-validate-all-claims-on-verification"><strong>Validate ALL Claims on Verification:</strong></h3>
<p>When verifying a JWT, don't just check the signature. Always validate all relevant claims, including:</p>
<ul>
<li><p><code>exp</code> (Expiration): Ensure the token hasn't expired.</p>
</li>
<li><p><code>iss</code> (Issuer): Verify the token came from the expected authentication server.</p>
</li>
<li><p><code>aud</code> (Audience): Ensure the token is intended for your specific API/application.</p>
</li>
<li><p><code>nbf</code> (Not Before): Check if the token is active yet.</p>
</li>
</ul>
<h3 id="heading-consider-token-revocation-for-critical-cases"><strong>Consider Token Revocation (for critical cases):</strong></h3>
<p>For situations requiring immediate revocation (for example, user password change, account deactivation), typical stateless JWTs are challenging. Strategies include:</p>
<ul>
<li><p>Short expiration times (as above).</p>
</li>
<li><p>A blacklist/revocation list: Store the <code>jti</code> (JWT ID) of revoked tokens in a database, checking this list on every request. This adds a stateful lookup but provides immediate revocation.</p>
</li>
</ul>
<h2 id="heading-thanks-for-reading">Thanks for reading!</h2>
<p>I hope you’ve found this tutorial useful, and as always if you want to ask any questions or hear about upcoming articles, you can always follow me on ‘X’, my handle is @grantdotdev and follow by clicking <a target="_blank" href="https://x.com/grantdotdev">here</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Secure SSR Authentication with Supabase, Astro, and Cloudflare Turnstile ]]>
                </title>
                <description>
                    <![CDATA[ In this guide, you'll build a full server-side rendered (SSR) authentication system using Astro, Supabase, and Cloudflare Turnstile to protect against bots. By the end, you'll have a fully functional authentication system with Astro actions, magic li... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-secure-ssr-authentication-with-supabase-astro-and-cloudflare-turnstile/</link>
                <guid isPermaLink="false">685594145aea0dba325c37e1</guid>
                
                    <category>
                        <![CDATA[ supabase ss ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Astro ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ supabase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ supabase auth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ magic links ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cloudflare ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloudflare Turnstile ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SSR ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Fatuma Abdullahi ]]>
                </dc:creator>
                <pubDate>Fri, 20 Jun 2025 17:02:12 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750438909287/d36c0c01-e779-4eea-aa41-b797fcbb05f6.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this guide, you'll build a full server-side rendered (SSR) authentication system using Astro, Supabase, and Cloudflare Turnstile to protect against bots.</p>
<p>By the end, you'll have a fully functional authentication system with Astro actions, magic link authentication using Supabase, bot protection via Cloudflare Turnstile, protected routes and middleware, and secure session management.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-technologies">Understanding the Technologies</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-astro">What is Astro?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-astro-actions">What are Astro Actions?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-supabase">What is Supabase?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-cloudflare-turnstile">What is Cloudflare Turnstile?</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-ssr-authentication">Understanding SSR Authentication</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-ssr-vs-spa-authentication">SSR vs. SPA Authentication</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-why-protect-auth-forms">Why Protect Auth Forms?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-part-1-how-to-set-up-the-backend">Part 1: How to Set Up the Backend</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-set-up-supabase-backend">Set Up Supabase Backend</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-set-up-cloudflare-turnstile">Set Up Cloudflare Turnstile</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-part-2-how-to-set-up-the-frontend">Part 2: How to Set Up the Frontend</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-create-the-astro-project">Create the Astro Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-configure-astro-for-ssr">Configure Astro for SSR</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-install-supabase-dependencies">Install Supabase Dependencies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-configure-environment-variables">Configure Environment Variables</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-part-3-how-to-set-up-supabase-ssr">Part 3: How to Set Up Supabase SSR</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-create-the-supabase-client">Create the Supabase Client</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-middleware-for-route-protection">Create Middleware for Route Protection</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-part-4-how-to-build-the-user-interface">Part 4: How to Build the User Interface</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-update-the-layout">Update the Layout</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-the-sign-in-page">Create the Sign-In Page</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-the-protected-page">Create the Protected Page</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-part-5-how-to-set-up-astro-actions">Part 5: How to Set Up Astro Actions</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-create-the-authentication-actions">Create the Authentication Actions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-the-code-exchange-api-route">Create the Code Exchange API Route</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-part-6-how-to-test-your-application">Part 6: How to Test Your Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-notes-and-additional-resources">Notes and Additional Resources</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-useful-documentation">Useful Documentation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-complete-code-repository">Complete Code Repository</a></p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>This tutorial assumes you are familiar with:</p>
<ul>
<li><p>Web development frameworks</p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/set-up-authentication-in-apps-with-supabase/">Basic authentication flows</a></p>
</li>
<li><p>Basic Backend-as-a-Service (BaaS) concepts</p>
</li>
</ul>
<h2 id="heading-understanding-the-technologies">Understanding the Technologies</h2>
<h3 id="heading-what-is-astro">What is Astro?</h3>
<p><a target="_blank" href="https://docs.astro.build/en/getting-started/">Astro</a> is a UI-agnostic web framework that renders <a target="_blank" href="https://docs.astro.build/en/concepts/why-astro/#server-first">server-first</a> by default. It <a target="_blank" href="https://docs.astro.build/en/guides/integrations-guide/#official-integrations">can be used with any UI framework</a>, including <a target="_blank" href="https://docs.astro.build/en/guides/client-side-scripts/">Astro client components</a>.</p>
<h3 id="heading-what-are-astro-actions">What are Astro Actions?</h3>
<p><a target="_blank" href="https://docs.astro.build/en/guides/actions/">Astro actions</a> allow you to write server-side functions that can be called without explicitly setting up API routes. They provide many useful utilities that simplify the process of running server logic and can be called from both client and server environments.</p>
<h3 id="heading-what-is-supabase">What is Supabase?</h3>
<p><a target="_blank" href="https://supabase.com/docs">Supabase</a> is an open-source Backend-as-a-Service that builds upon <a target="_blank" href="https://www.postgresql.org/docs/">Postgres</a>. It provides key features such as authentication, real-time capabilities, edge functions, storage, and more. Supabase offers both a hosted version for easy scaling and a self-hostable version for full control.</p>
<h3 id="heading-what-is-cloudflare-turnstile">What is Cloudflare Turnstile?</h3>
<p>Turnstile is <a target="_blank" href="https://www.cloudflare.com/en-gb/application-services/products/turnstile/">Cloudflare's replacement for CAPTCHAs</a>, which are visual puzzles used to differentiate between genuine users and bots. Unlike traditional CAPTCHAs, which are visually clunky, annoying, and <a target="_blank" href="https://blog.cloudflare.com/turnstile-ga/">sometimes difficult to solve</a>, Turnstile detects malicious activity without requiring users to solve puzzles, while providing a better user experience.</p>
<h2 id="heading-understanding-ssr-authentication">Understanding SSR Authentication</h2>
<p>Server-side rendered (SSR) auth refers to handling authentication on the server using a <a target="_blank" href="https://www.freecodecamp.org/news/set-up-authentication-in-apps-with-supabase/#how-does-authentication-work">cookie-based authentication method</a>.</p>
<p>The flow works as follows:</p>
<ol>
<li><p>The server creates a session and stores a session ID in a cookie sent to the client</p>
</li>
<li><p>The browser receives the cookie and automatically includes it in future requests</p>
</li>
<li><p>The server uses the cookie to determine if the user is authenticated</p>
</li>
</ol>
<p>Since browsers cannot modify HTTP-only cookies and servers cannot access local storage, SSR authentication requires careful management to prevent security risks such as session hijacking and stale sessions.</p>
<h3 id="heading-ssr-vs-spa-authentication">SSR vs. SPA Authentication</h3>
<p>Single-Page Applications (SPAs), like traditional React apps, handle authentication on the client side because they don't have direct access to a server. SPAs typically use JWTs stored in local storage, cookies, or session storage, sending these tokens in HTTP headers when communicating with servers.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/HdE3dk8VkRU" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<h2 id="heading-why-protect-auth-forms">Why Protect Auth Forms?</h2>
<p>Authentication protects sensitive resources from unauthorized access, making auth forms primary targets for bots and malicious actors. Taking extra precautions is important for maintaining security.</p>
<h2 id="heading-part-1-how-to-set-up-the-backend">Part 1: How to Set Up the Backend</h2>
<h3 id="heading-set-up-supabase-backend">Set Up Supabase Backend</h3>
<p>First, you'll need <a target="_blank" href="https://supabase.com/dashboard/">a Supabase account</a>. Create a project, then:</p>
<ol>
<li><p>Go to the Authentication tab in the sidebar</p>
</li>
<li><p>Click the Sign In / Up tab under Configuration</p>
</li>
<li><p>Enable user sign-ups</p>
</li>
<li><p>Scroll down to Auth Providers and enable email (disable email confirmation for this tutorial)</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742054137964/a379192b-4eaf-491f-bcf4-a0e1e0deef94.png" alt="Supabase authentication configuration interface showing user signup options and email provider enabled" width="2480" height="1448" loading="lazy"></p>
<h3 id="heading-set-up-cloudflare-turnstile">Set Up Cloudflare Turnstile</h3>
<ol>
<li><p><a target="_blank" href="https://dash.cloudflare.com/login">Log in or register for a Cloudflare account</a></p>
</li>
<li><p>Click the Turnstile tab in the sidebar</p>
</li>
<li><p>Click the "Add widget" button</p>
</li>
<li><p>Name your widget and add "localhost" as the hostname</p>
</li>
<li><p>Leave all other settings as default, and create the widget</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750260766060/95ec02e5-8ee7-4438-a66c-76866ec068c1.png" alt="Cloudflare Turnstile widget creation interface" width="2200" height="1796" loading="lazy"></p>
<p>After creating the widget, copy the secret key and add it to your Supabase dashboard:</p>
<ol>
<li><p>Go back to Supabase Authentication settings</p>
</li>
<li><p>Navigate to the Auth Protection tab under Configuration</p>
</li>
<li><p>Turn on Captcha protection</p>
</li>
<li><p>Choose Cloudflare as the provider</p>
</li>
<li><p>Paste your secret key</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750260776990/56ef5fc1-3321-45f0-ab9a-878679a08e88.png" alt="Supabase Attack Protection settings with Turnstile configuration" width="2302" height="986" loading="lazy"></p>
<h2 id="heading-part-2-how-to-set-up-the-frontend">Part 2: How to Set Up the Frontend</h2>
<h3 id="heading-create-the-astro-project">Create the Astro Project</h3>
<p>Next, you will need to create an Astro project. Open your preferred IDE or Text editor’s integrated terminal and run the following command to scaffold an Astro project in a folder named “ssr-auth.” Feel free to use any name you like.</p>
<pre><code class="lang-bash">npm create astro@latest ssr-auth
</code></pre>
<p>Follow the provided prompts and choose a basic template to start with. When it’s done, change into the folder, then run <code>npm install</code> to install dependencies, followed by <code>npm run dev</code> to start the server, and your site will be available at <a target="_blank" href="http://localhost:4321"><code>localhost:4321</code></a>.</p>
<h3 id="heading-configure-astro-for-ssr">Configure Astro for SSR</h3>
<p>Set Astro to run in SSR mode by adding <code>output: "server",</code> to the <code>defineConfig</code> function found in the <code>astro.config.mjs</code> file at the root of the folder.</p>
<p>Next, <a target="_blank" href="https://docs.astro.build/en/guides/integrations-guide/node/">add an adapter</a> to create a server runtime. For this, use the Node.js adapter by running this command in a terminal: <code>npx astro add node</code>. This will add it and automatically make all relevant changes.</p>
<p>Finally, add Tailwind for styling. Run this command in a terminal window: <code>npx astro add tailwind</code>. Follow the prompts, and it will make any changes necessary.</p>
<p>At this stage, your <code>astro.config.mjs</code> should look like this:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @ts-check</span>
<span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro/config"</span>;
<span class="hljs-keyword">import</span> node <span class="hljs-keyword">from</span> <span class="hljs-string">"@astrojs/node"</span>;
<span class="hljs-keyword">import</span> tailwindcss <span class="hljs-keyword">from</span> <span class="hljs-string">"@tailwindcss/vite"</span>;

<span class="hljs-comment">// https://astro.build/config</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  output: <span class="hljs-string">"server"</span>,
  adapter: node({
    mode: <span class="hljs-string">"standalone"</span>,
  }),
  vite: {
    plugins: [tailwindcss()],
  },
});
</code></pre>
<h3 id="heading-install-supabase-dependencies">Install Supabase Dependencies</h3>
<p>You can do this by running the following command:</p>
<pre><code class="lang-bash">npm install @supabase/supabase-js @supabase/ssr
</code></pre>
<h3 id="heading-configure-environment-variables">Configure Environment Variables</h3>
<p>Create a <code>.env</code> file in the project root and add the following. Remember to replace with your actual credentials:</p>
<pre><code class="lang-bash">SUPABASE_URL=&lt;YOUR_URL&gt;
SUPABASE_ANON_KEY=&lt;YOUR_ANON_KEY&gt;
TURNSTILE_SITE_KEY=&lt;YOUR_TURNSTILE_SITE_KEY&gt;
</code></pre>
<p>You can get the Supabase values from the dashboard:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742054292788/8aeec326-259c-49bd-a6f8-b885e9a9e6ea.png" alt="Supabase project connection interface showing environment variables" width="2132" height="802" loading="lazy"></p>
<p><strong>💡Note:</strong> In Astro, environment variables accessed on the client side must be prefixed with 'PUBLIC'. But since we're using Astro actions that run on the server, the prefix is not required.</p>
<h2 id="heading-part-3-how-to-set-up-supabase-ssr">Part 3: How to Set Up Supabase SSR</h2>
<h3 id="heading-create-the-supabase-client">Create the Supabase Client</h3>
<p>Create <code>src/lib/supabase.ts</code>:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { createServerClient, parseCookieHeader } <span class="hljs-keyword">from</span> <span class="hljs-string">"@supabase/ssr"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { AstroCookies } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createClient</span>(<span class="hljs-params">{
    request,
    cookies,
}: {
    request: Request;
    cookies: AstroCookies;
}</span>) </span>{
    <span class="hljs-keyword">const</span> cookieHeader = request.headers.get(<span class="hljs-string">"Cookie"</span>) || <span class="hljs-string">""</span>;

    <span class="hljs-keyword">return</span> createServerClient(
        <span class="hljs-keyword">import</span>.meta.env.SUPABASE_URL,
        <span class="hljs-keyword">import</span>.meta.env.SUPABASE_ANON_KEY,
        {
            cookies: {
                getAll() {
                    <span class="hljs-keyword">const</span> cookies = parseCookieHeader(cookieHeader);
                    <span class="hljs-keyword">return</span> cookies.map(<span class="hljs-function">(<span class="hljs-params">{ name, value }</span>) =&gt;</span> ({
                        name,
                        value: value ?? <span class="hljs-string">""</span>,
                    }));
                },
                setAll(cookiesToSet) {
                    cookiesToSet.forEach(<span class="hljs-function">(<span class="hljs-params">{ name, value, options }</span>) =&gt;</span>
                        cookies.set(name, value, options)
                    );
                },
            },
        }
    );
}
</code></pre>
<p>This sets up Supabase to handle <a target="_blank" href="https://supabase.com/docs/guides/auth/server-side/creating-a-client?queryGroups=framework&amp;framework=astro&amp;queryGroups=environment&amp;environment=astro-browser">cookies in a server-rendered application</a> and exports a function that takes the request and cookies object as input. The function is set up like this because Astro has three ways to access request and cookie information:</p>
<ul>
<li><p>Through Astro’s global object, which is only available on Astro pages.</p>
</li>
<li><p>Through <code>AstroAPIContext</code> object, which is only available in Astro actions.</p>
</li>
<li><p>Through <code>APIContext</code> which is a subset of the global object and is available through API routes and middleware.</p>
</li>
</ul>
<p>So the <code>createClient</code> function accepts the <code>request</code> and <code>cookies</code> objects separately to make it flexible and applicable in the various contexts in which it may be used.</p>
<h3 id="heading-create-middleware-for-route-protection">Create Middleware for Route Protection</h3>
<p>Next, create a <code>middleware.ts</code> file in the <code>src</code> folder and paste this into it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { defineMiddleware } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:middleware"</span>;
<span class="hljs-keyword">import</span> { createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"./lib/supabase"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> onRequest = defineMiddleware(<span class="hljs-keyword">async</span> (context, next) =&gt; {
    <span class="hljs-keyword">const</span> { pathname } = context.url;

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Middleware executing for path:"</span>, pathname);

    <span class="hljs-keyword">const</span> supabase = createClient({
        request: context.request,
        cookies: context.cookies,
    });

    <span class="hljs-keyword">if</span> (pathname === <span class="hljs-string">"/protected"</span>) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Checking auth for protected route"</span>);

        <span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> supabase.auth.getUser();

        <span class="hljs-comment">// If no user, redirect to index</span>
        <span class="hljs-keyword">if</span> (!data.user) {
            <span class="hljs-keyword">return</span> context.redirect(<span class="hljs-string">"/"</span>);
        }
    }

    <span class="hljs-keyword">return</span> next();
});
</code></pre>
<p>This middleware checks for an active user when accessing the protected route and redirects unauthenticated users to the index page.</p>
<h2 id="heading-part-4-how-to-build-the-user-interface">Part 4: How to Build the User Interface</h2>
<h3 id="heading-update-the-layout">Update the Layout</h3>
<p>First, update <code>src/layouts/Layout.astro</code> to include the Turnstile script. Add this just above the closing <code>&lt;/head&gt;</code> tag:</p>
<pre><code class="lang-typescript">&lt;script
    src=<span class="hljs-string">"https://challenges.cloudflare.com/turnstile/v0/api.js"</span>
    <span class="hljs-keyword">async</span>
    defer&gt;
&lt;/script&gt;
</code></pre>
<h3 id="heading-create-the-sign-in-page">Create the Sign-In Page</h3>
<p>Replace the contents of <code>src/pages/index.astro</code>:</p>
<pre><code class="lang-typescript">---
<span class="hljs-keyword">import</span> Layout <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/Layout.astro"</span>;
<span class="hljs-keyword">import</span> { createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/supabase"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../styles/global.css"</span>;

<span class="hljs-keyword">const</span> supabase = createClient({
    request: Astro.request,
    cookies: Astro.cookies,
});

<span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> supabase.auth.getUser();

<span class="hljs-keyword">if</span> (data.user) {
    <span class="hljs-keyword">return</span> Astro.redirect(<span class="hljs-string">"/protected"</span>);
}

<span class="hljs-keyword">const</span> apiKey = <span class="hljs-keyword">import</span>.meta.env.TURNSTILE_SITE_KEY;
---

&lt;Layout&gt;
    &lt;section <span class="hljs-keyword">class</span>=<span class="hljs-string">"flex flex-col items-center justify-center m-30"</span>&gt;
        &lt;h1 <span class="hljs-keyword">class</span>=<span class="hljs-string">"text-4xl text-left font-bold mb-12"</span>&gt;Sign In to Your Account&lt;/h1&gt;
        &lt;form id=<span class="hljs-string">"signin-form"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"flex flex-col gap-2 w-1/2"</span>&gt;
            &lt;label <span class="hljs-keyword">for</span>=<span class="hljs-string">"email"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">""</span>&gt;Enter your email&lt;/label&gt;
            &lt;input
                <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span>
                name=<span class="hljs-string">"email"</span>
                id=<span class="hljs-string">"email"</span>
                placeholder=<span class="hljs-string">"youremail@example.com"</span>
                <span class="hljs-keyword">class</span>=<span class="hljs-string">"border border-gray-500 rounded-md p-2"</span>
                required
            /&gt;
            &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"cf-turnstile"</span> data-sitekey={apiKey}&gt;&lt;/div&gt;
            &lt;button
                <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
                id=<span class="hljs-string">"sign-in"</span>
                <span class="hljs-keyword">class</span>=<span class="hljs-string">"bg-gray-600 hover:bg-gray-700 p-2 rounded-md text-white font-bold w-full cursor-pointer disabled:bg-gray-500 disabled:hover:bg-gray-500 disabled:cursor-not-allowed"</span>
                &gt;Sign In&lt;/button
            &gt;
        &lt;/form&gt;
    &lt;/section&gt;
&lt;/Layout&gt;
</code></pre>
<p>Here, the frontmatter creates a Supabase server client and then uses it to check if we have an active user. It redirects based on this information. This works because the front matter runs on the server side, and the project is set to server output.</p>
<p>The template displays a simple form with an email input. To complete it, add this below the closing <code>&lt;/Layout&gt;</code> tag:</p>
<pre><code class="lang-typescript">
&lt;script&gt;
    <span class="hljs-keyword">import</span> { actions } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:actions"</span>;

    <span class="hljs-keyword">declare</span> <span class="hljs-built_in">global</span> {
        <span class="hljs-keyword">interface</span> Window {
            turnstile?: {
                reset: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>;
            };
        }
    }

    <span class="hljs-keyword">const</span> signInForm = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#signin-form"</span>) <span class="hljs-keyword">as</span> HTMLFormElement;
    <span class="hljs-keyword">const</span> formSubmitBtn = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"sign-in"</span>) <span class="hljs-keyword">as</span> HTMLButtonElement;

    signInForm?.addEventListener(<span class="hljs-string">"submit"</span>, <span class="hljs-keyword">async</span> (e) =&gt; {
        e.preventDefault();
        formSubmitBtn.disabled = <span class="hljs-literal">true</span>;
        formSubmitBtn.textContent = <span class="hljs-string">"Signing in..."</span>;

        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> turnstileToken = (
                <span class="hljs-built_in">document</span>.querySelector(
                    <span class="hljs-string">"[name='cf-turnstile-response']"</span>
                ) <span class="hljs-keyword">as</span> HTMLInputElement
            )?.value;

            <span class="hljs-keyword">if</span> (!turnstileToken) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"verification_missing"</span>);
            }

            <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">new</span> FormData(signInForm);
            formData.append(<span class="hljs-string">"captchaToken"</span>, turnstileToken);

            <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> actions.signIn(formData);

            <span class="hljs-keyword">if</span> (!results.data?.success) {
                <span class="hljs-keyword">if</span> (results.data?.message?.includes(<span class="hljs-string">"captcha protection"</span>)) {
                    alert(<span class="hljs-string">"Verification failed. Please try again."</span>);
                    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">window</span>.turnstile) {
                        <span class="hljs-built_in">window</span>.turnstile.reset();
                    }
                    formSubmitBtn.disabled = <span class="hljs-literal">false</span>;
                    formSubmitBtn.textContent = <span class="hljs-string">"Sign In"</span>;
                    <span class="hljs-keyword">return</span>;
                } <span class="hljs-keyword">else</span> {
                    alert(<span class="hljs-string">"Oops! Could not sign in. Please try again"</span>);
                    formSubmitBtn.disabled = <span class="hljs-literal">false</span>;
                    formSubmitBtn.textContent = <span class="hljs-string">"Sign In"</span>;
                    <span class="hljs-keyword">return</span>;
                }
            }

            formSubmitBtn.textContent = <span class="hljs-string">"Sign In"</span>;
            alert(<span class="hljs-string">"Please check your email to sign in"</span>);
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-keyword">if</span> (<span class="hljs-built_in">window</span>.turnstile) {
                <span class="hljs-built_in">window</span>.turnstile.reset();
            }
            formSubmitBtn.disabled = <span class="hljs-literal">false</span>;
            formSubmitBtn.textContent = <span class="hljs-string">"Sign In"</span>;
            <span class="hljs-built_in">console</span>.log(error);
            alert(<span class="hljs-string">"Something went wrong. Please try again"</span>);
        }
    });
&lt;/script&gt;
</code></pre>
<p>This adds some vanilla JavaScript that calls the <code>SignIn</code> Upon form submission. This action provides user feedback through alerts and manages the button’s text and disabled state. This effectively adds client-side interactivity to the page.</p>
<h3 id="heading-create-the-protected-page">Create the Protected Page</h3>
<p>Create <code>src/pages/protected.astro</code>:</p>
<pre><code class="lang-typescript">---
<span class="hljs-keyword">import</span> Layout <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/Layout.astro"</span>;
<span class="hljs-keyword">import</span> { createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/supabase"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../styles/global.css"</span>;

<span class="hljs-keyword">const</span> supabase = createClient({
    request: Astro.request,
    cookies: Astro.cookies,
});

<span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> supabase.auth.getUser();
---

&lt;Layout&gt;
    &lt;section <span class="hljs-keyword">class</span>=<span class="hljs-string">"flex flex-col items-center justify-center m-30"</span>&gt;
        &lt;h1 <span class="hljs-keyword">class</span>=<span class="hljs-string">"text-4xl text-left font-bold mb-12"</span>&gt;You are logged <span class="hljs-keyword">in</span>!&lt;/h1&gt;
        &lt;p <span class="hljs-keyword">class</span>=<span class="hljs-string">"mb-6"</span>&gt;Your user Id: {data.user?.id}&lt;/p&gt;
        &lt;button
            id=<span class="hljs-string">"sign-out"</span>
            <span class="hljs-keyword">class</span>=<span class="hljs-string">"bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded-md text-white font-bold cursor-pointer disabled:bg-gray-500 disabled:hover:bg-gray-500 disabled:cursor-not-allowed"</span>
            &gt;Sign Out&lt;/button
        &gt;
    &lt;/section&gt;
&lt;/Layout&gt;

&lt;script&gt;
    <span class="hljs-keyword">import</span> { actions } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:actions"</span>;
    <span class="hljs-keyword">const</span> signOutBtn = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"sign-out"</span>) <span class="hljs-keyword">as</span> HTMLButtonElement;

    signOutBtn?.addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-keyword">async</span> (e) =&gt; {
        e.preventDefault();
        signOutBtn!.disabled = <span class="hljs-literal">true</span>;
        signOutBtn!.textContent = <span class="hljs-string">"Signing out..."</span>;

        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> actions.signOut();

            <span class="hljs-keyword">if</span> (!results.data?.success) {
                signOutBtn!.disabled = <span class="hljs-literal">false</span>;
                signOutBtn!.textContent = <span class="hljs-string">"Sign Out"</span>;
                <span class="hljs-keyword">return</span> alert(<span class="hljs-string">"Oops! Could not sign Out. Please try again"</span>);
            }
            <span class="hljs-keyword">return</span> <span class="hljs-built_in">window</span>.location.reload();
        } <span class="hljs-keyword">catch</span> (error) {
            signOutBtn.disabled = <span class="hljs-literal">false</span>;
            signOutBtn.textContent = <span class="hljs-string">"Sign Out"</span>;
            <span class="hljs-built_in">console</span>.log(error);
            <span class="hljs-keyword">return</span> alert(<span class="hljs-string">"Something went wrong. Please try again"</span>);
        }
    });
&lt;/script&gt;
</code></pre>
<p>This page retrieves the user data server-side in the front matter and displays it in the template, along with a sign-out button.</p>
<p>The JavaScript in the <code>script</code> tags handle calling the sign-out action, user feedback, and button state, as in the <code>index.astro</code> page.</p>
<h2 id="heading-part-5-how-to-set-up-astro-actions">Part 5: How to Set Up Astro Actions</h2>
<h3 id="heading-create-the-authentication-actions">Create the Authentication Actions</h3>
<p>Finally, add an <code>actions</code> folder in the <code>src</code> folder and create an <code>index.ts</code> file to hold our logic. Paste the following into it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { defineAction, <span class="hljs-keyword">type</span> ActionAPIContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:actions"</span>;
<span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:schema"</span>;
<span class="hljs-keyword">import</span> { createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/supabase"</span>;

<span class="hljs-keyword">const</span> emailSignUp = <span class="hljs-keyword">async</span> (
    {
        email,
        captchaToken,
    }: {
        email: <span class="hljs-built_in">string</span>;
        captchaToken: <span class="hljs-built_in">string</span>;
    },
    context: ActionAPIContext
) =&gt; {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Sign up action"</span>);
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> supabase = createClient({
            request: context.request,
            cookies: context.cookies,
        });

        <span class="hljs-keyword">const</span> { data, error } = <span class="hljs-keyword">await</span> supabase.auth.signInWithOtp({
            email,
            options: {
                captchaToken,
                emailRedirectTo: <span class="hljs-string">"http://localhost:4321/api/exchange"</span>,
            },
        });

        <span class="hljs-keyword">if</span> (error) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Sign up error"</span>, error);
            <span class="hljs-keyword">return</span> {
                success: <span class="hljs-literal">false</span>,
                message: error.message,
            };
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Sign up success"</span>, data);
            <span class="hljs-keyword">return</span> {
                success: <span class="hljs-literal">true</span>,
                message: <span class="hljs-string">"Successfully logged in"</span>,
            };
        }
    } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"SignUp action other error"</span>, err);
        <span class="hljs-keyword">return</span> {
            success: <span class="hljs-literal">false</span>,
            message: <span class="hljs-string">"Unexpected error"</span>,
        };
    }
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> server = {
    signIn: defineAction({
        accept: <span class="hljs-string">"form"</span>,
        input: z.object({
            email: z.string().email(),
            captchaToken: z.string(),
        }),
        handler: <span class="hljs-keyword">async</span> (input, context) =&gt; {
            <span class="hljs-keyword">return</span> emailSignUp(input, context);
        },
    }),
    signOut: defineAction({
        handler: <span class="hljs-keyword">async</span> (_, context) =&gt; {
            <span class="hljs-keyword">const</span> supabase = createClient({
                request: context.request,
                cookies: context.cookies,
            });
            <span class="hljs-keyword">const</span> { error } = <span class="hljs-keyword">await</span> supabase.auth.signOut();
            <span class="hljs-keyword">if</span> (error) {
                <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Sign out error"</span>, error);
                <span class="hljs-keyword">return</span> {
                    success: <span class="hljs-literal">false</span>,
                    message: error.message,
                };
            }
            <span class="hljs-keyword">return</span> {
                success: <span class="hljs-literal">true</span>,
                message: <span class="hljs-string">"Successfully signed out"</span>,
            };
        },
    }),
};
</code></pre>
<p>This action handles both sign-in and sign-out methods. A Supabase server instance is created during the sign-in method, and the magic link method is used for sign-in. It passes a redirect URL, which we have yet to create, and handles any errors that may occur.</p>
<p>It also passes the token verification, allowing Supabase to perform verification on our behalf, eliminating the need to call <a target="_blank" href="https://developers.cloudflare.com/turnstile/get-started/server-side-validation/">Cloudflare’s verify APIs</a> directly.</p>
<p>The sign-out method calls Supabase’s sign-out method and handles any potential errors.</p>
<p>The redirect URL refers to an API route that exchanges the code from the email Supabase sends for a session that Supabase handles.</p>
<h3 id="heading-create-the-code-exchange-api-route">Create the Code Exchange API Route</h3>
<p>Create <code>src/pages/api/exchange.ts</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { APIRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;
<span class="hljs-keyword">import</span> { createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../lib/supabase"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> GET: APIRoute = <span class="hljs-keyword">async</span> ({ request, cookies, redirect }) =&gt; {
    <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(request.url);
    <span class="hljs-keyword">const</span> code = url.searchParams.get(<span class="hljs-string">"code"</span>);

    <span class="hljs-keyword">if</span> (!code) {
        <span class="hljs-keyword">return</span> redirect(<span class="hljs-string">"/"</span>);
    }

    <span class="hljs-keyword">const</span> supabase = createClient({ request, cookies });
    <span class="hljs-keyword">const</span> { error } = <span class="hljs-keyword">await</span> supabase.auth.exchangeCodeForSession(code);

    <span class="hljs-keyword">if</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error exchanging code for session:"</span>, error);
        <span class="hljs-keyword">return</span> redirect(<span class="hljs-string">"/404"</span>);
    }

    <span class="hljs-keyword">return</span> redirect(<span class="hljs-string">"/protected"</span>);
};
</code></pre>
<p>This grabs the code from the URL in the magic link sent, creates a server client, and calls the <code>exchangeCodeForSession</code> method with the code. It handles any error by redirecting to Astro’s built-in not-found page.</p>
<p>Otherwise, it will redirect to the protected page as Supabase handles the session implementation details.</p>
<h2 id="heading-part-6-how-to-test-your-application">Part 6: How to Test Your Application</h2>
<p>Start your development server: <code>npm run dev</code></p>
<p>Visit the provided localhost URL. You should see the sign-in page with the Turnstile widget:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750267075336/66ad5f39-67c6-458a-96ea-4dfe1123b015.png" alt="Sign-in page with Turnstile verification and email input field" width="2356" height="956" loading="lazy"></p>
<p>If you try to access the <code>/protected</code> page, it will redirect you back to this view until you sign in. Now, sign in, and you should get an email with a link that will redirect you to the <code>/protected</code> page. This is what you should see:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750335131827/f85cde2f-f9bb-46b0-a09e-6ae6456cd49f.png" alt="Text reads: &quot;You are logged in!&quot; with a field labeled &quot;Your user Id&quot; and a &quot;Sign Out&quot; button below." width="1200" height="502" loading="lazy"></p>
<p>And with that, you've successfully built a comprehensive auth system that leverages Astro actions, Supabase auth, and Cloudflare Turnstile's bot protection. This setup provides a secure, user-friendly authentication experience while protecting your application from malicious actors.</p>
<h2 id="heading-notes-and-additional-resources">Notes and Additional Resources</h2>
<h3 id="heading-useful-documentation">Useful Documentation</h3>
<ul>
<li><p><a target="_blank" href="https://supabase.com/docs/guides/auth/server-side/advanced-guide">Supabase's advanced guide to SSR</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/supabase/ssr">Supabase SSR package</a></p>
</li>
<li><p><a target="_blank" href="https://docs.astro.build/en/reference/api-reference/#cookies">Astro Cookies documentation</a></p>
</li>
<li><p><a target="_blank" href="https://supabase.com/docs/guides/auth/sessions/pkce-flow">Supabase PKCE flow documentation</a></p>
</li>
<li><p><a target="_blank" href="https://docs.astro.build/en/guides/actions/">Astro Actions documentation</a></p>
</li>
<li><p><a target="_blank" href="https://developers.cloudflare.com/turnstile/get-started/">Get started with Turnstile</a></p>
</li>
</ul>
<h3 id="heading-complete-code-repository">Complete Code Repository</h3>
<p>The complete code for this project is available on GitHub:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/FatumaA/supa-ssr">Base authentication setup</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/FatumaA/supa-ssr/tree/add-cloudflare">With Cloudflare Turnstile</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ A Developer's Guide to Protecting Personal Data: Best Practices and Tools ]]>
                </title>
                <description>
                    <![CDATA[ Think about it: you're sitting there enjoying your morning coffee, reading the headlines when again another data breach is making headlines. Millions of users' personal information – gone. You can't help but cringe as a developer at the prospect. Cou... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/developers-guide-to-protecting-personal-data/</link>
                <guid isPermaLink="false">680102ccf67e471495d5a624</guid>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Personal data protection ]]>
                    </category>
                
                    <category>
                        <![CDATA[ encryption ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Databases ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Alex Tray ]]>
                </dc:creator>
                <pubDate>Thu, 17 Apr 2025 13:31:56 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1744839185611/b3e49efc-6eee-4a0b-9522-20407b1782e3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Think about it: you're sitting there enjoying your morning coffee, reading the headlines when again another data breach is making headlines. Millions of users' personal information – gone. You can't help but cringe as a developer at the prospect. Could it happen on your watch?</p>
<p>The reality is, keeping personal data safe isn't something you should be doing because it's good practice – it's something you have to do. Users are trusting developers to care for their data day in and day out, and power must be wielded wisely. If you're writing code that involves getting, processing, or storing someone's personal data, then you should be being proactive about keeping it safe.</p>
<p>So the question is: how do you safely keep personal data?</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<p></p><dl><p></p>
<p></p><ul><p></p>
<p></p><li><a href="heading-know-what-youre-protecting">Know What You</a></li><p></p>
<h2 id="heading-know-what-youre-protecting">Know What You're Protecting</h2>
<p>If you must protect information, first determine what information must be protected. It is crucial to <a target="_blank" href="https://blog.incogni.com/opt-out-guides/">protect sensitive information</a> from unauthorized access to ensure data security. Below is a list of some common types of sensitive data:</p>
<ul>
<li><p>Personally Identifiable Information (PII): name, address, phone number, email, Social Security number.</p>
</li>
<li><p>Financial Data: bank details, payment history, credit card number.</p>
</li>
<li><p>Authentication Data: password, auth tokens, API keys, security question responses.</p>
</li>
<li><p>Health Info: any kind of <a target="_blank" href="https://www.jotform.com/what-is-hipaa-compliance/">HIPAA</a>-protected information about the health and medical history of the user.</p>
</li>
</ul>
<p>Once you know what information has to be rendered secure, then you can go ahead and render it secure.</p>
<h2 id="heading-best-practices-in-data-security">Best Practices in Data Security</h2>
<h3 id="heading-1-encrypt-everything">1. Encrypt Everything</h3>
<p>Your best protection against hacking is encryption. When data is encrypted, even if hackers have access to it, they cannot do anything with it in the absence of the decryption key.</p>
<p>For stored sensitive information, use <strong>hashing with a salt</strong>, a process that turns a password into an irreversible value. This way, even if someone gains access to the stored data, the actual password isn't exposed.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> hashlib
<span class="hljs-keyword">import</span> os

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">hash_password</span>(<span class="hljs-params">password</span>):</span>
    salt = os.urandom(<span class="hljs-number">32</span>)  <span class="hljs-comment"># Generate a new salt</span>
    hashed_password = hashlib.pbkdf2_hmac(<span class="hljs-string">'sha256'</span>, password.encode(<span class="hljs-string">'utf-8'</span>), salt, <span class="hljs-number">100000</span>)
    <span class="hljs-keyword">return</span> salt + hashed_password
</code></pre>
<p>For data in transit, always use HTTPS:</p>
<pre><code class="lang-bash">sudo certbot --nginx -d yourdomain.com
</code></pre>
<p>This ensures data is encrypted between your server and the user. You can also reduce how often data is in transit by using <a target="_blank" href="https://www.suse.com/c/what-is-edge-computing/">edge computing</a>. Rather than sending sensitive data to external servers, increasing risk, it allows data to be stored and processed locally.</p>
<h3 id="heading-2-perform-secure-authentication">2. Perform Secure Authentication</h3>
<p>Weak authentication is an extremely critical security vulnerability.</p>
<p><strong>Authentication</strong> is the process of verifying who a user is (for example, logging in), while <strong>authorization</strong> is verifying what they're allowed to do (for example, access admin features).</p>
<p>Make sure that you:</p>
<ul>
<li><p>Perform strong password habits.</p>
</li>
<li><p>Perform multi-factor authentication (MFA). MFA requires users to present two or more verification factors (for example password and one-time code from a mobile device), making it much harder for attackers to gain access.</p>
</li>
<li><p>Perform OAuth 2.0 or OpenID Connect third-party authentication. These are secure industry-standard protocols that allow users to authenticate via trusted platforms like Google or Facebook, reducing the need to store credentials yourself.</p>
</li>
</ul>
<p>Example: Here’s an authentication setup using JWT (JSON Web Tokens) in Python:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> jwt
<span class="hljs-keyword">import</span> datetime

SECRET_KEY = <span class="hljs-string">"your_secret_key"</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">generate_token</span>(<span class="hljs-params">user_id</span>):</span>
    payload = {
        <span class="hljs-string">"user_id"</span>: user_id,
        <span class="hljs-string">"exp"</span>: datetime.datetime.utcnow() + datetime.timedelta(hours=<span class="hljs-number">1</span>)
    }
    <span class="hljs-keyword">return</span> jwt.encode(payload, SECRET_KEY, algorithm=<span class="hljs-string">'HS256'</span>)
</code></pre>
<p>This function generates a secure token for a user. The token contains the user ID and an expiration time, and it's signed using a secret key. Clients send this token with each request, and servers verify it to ensure the request comes from an authenticated user.</p>
<h3 id="heading-3-minimize-the-data-you-need-to-store">3. Minimize the Data You Need to Store</h3>
<p>One of the simplest things you can do to protect personal data? Store less than you have to. Consider the following questions:</p>
<ul>
<li><p>Do I really need to store this data?</p>
</li>
<li><p>How long do I really need to keep it for?</p>
</li>
<li><p>Can I anonymise it?</p>
</li>
</ul>
<p>For example, if you are going to need analytics, consider deleting personal identifiers prior to storing the data:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> anonymizeData = <span class="hljs-function">(<span class="hljs-params">user</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">sessionId</span>: generateRandomId(),
        <span class="hljs-attr">event</span>: user.event,
        <span class="hljs-attr">timestamp</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString()
    };
};
</code></pre>
<p>This JavaScript function removes identifying information (like name or email) and replaces it with a random session ID, keeping only the data necessary for analytics.</p>
<p>For instance, if you manage email lists, avoid storing unnecessary subscriber data beyond what is required for communication.</p>
<p>Regularly clean and scrub email lists to remove outdated or inactive addresses. Sending emails to outdated/inactive addresses can damage your domain reputation, leading to blacklisting and email deliverability issues. If you only need email addresses for temporary campaigns, consider <a target="_blank" href="https://support.google.com/a/answer/151128?hl=en">automated deletion policies</a> to remove old data.</p>
<h3 id="heading-4-secure-your-apis">4. Secure Your APIs</h3>
<p>If your application is consuming other services, protect your API endpoints. You can do this by:</p>
<ul>
<li><p><strong>Require tokens or API keys</strong>: These act as credentials to access the API and prevent unauthorized use.</p>
</li>
<li><p><strong>Implement rate limiting to deter abuse</strong>: This prevents attackers from flooding your server with too many requests.</p>
</li>
<li><p><strong>Validate and sanitize all input data</strong>: This protects against injection attacks and malformed inputs.</p>
</li>
</ul>
<p>Here's how you can validate API input in Node.js:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> app = express();

app.post(<span class="hljs-string">'/api/data'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { name, email } = req.body;
    <span class="hljs-keyword">if</span> (!name || !email.includes(<span class="hljs-string">'@'</span>)) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).send(<span class="hljs-string">'Invalid input'</span>);
    }
    res.send(<span class="hljs-string">'Data received'</span>);
});
</code></pre>
<p>This ensures the API receives valid data and returns an error for incorrect input, which is a basic form of input sanitization.</p>
<h3 id="heading-5-lock-down-your-database">5. Lock Down Your Database</h3>
<p>Your database is an attack treasure trove, so lock it down:</p>
<ul>
<li><p><strong>Use parameterized queries</strong> to prevent SQL injection. These queries separate data from code.</p>
</li>
<li><p><strong>Limit database access using role-based permissions</strong>: Only give each user or service the access it needs—no more.</p>
</li>
<li><p><strong>Back up and test restoration procedures</strong>: Regular backups ensure you can recover data in the event of a breach or corruption.</p>
</li>
</ul>
<p>Here's a safe way to query a database in Python:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_user</span>(<span class="hljs-params">email</span>):</span>
    conn = sqlite3.connect(<span class="hljs-string">'database.db'</span>)
    cursor = conn.cursor()
    cursor.execute(<span class="hljs-string">"SELECT * FROM users WHERE email = ?"</span>, (email,))
    <span class="hljs-keyword">return</span> cursor.fetchone()
</code></pre>
<p>This example uses a parameterized query (the ? placeholder) to safely insert the email into the SQL command, protecting against injection.</p>
<p>Also, never overlook how databases and internal systems might be accessed remotely. Remote access, whether for IT admins, support teams, or mobile workers, often involves logging in from unfamiliar devices—which introduces new security challenges. Tools that allow for secure, contactless logins without typing passwords or installing software on the remote machine reduce the risk of credential theft.</p>
<p>You can also ensure that remote database connections, SSH access, and admin panels are protected with strong authentication, IP restrictions, and, ideally, VPN access to avoid exposing sensitive entry points to the internet.</p>
<p>And remember, you don’t have to reinvent the wheel—there are <a target="_blank" href="http://blog.scalefusion.com/best-data-protection-software/">powerful data protection tools</a> available to keep your data safe from breaches and downtime. Want to know which ones stand out? Check out this guide for a breakdown of some of the best solutions.</p>
<h3 id="heading-6-periodically-audit-and-update-your-code">6. Periodically Audit and Update Your Code</h3>
<p>Unpatched software and outdated dependencies are essentially an open invitation to the attackers. Update your software and conduct security audits regularly.</p>
<p>Perform security scans for your project:</p>
<pre><code class="lang-javascript">npm audit fix --force  # For Node.js projects
</code></pre>
<pre><code class="lang-python">pip install --upgrade package_name  <span class="hljs-comment"># For Python projects</span>
</code></pre>
<p>These commands help find and fix known vulnerabilities in your project dependencies.</p>
<h3 id="heading-7-train-your-employees">7. Train Your Employees</h3>
<p>Your security is just as strong as your weakest link. If one employee handles sensitive data irresponsibly, everything else may have been for naught.</p>
<ul>
<li><p><strong>Standard security training</strong>: Regular sessions on topics like phishing, password security, and data handling.</p>
</li>
<li><p><strong>Implement solid policies on user data handling</strong>: For instance, never download sensitive data to personal devices.</p>
</li>
<li><p><strong>Establish a security-oriented culture</strong>: Encourage reporting of suspicious activity, regular internal audits, and open communication about threats.</p>
</li>
</ul>
<h3 id="heading-8-give-users-control-over-their-data">8. Give Users Control Over Their Data</h3>
<p>Transparency breeds trust. Give users control to:</p>
<ul>
<li><p>View and download their data.</p>
</li>
<li><p>Terminate their account easily.</p>
</li>
<li><p>Make adjustments in privacy settings.</p>
</li>
</ul>
<p>If you are collecting data, provide an opt-out. Users must be able to protect sensitive data and be in control of what becomes of their information. This is why it is important to have a privacy policy: users need to know what data you are collecting and for what purpose. Check out this <a target="_blank" href="https://www.iubenda.com/en/help/36387-privacy-policy-template">privacy policy template</a> if you need to create one for your site.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Data protection isn't just about coding well—it's about attitude. Get in the head of an attacker for a day, minimize vulnerabilities, and put user privacy at the top of your mind.</p>
<p>So the next time you're scanning the headlines for news of the latest ginormous data breach, you can be confident that your apps are bulletproof. Be smart, continue to learn, and let's make the internet safe—one line of secure code at a time.</p>
</ul></dl> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Backend as a Service (BaaS)? A Beginner's Guide ]]>
                </title>
                <description>
                    <![CDATA[ Building an authentication system can be complex, often requiring a server to store user data. Sometimes, you need a faster, easier solution. For those new to development or without technical expertise, managing servers, databases, and user logins ca... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/backend-as-a-service-beginners-guide/</link>
                <guid isPermaLink="false">67b30da662ec9a593dfeb4a7</guid>
                
                    <category>
                        <![CDATA[ backend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Databases ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ijeoma Igboagu ]]>
                </dc:creator>
                <pubDate>Mon, 17 Feb 2025 10:21:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739291731037/169ad924-9bcb-4af2-9281-fad2488a868d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building an authentication system can be complex, often requiring a server to store user data. Sometimes, you need a faster, easier solution.</p>
<p>For those new to development or without technical expertise, managing servers, databases, and user logins can be overwhelming. This is where Backend as a Service (BaaS) helps.</p>
<p>BaaS platforms provide ready-made backend solutions, making app development simpler. Whether you're a developer or someone with no coding experience, BaaS allows you to focus on your app’s features instead of handling backend complexities.</p>
<p>​​This article will explore BaaS, its features, pricing, and popular BaaS​ tools.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-backend-as-a-service-baas">​​​What is Backend as a Service (BaaS)?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-key-features-of-baas">Key Features of Baas</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-use-backend-as-a-service-baas">Why use Backend as a Service (BaaS)?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-to-use-backend-as-a-service-baas">When to Use Backend as a Service (BaaS)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-the-popular-backend-as-a-service-baas-tools">What are the Popular Backend as a Service (BaaS) Tools?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-started-with-baas-quick-example">How to Get Started with BaaS (Quick Example)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-backend-as-a-service-baas">​​​What is Backend as a Service (BaaS)?</h2>
<p>BaaS is a cloud platform that provides pre-built backend infrastructure and services. It eliminates the need for developers to manage servers, databases, and other backend tasks.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739537937739/4ae3bb03-1196-4298-9299-a0a09c4bd41d.png" alt="Graphical interface of BaaS" width="810" height="583" loading="lazy"></p>
<p>​​<strong>Source:</strong> <a target="_blank" href="https://www.cloudflare.com/fr-fr/learning/serverless/glossary/backend-as-a-service-baas/">https://www.cloudflare.com</a></p>
<h2 id="heading-key-features-of-baas">​​Key Features of BaaS</h2>
<p>Here are some features of BaaS:</p>
<ul>
<li><p>BaaS makes it easy to create and manage user accounts and logins without much coding.</p>
</li>
<li><p>It lets you store and manage data, eliminating the need to set up a database from scratch.</p>
</li>
<li><p>BaaS comes with tools (APIs and SDKs) that help connect your application to the backend easily.</p>
</li>
<li><p>Many BaaS platforms let you see updates in real time, so your application can show live data to users.</p>
</li>
<li><p>BaaS offers space in the cloud to store files and images, making it easy to handle user uploads.</p>
</li>
<li><p>You don’t need to worry about managing servers—BaaS takes care of that for you, so you can focus on building your application.</p>
</li>
<li><p>Some BaaS platforms allow you to send notifications to users about updates or messages.</p>
</li>
<li><p>BaaS often provides tools to track user interactions, helping you understand what works and what doesn’t.</p>
</li>
<li><p>It also makes it easy to integrate with other services like payment systems and social media with minimal effort.</p>
</li>
<li><p>As your app grows, BaaS scales with it, handling more users and data seamlessly.</p>
</li>
</ul>
<h2 id="heading-why-use-backend-as-a-service-baas">Why use Backend as a Service (BaaS)?</h2>
<p>There are several key reasons why BaaS is an excellent choice for developers:</p>
<ul>
<li><p>Pre-built features reduce development time, allowing you to focus on design and functionality instead of backend issues.</p>
</li>
<li><p>With BaaS, you don’t have to worry about servers, scaling, or security updates—the provider takes care of it all.</p>
</li>
<li><p>Most BaaS platforms offer essential features like user authentication, data storage, and real-time updates, helping you build your app without starting from scratch.</p>
</li>
<li><p>As your application gets more users, BaaS can handle it! These services adjust to support more users and data, so you can focus on growing your app.</p>
</li>
<li><p>BaaS handles the infrastructure so you don’t need to spend time or money on the backend. This allows you to focus on design and creating user experiences that add value to your users.</p>
</li>
</ul>
<h2 id="heading-when-to-use-backend-as-a-service-baas">When to Use Backend as a Service (BaaS)</h2>
<p>BaaS is perfect for building an app in a short amount of time without managing the backend. Here are the scenarios when BaaS makes sense:</p>
<ul>
<li><p>BaaS handles your app’s backend, letting you focus on its features. <strong>For example,</strong> when building a to-do list app, BaaS makes it easy to manage user logins and task data without setting up servers from scratch.</p>
</li>
<li><p>For small teams or solo devs, BaaS handles the backend. You do not need extra resources.</p>
</li>
<li><p>If you're launching a startup, Baas lets you release a Minimum Viable Product (MVP) without delay. It helps you speed up development and cut costs. </p>
</li>
<li><p>If your app needs features like user authentication, data storage, or push notifications, BaaS provides them out of the box. For example, when building a social media app, BaaS simplifies user logins and file uploads, saving you from starting from scratch.</p>
</li>
<li><p>BaaS automatically scales to support more users, allowing you to focus on improving your app. For example, a small multiplayer game can start with a few players, and as it grows, BaaS will seamlessly handle thousands without extra backend effort.</p>
</li>
</ul>
<h2 id="heading-what-are-the-popular-backend-as-a-service-baas-tools">What are the Popular Backend as a Service (BaaS) Tools?</h2>
<p>If you're looking to explore BaaS, here are popular platforms you can use:</p>
<h3 id="heading-clerk"><strong>Clerk</strong></h3>
<p>Clerk software focuses on user management. It offers tools for authentication, user profiles, and permissions management. It’s great for developers who need simple user management in their apps.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_C0064052A71C5CFDDDBA59A6AE53132401EA70FC25ACA9B576D0C25C8E9EB8BE_1730034843051_FireShot+Capture+598+-+Clerk+-+Authentication+and+User+Management+-+clerk.com.png" alt="The Graphical Interface of Clerk" width="1920" height="970" loading="lazy"></p>
<h3 id="heading-features-of-clerk"><strong>Features of clerk</strong></h3>
<p>Clerk provides:</p>
<ul>
<li><p>Multi-factor authentication (MFA)</p>
</li>
<li><p>Passwordless login (magic links, OTPs)</p>
</li>
<li><p>Social &amp; OAuth login (Google, GitHub, and so on)</p>
</li>
<li><p>Enterprise SSO (SAML, OAuth)</p>
</li>
<li><p>Biometric login (Face ID, Touch ID)</p>
</li>
</ul>
<p>It also handles:</p>
<ul>
<li><p>User profiles &amp; custom attributes</p>
</li>
<li><p>Roles &amp; permissions</p>
</li>
<li><p>Teams &amp; organizations</p>
</li>
<li><p>Session management</p>
</li>
</ul>
<p>For security, it offers:</p>
<ul>
<li><p>Token-based authentication (JWT)</p>
</li>
<li><p>Rate limiting</p>
</li>
<li><p>Audit logs</p>
</li>
<li><p>GDPR &amp; SOC 2 compliance</p>
</li>
</ul>
<p>For developers, it comes with:</p>
<ul>
<li><p>Prebuilt UI components</p>
</li>
<li><p>SDKs for React, Next.js, Vue, and so on</p>
</li>
<li><p>Custom email &amp; SMS templates</p>
</li>
</ul>
<p>To learn more, click here: <a target="_blank" href="https://clerk.com/">Clerk</a></p>
<h3 id="heading-pricing"><strong>Pricing</strong></h3>
<p>Clerk offers a <strong>Free Plan</strong> that includes up to 10,000 Monthly Active Users (MAUs) at no cost. For more advanced features, the <strong>Pro Plan</strong> is available at $25 per month, which also includes the first 10,000 MAUs.</p>
<p>For detailed and up-to-date information on Clerk's pricing plans, please visit their <a target="_blank" href="https://clerk.com/pricing">official pricing page</a>:</p>
<h3 id="heading-firebase"><strong>Firebase</strong></h3>
<p>Firebase is a Google-backed BaaS platform. It is known for its real-time databases, authentication, and cloud storage. It also has easy-to-use tools for web and mobile apps.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_C0064052A71C5CFDDDBA59A6AE53132401EA70FC25ACA9B576D0C25C8E9EB8BE_1730035263750_FireShot+Capture+599+-+Firebase+-+Googles+Mobile+and+Web+App+Development+Platform_+-+firebase.google.com.png" alt="The Graphical Interface of Firebase" width="1920" height="970" loading="lazy"></p>
<h3 id="heading-features-of-firebase">Features of Firebase</h3>
<p>Firebase provides:</p>
<h3 id="heading-backend-services"><strong>Backend Services</strong></h3>
<ul>
<li><p>Firestore &amp; Realtime Database</p>
</li>
<li><p>Cloud Storage</p>
</li>
<li><p>Serverless Functions</p>
</li>
<li><p>Web Hosting</p>
</li>
</ul>
<h3 id="heading-authentication"><strong>Authentication</strong></h3>
<ul>
<li><p>Email &amp; password login</p>
</li>
<li><p>Social logins (Google, Facebook, and so on)</p>
</li>
<li><p>Phone authentication</p>
</li>
<li><p>Anonymous sign-in</p>
</li>
</ul>
<h3 id="heading-analytics-amp-monitoring"><strong>Analytics &amp; Monitoring</strong></h3>
<ul>
<li><p>Google Analytics</p>
</li>
<li><p>Crash tracking (Crashlytics)</p>
</li>
<li><p>Performance monitoring</p>
</li>
<li><p>A/B testing</p>
</li>
</ul>
<h3 id="heading-engagement-tools"><strong>Engagement Tools</strong></h3>
<ul>
<li><p>Push notifications</p>
</li>
<li><p>Remote app updates</p>
</li>
<li><p>In-app messaging</p>
</li>
</ul>
<h3 id="heading-machine-learning"><strong>Machine Learning</strong></h3>
<ul>
<li><p>Text recognition</p>
</li>
<li><p>Image labelling</p>
</li>
</ul>
<p>To learn more, click here: <a target="_blank" href="https://firebase.google.com/">Firebase</a></p>
<h3 id="heading-pricing-plan">Pricing plan</h3>
<p>Firebase offers a <strong>Spark Plan</strong> (free tier) and a <strong>Blaze Plan</strong> (pay-as-you-go). The Spark Plan provides limited free usage, while the Blaze Plan charges based on your actual usage. For detailed and up-to-date information on Firebase's pricing plans, please visit their <a target="_blank" href="https://firebase.google.com/pricing">official pricing page</a>.</p>
<h3 id="heading-convex"><strong>Convex</strong></h3>
<p>Convex is a serverless BaaS platform. It provides real-time data sync and scalable backend services. The design simplifies serverless computing for developers.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_C0064052A71C5CFDDDBA59A6AE53132401EA70FC25ACA9B576D0C25C8E9EB8BE_1730035688864_FireShot+Capture+600+-+Convex+-+The+fullstack+TypeScript+development+platform+-+www.convex.dev.png" alt="The Graphical Interface of Convex" width="1920" height="970" loading="lazy"></p>
<h3 id="heading-convex-features"><strong>Convex Features</strong></h3>
<ul>
<li><p><strong>Database</strong> – Real-time data storage</p>
</li>
<li><p><strong>Serverless Functions</strong> – Run backend logic without managing servers</p>
</li>
<li><p><strong>Authentication</strong> – Built-in user auth &amp; access control</p>
</li>
<li><p><strong>Caching</strong> – Faster data retrieval</p>
</li>
<li><p><strong>Webhooks &amp; Crons</strong> – Automate tasks &amp; trigger events</p>
</li>
</ul>
<p>To learn more, click here: <a target="_blank" href="https://www.convex.dev/">Convex</a></p>
<h3 id="heading-pricing-1"><strong>Pricing</strong></h3>
<ul>
<li><p><strong>Free Plan</strong> – Limited resources for small projects</p>
</li>
<li><p><strong>Pro Plan</strong> – Pay-as-you-go based on usage</p>
</li>
</ul>
<p>Check out full details for <a target="_blank" href="https://convex.dev/pricing">convex pricing</a></p>
<h3 id="heading-8base"><strong>8base</strong></h3>
<p>A low-code platform that allows developers to build serverless apps with minimal setup. It provides database management, authentication, and API development tools.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_C0064052A71C5CFDDDBA59A6AE53132401EA70FC25ACA9B576D0C25C8E9EB8BE_1730036229410_gui+8base.png" alt="The Graphical Interface of 8base" width="1920" height="970" loading="lazy"></p>
<h3 id="heading-8base-features"><strong>8base Features</strong></h3>
<ul>
<li><p><strong>Backend Builder</strong> – Manage your database easily.</p>
</li>
<li><p><strong>Serverless Functions</strong> – Run custom backend logic.</p>
</li>
<li><p><strong>GraphQL API</strong> – Auto-generated API for your data.</p>
</li>
<li><p><strong>Authentication</strong> – Built-in user login &amp; access control.</p>
</li>
<li><p><strong>File Management</strong> – Store and manage files.</p>
</li>
</ul>
<p>To learn more, click here: <a target="_blank" href="https://www.8base.com/">8base</a></p>
<h3 id="heading-pricing-2"><strong>Pricing</strong></h3>
<ul>
<li><p><strong>Free Plan</strong> – $0/month (1 developer, basic features).</p>
</li>
<li><p><strong>Developer Plan</strong> – $25/month per developer.</p>
</li>
<li><p><strong>Professional Plan</strong> – $150/month (5 developers).</p>
</li>
<li><p><strong>Custom Plan</strong> – Contact 8base for enterprise solutions.</p>
</li>
</ul>
<p>Check out full pricing details here: <a target="_blank" href="https://www.8base.com/pricing">8base Pricing</a></p>
<h3 id="heading-backendless"><strong>Backendless</strong></h3>
<p>Backendless is a no-code platform that makes app development easy. It provides APIs, data storage, user management, and real-time updates in one place.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_C0064052A71C5CFDDDBA59A6AE53132401EA70FC25ACA9B576D0C25C8E9EB8BE_1730036359851_FireShot+Capture+584+-+Backendless+Visual+App+Development+Platform+-+UI+Backend++Database_+-+backendless.com.png" alt="The Graphical Interface of Backendless" width="1920" height="970" loading="lazy"></p>
<h3 id="heading-features">Features</h3>
<ul>
<li><p><strong>UI Builder</strong>: Design your app's front end visually without coding.</p>
</li>
<li><p><strong>Real-Time Database</strong>: Store and sync data in real-time across clients.</p>
</li>
<li><p><strong>User Authentication</strong>: Manage user sign-ups, logins, and roles.</p>
</li>
<li><p><strong>Cloud Code</strong>: Implement custom server-side logic without managing servers.</p>
</li>
<li><p><strong>Push Notifications</strong>: Send real-time alerts to users on various devices.</p>
</li>
</ul>
<p>To learn more, click here: <a target="_blank" href="https://backendless.com/">Backendless</a></p>
<h3 id="heading-pricing-3">Pricing</h3>
<p>Backendless offers several plans to suit different needs:</p>
<ul>
<li><p><strong>Free Plan</strong>: Ideal for small projects or learning purposes.</p>
</li>
<li><p><strong>Scale Fixed Plan</strong>: Provides predictable monthly billing with set resource limits.</p>
</li>
<li><p><strong>Scale Variable Plan</strong>: Offers flexibility with usage-based billing, scaling as your app grows.</p>
</li>
<li><p><strong>Backendless Pro</strong>: A self-hosted solution for enterprises requiring unlimited scalability and control.</p>
</li>
</ul>
<p>For more details on Backendless's pricing plans, please visit their <a target="_blank" href="https://backendless.com/pricing/">official pricing plan page</a>.</p>
<h3 id="heading-appwrite"><strong>Appwrite</strong></h3>
<p>Appwrite is an open-source BaaS that provides databases, authentication, file storage, real-time updates, serverless functions, and API management. It supports multiple platforms and offers built-in security and scalability for modern apps.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_C0064052A71C5CFDDDBA59A6AE53132401EA70FC25ACA9B576D0C25C8E9EB8BE_1730036473890_FireShot+Capture+583+-+Appwrite+-+Build+like+a+team+of+hundreds+-+appwrite.io.png" alt="The Graphical Interface of Appwrite" width="1920" height="970" loading="lazy"></p>
<h3 id="heading-features-1">Features</h3>
<ul>
<li><p><strong>Authentication</strong>: Secure user login with over 30 methods, including email/password, OAuth, and magic URLs.</p>
</li>
<li><p><strong>Database</strong>: Scalable storage with advanced permissions, custom data validation, and support for relationships.</p>
</li>
<li><p><strong>Functions</strong>: Deploy serverless functions in over 13 languages, with automatic GitHub deployment and custom domain support.</p>
</li>
<li><p><strong>Storage</strong>: Manage and serve files with built-in security and privacy features.</p>
</li>
<li><p><strong>Real-Time</strong>: Subscribe to database events for instant updates.</p>
</li>
</ul>
<p>To learn more, click here: <a target="_blank" href="https://appwrite.io/">Appwrite</a></p>
<h3 id="heading-pricing-4"><strong>Pricing</strong></h3>
<ul>
<li><p><strong>Free</strong> – $0/month (5GB bandwidth, 2GB storage, 750K function runs).</p>
</li>
<li><p><strong>Pro</strong> – Starts at $15/month (more storage, bandwidth, &amp; features).</p>
</li>
<li><p><strong>Scale</strong> – Starts at $599/month (for large-scale projects).</p>
</li>
</ul>
<p>For more details on the pricing plan check their <a target="_blank" href="https://appwrite.io/pricing">official pricing page</a>.</p>
<h3 id="heading-nhost"><strong>Nhost</strong></h3>
<p>Nhost is a full backend platform with a GraphQL API, database, authentication, and storage. It’s easy to set up and great for modern app development.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_C0064052A71C5CFDDDBA59A6AE53132401EA70FC25ACA9B576D0C25C8E9EB8BE_1730036732414_FireShot+Capture+585+-+Nhost_+The+Open+Source+Firebase+Alternative+with+GraphQL+-+nhost.io.png" alt="s_C0064052A71C5CFDDDBA59A6AE53132401EA70FC25ACA9B576D0C25C8E9EB8BE_1730036732414_FireShot+Capture+585+-+Nhost_+The+Open+Source+Firebase+Alternative+with+GraphQL+-+nhost.io" width="1920" height="970" loading="lazy"></p>
<h3 id="heading-nhost-features"><strong>Nhost Features</strong></h3>
<ul>
<li><p><strong>Authentication</strong> – Secure login with email, OAuth, and so on.</p>
</li>
<li><p><strong>Database</strong> – Scalable storage with permissions.</p>
</li>
<li><p><strong>Serverless Functions</strong> – Run backend code without servers.</p>
</li>
<li><p><strong>Storage</strong> – Secure file hosting.</p>
</li>
<li><p><strong>Real-Time</strong> – Instant updates on data changes.</p>
</li>
</ul>
<p>To learn more, click here: <a target="_blank" href="https://nhost.io/">Nhost</a>.</p>
<h3 id="heading-pricing-5"><strong>Pricing</strong></h3>
<ul>
<li><p><strong>Free</strong> – $0/month (basic features for small projects).</p>
</li>
<li><p><strong>Pro</strong> – $25/month (more resources &amp; support).</p>
</li>
<li><p><strong>Dedicated Compute</strong> – $50/month per vCPU/2GB RAM (for scaling apps).</p>
</li>
</ul>
<p>Check out full details here: <a target="_blank" href="https://nhost.io/pricing">Nhost Pricing</a></p>
<h3 id="heading-back4apps"><strong>Back4apps</strong></h3>
<p>Back4App is an open-source BaaS that simplifies backend development. It provides a complete infrastructure for building, hosting, and managing scalable apps. With built-in server-side features, developers can focus on coding without managing servers or databases.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_C0064052A71C5CFDDDBA59A6AE53132401EA70FC25ACA9B576D0C25C8E9EB8BE_1730036859543_FireShot+Capture+587+-+Build+launch+and+scale+applications+faster+than+ever+with+the+power_+-+www.back4app.com.png" alt="The Graphical Interface of Back4apps" width="1920" height="970" loading="lazy"></p>
<h3 id="heading-back4app-features"><strong>Back4App Features</strong></h3>
<ul>
<li><p><strong>Database</strong> – Manage data with APIs &amp; a visual editor</p>
</li>
<li><p><strong>Authentication</strong> – Secure user login &amp; roles</p>
</li>
<li><p><strong>Real-Time</strong> – Instant data updates</p>
</li>
<li><p><strong>Push Notifications</strong> – Send alerts to users.</p>
</li>
<li><p><strong>Cloud Functions</strong> – Run custom backend code.</p>
</li>
</ul>
<p>To learn more, click here: <a target="_blank" href="https://www.back4app.com/">Back4apps</a>.</p>
<h3 id="heading-pricing-6"><strong>Pricing</strong></h3>
<ul>
<li><p><strong>Free</strong> – 25K requests, 250MB storage, 1GB transfer/month.</p>
</li>
<li><p><strong>MVP Plan</strong> – For launching small apps.</p>
</li>
<li><p><strong>Dedicated Plan</strong> – For production apps with more resources.</p>
</li>
</ul>
<p>The <strong>MVP Plan</strong> in Back4App refers to a <strong>Minimum Viable Product (MVP) Plan</strong>. It is designed for startups and developers who are launching a small app with essential backend services. This plan provides enough resources to test and validate an idea before scaling up.</p>
<p>While <strong>Dedicated Plan</strong> in Back4App provides a <strong>private server with dedicated resources</strong> for apps that need better performance, security, and scalability. It is ideal for production apps with high traffic or specific infrastructure requirements.</p>
<p>Check out full details here: <a target="_blank" href="https://www.back4app.com/pricing">Back4App Pricing</a>.</p>
<h3 id="heading-aws-amplify"><strong>AWS Amplify</strong></h3>
<p>AWS Amplify is a development platform from Amazon Web Services (AWS). It simplifies building and deploying web and mobile apps. It offers tools and services for developers. They can integrate scalable backends, manage frontends, and add features like authentication, storage, and APIs.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_C0064052A71C5CFDDDBA59A6AE53132401EA70FC25ACA9B576D0C25C8E9EB8BE_1730036938873_FireShot+Capture+588+-+Full+Stack+Development+-+Web+and+Mobile+Apps+-+AWS+Amplify+-+aws.amazon.com.png" alt="The Graphical Interface of Aws Amplify" width="1920" height="970" loading="lazy"></p>
<h3 id="heading-aws-amplify-features"><strong>AWS Amplify Features</strong></h3>
<ul>
<li><p><strong>Authentication</strong> – Secure login with email, social sign-in, and multi-factor authentication</p>
</li>
<li><p><strong>Database &amp; API</strong> – Build real-time APIs with AWS databases</p>
</li>
<li><p><strong>Storage</strong> – Manage files and media with Amazon S3</p>
</li>
<li><p><strong>Hosting</strong> – Deploy full-stack apps with continuous deployment</p>
</li>
</ul>
<p>To learn more, click here: <a target="_blank" href="https://aws.amazon.com/amplify/?gclid=Cj0KCQjwpP63BhDYARIsAOQkATZlSP8VJyO8gGZMtrSp7JE6hMJjFPh1Am4F2eQv5Yex_okPLLvWjlUaAgDQEALw_wcB&amp;trk=e37f908f-322e-4ebc-9def-9eafa78141b8&amp;sc_channel=ps&amp;ef_id=Cj0KCQjwpP63BhDYARIsAOQkATZlSP8VJyO8gGZMtrSp7JE6hMJjFPh1Am4F2eQv5Yex_okPLLvWjlUaAgDQEALw_wcB:G:s&amp;s_kwcid=AL!4422!3!647301987559!p!!g!!amplify%20framework!19613610159!148358959649">Aws Amplify</a></p>
<h3 id="heading-pricing-7"><strong>Pricing</strong></h3>
<ul>
<li><p><strong>Free Tier (First 12 months)</strong></p>
<ul>
<li><p>1,000 build minutes/month</p>
</li>
<li><p>5GB storage</p>
</li>
<li><p>15GB bandwidth</p>
</li>
<li><p>500K API requests</p>
</li>
</ul>
</li>
<li><p><strong>Pay-As-You-Go (After Free Tier)</strong></p>
<ul>
<li><p><strong>Build &amp; Deploy</strong> – $0.01 per build minute</p>
</li>
<li><p><strong>Storage</strong> – $0.023 per GB/month</p>
</li>
<li><p><strong>Bandwidth</strong> – $0.15 per GB served</p>
</li>
<li><p><strong>API Requests</strong> – $0.30 per 1M requests</p>
</li>
</ul>
</li>
</ul>
<p>Full details here: <a target="_blank" href="https://aws.amazon.com/amplify/pricing/">AWS Amplify Pricing</a></p>
<h3 id="heading-supabase"><strong>Supabase</strong></h3>
<p>Supabase is an open-source alternative to Firebase. It uses PostgreSQL for its database. It has built-in features like authentication, APIs, and real-time subscriptions.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_C0064052A71C5CFDDDBA59A6AE53132401EA70FC25ACA9B576D0C25C8E9EB8BE_1730037060219_FireShot+Capture+581+-+Supabase+-+The+Open+Source+Firebase+Alternative+-+supabase.com.png" alt="The Graphical Interface of Supabase" width="1920" height="970" loading="lazy"></p>
<h3 id="heading-supabase-features"><strong>Supabase Features</strong></h3>
<ul>
<li><p><strong>Database</strong> – PostgreSQL with full SQL support.</p>
</li>
<li><p><strong>Authentication</strong> – Secure login with email, password, and social logins.</p>
</li>
<li><p><strong>Storage</strong> – Store and serve files easily.</p>
</li>
<li><p><strong>Real-Time</strong> – Get instant updates when data changes.</p>
</li>
<li><p><strong>Edge Functions</strong> – Run serverless backend logic.</p>
</li>
</ul>
<p>To learn more, click here: <a target="_blank" href="https://supabase.com/">Supabase</a>.</p>
<h3 id="heading-pricing-8"><strong>Pricing</strong></h3>
<ul>
<li><p><strong>Free</strong> – Great for small projects i.e. projects for learning, and experimentation.</p>
</li>
<li><p><strong>Pro</strong> – Starts at $25/month (includes $10 compute credits).</p>
</li>
<li><p><strong>Team</strong> – Starts at $599/month (for advanced features &amp; support).</p>
</li>
</ul>
<p>Full details here: <a target="_blank" href="https://supabase.com/pricing">Supabase Pricing</a></p>
<h2 id="heading-how-to-get-started-with-baas-quick-example">How to Get Started with BaaS (Quick Example)</h2>
<p>Let’s go through a quick example to get started. In this tutorial, I’ll use Firebase as an example.</p>
<ul>
<li><p>Go to the <a target="_blank" href="https://firebase.google.com/">Firebase website</a> and sign up using your Google account.</p>
</li>
<li><p>After signing in, create a new Firebase project by following the on-screen instructions.</p>
</li>
<li><p>Go to "Authentication" and enable a sign-in method, like email/password or Google login</p>
</li>
<li><p>In "Firestore Database," create a new database for your app's data.</p>
</li>
<li><p>Install Firebase SDK in your project and integrate authentication, databases, and other Firebase services into your app.</p>
</li>
</ul>
<p>For more detailed instructions on setting up Firebase, check out this article: <a target="_blank" href="https://www.freecodecamp.org/news/authenticate-react-app-using-firebase/">How to Authenticate Your React App Using Firebase</a> where I explain each step in depth.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Backend as a Service (BaaS) is ideal for developers. It provides an efficient and cost-effective way to handle backend development tasks. BaaS can speed up your development. It lets you avoid server management. You can then focus on building better apps.</p>
<p>If you're new to backend development, check out the BaaS tools in this article. They can simplify your workflow. Try out BaaS today and take your development to the next level!</p>
<p>Have you tried using BaaS for your applications? Share your experiences!</p>
<p>If you found this article helpful, share it with others who may find it interesting.</p>
<p>Stay updated with my projects by following me on <a target="_blank" href="https://twitter.com/ijaydimples">Twitter</a>, <a target="_blank" href="https://twitter.com/ijaydimples">LinkedIn</a> and <a target="_blank" href="https://github.com/ijayhub">GitHub</a>.</p>
<p>Thank you for reading.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Keycloak for Identity and Access Management ]]>
                </title>
                <description>
                    <![CDATA[ Whether your deployment requires logins from hundreds of thousands of end users or just a few remote admins, there's no escaping the need to properly control access to your infrastructure. And integrating those logins with industry-standard tools lik... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/keycloak-identity-and-access-management/</link>
                <guid isPermaLink="false">67924b2d0a4e059db2a7577f</guid>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ IAM ]]>
                    </category>
                
                    <category>
                        <![CDATA[ keycloak ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SSO ]]>
                    </category>
                
                    <category>
                        <![CDATA[ single sign on ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ David Clinton ]]>
                </dc:creator>
                <pubDate>Thu, 23 Jan 2025 13:59:09 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1737640179567/36b76fb3-3e9f-4124-a4d5-bb7d11428a6c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Whether your deployment requires logins from hundreds of thousands of end users or just a few remote admins, there's no escaping the need to properly control access to your infrastructure. And integrating those logins with industry-standard tools like LDAP and Active Directory can cut down the amount of work it'll take to get yourself up and running.</p>
<p>Keycloak is an enterprise-ready, open source identity access management (IAM) solution that's scalable, extensible, and robust. And it really doesn't need all that much care and feeding to launch a simple implementation.</p>
<p>This article will introduce you to the technology and the ways it can integrate best-practice authentication into your infrastructure.</p>
<p><em>Note on Hitachi Contributions to Keycloak:</em></p>
<p>Takashi Norimatsu works for Hitachi and has been the official maintainer of Keycloak since late 2021. Hitachi has been actively contributing to Keycloak since at least 2018.</p>
<p><a target="_blank" href="https://www.hitachi.com/New/cnews/month/2024/11/241108.html">Hitachi appears to be doing more strategically with open source in general</a> and Keycloak in particular. I believe strong, continued corporate support as part of an open source project is a positive sign, but at the very least, you should be aware of the corporate support for Keycloak during your assessment.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737406737359/1fc95289-7777-4c9f-a651-00bd8a44b517.png" alt="1fc95289-7777-4c9f-a651-00bd8a44b517" class="image--center mx-auto" width="751" height="589" loading="lazy"></p>
<h2 id="heading-getting-started-with-keycloak">Getting Started with Keycloak</h2>
<p>I'll begin with a brief "quick start". As you can see from this screenshot, Keycloak will run happily on multiple platforms. And their <a target="_blank" href="https://www.keycloak.org/guides">product documentation is excellent</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737406768154/a84122e2-1e72-43a8-86f2-aeaddd0c3a3b.png" alt="a84122e2-1e72-43a8-86f2-aeaddd0c3a3b" class="image--center mx-auto" width="1280" height="720" loading="lazy"></p>
<p>But here's some very simple one-command Docker syntax that will create a fully-functioning live Keycloak instance on your local machine:</p>
<pre><code class="lang-bash">docker run -p 8080:8080 \
     -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
     -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:26.0.7 start-dev
</code></pre>
<p>That's it. After a minute or two, you can open the administration interface on your browser using the appropriate variation of:</p>
<p><a target="_blank" href="http://localhost:8080">localhost:8080</a></p>
<p>Based on the Docker command defaults, you'll log in using admin and admin. Spend a few minutes digging into the environment to get a feel for the tools that are available.</p>
<h2 id="heading-what-keycloak-offers">What Keycloak Offers</h2>
<p>Ok. So why do you need Keycloak? Because it supports all the functionality demanded by modern deployments. That'll include Single Sign-On (SSO) to allow seamless authentication across multiple applications and services, OAuth2, OpenID Connect, SAML protocol compliance, and federated identities using existing LDAP or Active Directory setups or through social media logins like Google.</p>
<p>Keycloak incorporates the use of Multi-factor Authentication (MFA), built-in token revocation and expiration mechanisms, fine-grained permission management through Role-based Access Control (RBAC), and end-to-end encryption for sensitive communications. GDPR, HIPAA, and PCI DSS compliance are all possible.</p>
<p>Keycloak comes with a RESTful API for scripted and programmatic interactions. That will encourage task automation to further optimize your authentication processes. And your developers can build their own custom plugins to fill any usability gaps you encounter.</p>
<h2 id="heading-the-business-case-for-keycloak">The Business Case for Keycloak</h2>
<p>Because Keycloak is open source, there'll be no license fees to worry about. But open source gives you a lot more than just "cheap".</p>
<p>Keycloak cuts out vendor lock-in, allowing you to work with any platform or cloud provider – or move between them whenever necessary. It can also reduce overall operational costs through its simplified deployments (how much time did it take you to get that Docker image up and running?), automated updates, and no limits or cost penalties for even millions of monthly API calls or active users.</p>
<p>Having out-of-the-box (and free) access to the full feature set (including RBAC and MFA) also simplifies planning and execution. There's nothing "more" efficient than having to wait a week to access paywalled functionality until you get a response to your request for more project funding. All Keycloak features are just a click away.</p>
<p>This radar chart illustrates the feature and functionality differences between Keycloak and its major commercial peers.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737407002045/d9a45f49-afbb-4709-a9da-016782d7c6ae.png" alt="Differences between Keycloak, Okta, Auth0, and Azure AD" class="image--center mx-auto" width="1750" height="1424" loading="lazy"></p>
<h3 id="heading-what-to-consider">What to Consider</h3>
<p>As much as Keycloak has to offer, it won't be the ideal choice for every use-case. And there are issues about which you should be aware up front.</p>
<p>For instance, while getting started may be easy, fully configuring, say, clustering and high availability for Keycloak can be complex for teams without experience in identity management. Managing latency issues for very large deployments can be challenging.</p>
<p>And while the documentation is generally excellent, it may not fully address specific complexities or edge-case scenarios. Similarly, there's no resource within the Keycloak community that offers guaranteed support. Although there are excellent third-party providers out there.</p>
<p>It's possible that, because you're not working with a commercial product, demonstrating regulatory compliance could be a bit more involved. You may also need to adapt your logging functionality to comply with various audit trail requirements.</p>
<p>Finally, customizable environments risk introducing destabilizing complexity. The further off the beaten trail your plugins and API implementations wander, the greater the odds that something will eventually break – especially around version upgrades.</p>
<h2 id="heading-your-next-steps">Your Next Steps</h2>
<p>It's always helpful to explore the journeys other people took with a new technology.</p>
<p>So <a target="_blank" href="https://www.redhat.com/en/blog/keycloak-success-stories-from-the-openshift-commons-gathering-amsterdam-2023">this page</a> includes information on a fascinating case study involving a Japanese bank that was looking for an API solution and decided on Keycloak because of its high level API security features. Yuichi Nakamura’s presentation <a target="_blank" href="https://youtu.be/jH7-tyrUP9E?si=6gKMdYH-o0LMiYFZ&amp;t=490">at the OpenShift Commons event in 2023</a> gives details how the bank successfully used Keycloak to secure their APIs. Nakamura, Hitachi Chief OSS Strategist, has recently been appointed as Head of Hitachi Open Source Program Office (OSPO).</p>
<p>And <a target="_blank" href="https://hossted.com/knowledge-base/case-studies/infrastructure-and-network/security/enhancing-authentication-services-with-freeipa-and-keycloak/">this is an account</a> of a university that implemented Kerberos Single Sign-On (SSO) for FreeIPA and configured Keycloak to connect with FreeIPA. The university successfully achieved user authentication from Keycloak by leveraging the SSSD option under “user federation” instead of relying on Kerberos or LDAP.</p>
<p>I’m no stranger to Keycloak myself, having taught a <a target="_blank" href="https://www.pluralsight.com/courses/keycloak-getting-started">Getting Started with Keycloak course on Pluralsight</a>. For beginners, this may be a good place to start. A 10 day free trial is available.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Implement RBAC in a Community Dashboard with Nuxt ]]>
                </title>
                <description>
                    <![CDATA[ Role Based Access Control (RBAC) is a useful authorization model for users with different access levels, such as those in a community dashboard. In this article, you’ll learn how to integrate this type of authorization with Permit.io in Nuxt. Table o... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/rbac-community-dashboard-with-nuxt/</link>
                <guid isPermaLink="false">6740bf4cd3bb67c73837c3ef</guid>
                
                    <category>
                        <![CDATA[ authorization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Nuxt.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Obum ]]>
                </dc:creator>
                <pubDate>Fri, 22 Nov 2024 17:28:44 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732043730534/d88680d7-2590-4541-9120-40a43c3724ef.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Role Based Access Control (RBAC) is a useful authorization model for users with different access levels, such as those in a community dashboard.</p>
<p>In this article, you’ll learn how to integrate this type of authorization with Permit.io in Nuxt.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-table-of-contents">Table of Contents</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-the-difference-between-authentication-and-authorization">What is the Difference Between Authentication and Authorization?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-role-based-access-control-rbac">What is Role Based Access Control (RBAC)?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-the-benefits-of-using-authorization-as-a-service">What are the Benefits of Using Authorization As A Service?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-well-be-building">What We’ll Be Building</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-plan-rbac-with-permitio">How to Plan RBAC with</a> <a target="_blank" href="http://Permit.io">Permit.io</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-setup-permitio-in-nuxt">How to Setup</a> <a target="_blank" href="http://Permit.io">Permit.io</a> <a class="post-section-overview" href="#heading-how-to-setup-permitio-in-nuxt">in Nuxt</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-control-api-access-with-nuxt-middleware">How to Control API Access with Nuxt Middleware</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-test-rbac-in-the-community-dashboard">How to Test RBAC in the Community Dashboard</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-what-is-the-difference-between-authentication-and-authorization">What is the Difference Between Authentication and Authorization?</h2>
<p>When building applications, we often carry out authentication together with authorization. However, these two concepts are essentially different.</p>
<p>Authentication is verifying who a user is. During the authentication process, the user usually needs to log in with some identifier like email, phone, username, <em>with</em> Google, <em>with</em> Microsoft, and so on.</p>
<p>Authorization specifies the resources an authenticated user can view and what they can do in the application. Authorization tells a user's access rights after that user has been successfully authenticated.</p>
<p>For example, authentication is a user logging in with email and password or verifying their phone number with SMS. On the other hand, authorization is a writer creating and editing posts while only admins can approve and publish those posts.</p>
<p>The primary purpose of authentication is to establish a user's identity before granting them access to the system. The main goal of authorization is to control user actions and protect sensitive data or resources.</p>
<h2 id="heading-what-is-role-based-access-control-rbac">What is Role Based Access Control (RBAC)?</h2>
<p>Role-Based Access Control (RBAC) is an authorization model that you can use to manage and restrict access to system resources. It is based on the responsibilities, duties, or roles of users.</p>
<p>In RBAC, roles represent predefined sets of permissions that tell which actions a user can execute in an application. These roles are then assigned to users based on their job functions or responsibilities.</p>
<p>In common systems, anyone can assign permissions to individual users. In RBAC, we group permissions into roles. In turn, we assign these roles to users. For example, in a community dashboard, users might have roles like “Admin”, “Mentor”, or “Member”.</p>
<p>Aside from Role-Based Access Control, other popular authorization models exist such as Attribute-Based (ABAC) and Relationship-based (ReBAC) access controls. Attribute-Based Access Control uses a wide range of attributes and fits constantly changing systems. You could also combine it with Role-Based Access Control. For more info, see the <a target="_blank" href="https://www.permit.io/blog/rbac-vs-abac">Permit.io article on RBAC vs. ABAC</a>.</p>
<h2 id="heading-what-are-the-benefits-of-using-authorization-as-a-service">What are the Benefits of Using Authorization As A Service?</h2>
<p>You can build authorization models yourself in your application. But it may be time-consuming and cost-ineffective in the long run.</p>
<p>Using an external provider for authorization allows you to focus on the business logic in your applications. The benefits of outsourcing authorization are similar to using a third party like Auth0 or Firebase for Authentication.</p>
<p><a target="_blank" href="https://www.permit.io/blog/authorization-as-a-service">Authorization as a Service</a> provides a solution for managing user access and permissions in applications. When you use such an authorization solution, you enjoy enhanced security, granular access policies’ control, auto-scaling policies, reduced maintenance burden, faster upgrades, robust logging, and so on.</p>
<p>Permit.io is free to use for <a target="_blank" href="https://docs.permit.io/manage-your-account/workspace-usage/#maus--tenants-usage">up to a 1000 Monthly Active Users</a>, and has a UI and API for RBAC, ABAC, and ReBAC.</p>
<p>To Get Started with Permit.io:</p>
<ul>
<li><p>Go to <a target="_blank" href="https://app.permit.io">app.permit.io</a></p>
</li>
<li><p>Create an account</p>
</li>
<li><p>Create a workspace (in your account)</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732037452876/4604ad72-c5cc-4e07-8f04-70ff4f3dbb8c.gif" alt="Demo of Getting Started with Permit.io" class="image--center mx-auto" width="1280" height="720" loading="lazy"></p>
<h2 id="heading-what-well-be-building">What We’ll Be Building</h2>
<p>A community dashboard connects members within a community or forum. It is a platform where they can interact and access resources.</p>
<p>For demo purposes of RBAC, we’ll build a simple community dashboard that will include 3 types of content (entities): posts, materials, and announcements<em>.</em></p>
<p>The code we will be using is at <a target="_blank" href="https://github.com/obumnwabude/rbac-community-dashboard">https://github.com/obumnwabude/rbac-community-dashboard</a>. It consists of a Nuxt repo with its server configured for API calls and its front end built with Vue. Clone it with git and explore the code in an editor/IDE.</p>
<p>In the server, we will expose <em>GET, POST,</em> and <em>DELETE</em> endpoints for each entity (posts, materials, and announcements). In Nuxt, you can use the HTTP verb in the file name for the endpoint handler. So we can have <strong>posts.get.ts*</strong>,<em> <strong>posts.delete.ts</strong></em>,<em> <strong>materials.post.ts</strong></em>,* and so on, with each file containing the respective handler for the involved API endpoint.</p>
<p>In addition, the server files store and retrieve entities from JSON files. You should have a robust database setup in your product. For this project, we’ll use local JSON files to build a minimum reproducible example with focus on roles and authorization.</p>
<p>In the front end, we have four pages: three for the entities and a settings page. There is also a simple navigation: a bottom bar on smaller screens and a sidebar on wider ones. Each entity page shows a list of its items and a small form to create new ones.</p>
<p>Furthermore, the demo code uses <a target="_blank" href="https://tailwindcss.com/">tailwindcss</a> to style everything quickly. The settings page contains hardcoded user examples and roles for the demo. When testing, toggle the current user and see the roles in action.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732031161755/36a4981c-bc1c-4156-afb8-05fab7d45ee7.gif" alt="Demo of Exploring the Community Dashboard" class="image--center mx-auto" width="1280" height="720" loading="lazy"></p>
<p>This article focuses on the parts of the code that deal with authorization. For the backend and the UI specifics of the community dashboard, we will only overview them. After that, we will deep dive into RBAC touchpoints.</p>
<h2 id="heading-how-to-plan-rbac-with-permitio">How to Plan RBAC with Permit.io</h2>
<p>Overall, planning authorization means mapping out “who” can carry out an action on “what<em>”</em>. In RBAC, we define roles and then assign them to users. The roles and users combo is the “who” part of the authorization.</p>
<p>The “what” side refers to the entities or resources that your application provides or manages. For this article’s example, we’ve chosen our resources as Posts, Materials, and Announcements.</p>
<p>Actions are user activity. The most common actions are “create<em>”, “</em>read<em>”, “</em>update<em>”,</em> and, “delete<em>”</em>. Per resource in your application, you can use these four actions, add more, or omit some. In this article, our resources will each have all four actions.</p>
<p>When planning authorization, define resources alongside the <em>“</em>actions” users can execute on each resource. After that, define roles. For each role, specify what actions a user holding that role can carry out on each resource. The mapping of resources, actions, and roles allows you to define the authorization policies of your application.</p>
<p>Permit.io makes it easy to edit policies. In Permit.io, you have an intuitive dashboard where you can create resources and their actions, create roles, and merge both with policy tables.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732037510499/2c0acabb-e0b4-4002-8b7b-e2fd4dbb4777.gif" alt="Demo of Creating Resources and Actions in Permit.io" class="image--center mx-auto" width="1280" height="720" loading="lazy"></p>
<p>For our Community Dashboard example, we will create three roles with incremental access: member, mentor, and admin. For each role, we’ll allow read access to all resources. However, each role has different management access levels to the resources as follows:</p>
<ul>
<li><p><strong>Members</strong> can view all entities but can only create or delete posts.</p>
</li>
<li><p><strong>Mentors</strong> can view all entities and can create or delete posts and materials.</p>
</li>
<li><p><strong>Admins</strong> can create, view, and delete all entities.</p>
</li>
</ul>
<p>Assigning roles to actions in resources is the same as editing policies.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732037619254/e8900675-18c0-429c-9840-3ba8c1c38a6c.gif" alt="Demo of Creating Roles and Editing Policies in Permit.io" class="image--center mx-auto" width="1280" height="720" loading="lazy"></p>
<h2 id="heading-how-to-setup-permitio-in-nuxt">How to Setup Permit.io in Nuxt</h2>
<p>For our demo project, you need to run <code>npm install</code>, create a <code>.env</code> file, and export your Permit token.</p>
<p>However, if you are building a new project, to setup Permit in Nuxt, first install it with npm.</p>
<pre><code class="lang-bash">npm install permitio
</code></pre>
<p>After that, create a <code>.env</code> file and add your PERMIT_TOKEN. Get the token from the dashboard.</p>
<pre><code class="lang-bash">PERMIT_TOKEN=permit_key_XXXXXXXXXXXXXXXXXXXXX
</code></pre>
<p>To make this token available to the Nuxt <code>runtimeConfig</code>, add it to the <code>nuxt.config.ts</code> file. Also, add <code>permitio</code> (alongside other dependencies) in the <code>transpile</code> array of the build property of the Nuxt config file.</p>
<p>This addition is to account for Nuxt’s specific optimizations. Your <code>nuxt.config.ts</code> file should look like the following:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// https://nuxt.com/docs/api/configuration/nuxt-config</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineNuxtConfig({
  <span class="hljs-comment">// ... other properties</span>

  build: {
    transpile: [<span class="hljs-string">'axios'</span>, <span class="hljs-string">'permitio'</span>, <span class="hljs-string">'pino'</span>]
  },
  runtimeConfig: {
    permitToken: process.env.PERMIT_TOKEN
  }
});
</code></pre>
<p>After these, Permit.io should be available to your server code in Nuxt. You can now use in it middleware code to check for permissions.</p>
<h2 id="heading-how-to-control-api-access-with-nuxt-middleware">How to Control API Access with Nuxt Middleware</h2>
<p>In simple terms, middleware is code that runs before a target handler. In Nuxt, you can add middleware for API endpoints by creating necessary files in a <code>middleware</code> directory contained in the top-level <code>server</code> directory.</p>
<p>Since we are dealing with permissions, we will name our middleware file <code>permissions.ts</code>. Here, you’ll check if a user is permitted to take an action on a given resource.</p>
<p>Permit.io makes this easy with a simple <code>.check</code> method that returns a Boolean indicating if the user is permitted.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> permit.check(user, action, resource);
</code></pre>
<p>For this simple example community dashboard, our middleware code will first try to determine the user and action from the request properties. The example code achieves that in crude ways. They are enough to explain the concept and you should use more robust industry-standard methods for this.</p>
<p>After that, in the example code below, we construct the Permit object using our permit token and the default public PDP (Policy-Decision-Point microservice)’s endpoint. The <a target="_blank" href="https://github.com/permitio/PDP">Permit PDP is open-source.</a> If you wish, you can set up your local/personal PDP by following the steps <a target="_blank" href="https://docs.permit.io/how-to/deploy/deploy-to-production">here</a>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Permit } <span class="hljs-keyword">from</span> <span class="hljs-string">'permitio'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineEventHandler(<span class="hljs-keyword">async</span> (event) =&gt; {
  <span class="hljs-comment">// Only check permissions if the request is a POST or DELETE request</span>
  <span class="hljs-keyword">const</span> { method, path } = event;
  <span class="hljs-keyword">if</span> (method !== <span class="hljs-string">'POST'</span> &amp;&amp; method !== <span class="hljs-string">'DELETE'</span>) <span class="hljs-keyword">return</span>;

  <span class="hljs-comment">// Ensure authorization header is present</span>
  <span class="hljs-keyword">let</span> authorization = event.node.req.headers[<span class="hljs-string">'authorization'</span>];
  <span class="hljs-keyword">if</span> (!authorization) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Unauthorized'</span>);

  <span class="hljs-comment">// Extract the user from the authorization header. This is for example</span>
  <span class="hljs-comment">// purposes only. In a real application, you would use a JWT library or</span>
  <span class="hljs-comment">// better authentication methods in your API.</span>
  <span class="hljs-keyword">const</span> user = authorization.split(<span class="hljs-string">' '</span>)[<span class="hljs-number">1</span>];
  <span class="hljs-keyword">if</span> (!user) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Unauthorized'</span>);

  <span class="hljs-comment">// Extract the resource from the path. This is for example purposes only.</span>
  <span class="hljs-keyword">let</span> resource = path.split(<span class="hljs-string">'/'</span>).reverse()[<span class="hljs-number">0</span>]; <span class="hljs-comment">// get the last part of the path</span>
  resource = resource.slice(<span class="hljs-number">0</span>, <span class="hljs-number">-1</span>); <span class="hljs-comment">// remove the trailing 's' </span>
  <span class="hljs-comment">// Capitalize the first letter</span>
  resource = resource.charAt(<span class="hljs-number">0</span>).toUpperCase() + resource.slice(<span class="hljs-number">1</span>);

  <span class="hljs-comment">// Set the action on the resource from the request method. </span>
  <span class="hljs-comment">// This is for example purposes only. In a real application, you would</span>
  <span class="hljs-comment">// have a more robust way to determine the action. </span>
  <span class="hljs-keyword">const</span> action = method === <span class="hljs-string">'POST'</span> ? <span class="hljs-string">'create'</span> : <span class="hljs-string">'delete'</span>;

  <span class="hljs-comment">// Construct the Permit object. Use the token from runtime config.</span>
  <span class="hljs-keyword">const</span> config = useRuntimeConfig(event);
  <span class="hljs-keyword">const</span> permit = <span class="hljs-keyword">new</span> Permit({
    pdp: <span class="hljs-string">'https://cloudpdp.api.permit.io'</span>,
    token: config.permitToken
  });

  <span class="hljs-comment">// Check if the user is permitted to create the resource. </span>
  <span class="hljs-comment">// If not, throw an error.</span>
  <span class="hljs-keyword">const</span> isPermitted = <span class="hljs-keyword">await</span> permit.check(user, action, resource);
  <span class="hljs-keyword">if</span> (!isPermitted) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Unauthorized'</span>);
});
</code></pre>
<p>As you can see, if the Permit checker fails, an error will be thrown. This will make Nuxt to prevent unauthorized resource management in your system. Such separation of concerns is efficient, especially in Authorization.</p>
<h2 id="heading-how-to-test-rbac-in-the-community-dashboard">How to Test RBAC in the Community Dashboard</h2>
<p>The settings page works together with the <code>stores/permissions.ts</code> file to complete the flow in the front end. We hardcoded the roles and the “user IDs” to ease toggling and testing. You definitely won’t have this in a production application. You can <a target="_blank" href="https://docs.permit.io/integrations/feature-flagging/casl/">integrate CASL for permission checks in a frontend</a>.</p>
<p>In this demo community dashboard, the UI touchpoints only allow edits of entities for which the acting user has the right roles. In other words, you can only add or delete an entity if the current role in the settings page allows that. Let’s see this in action.</p>
<p>In the Permit.io dashboard, create three test users: “example-member”, “example-mentor”, and “example-admin”. Assign the respective roles to each user.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732037709104/0cb7b666-8597-4839-802b-e79e0434572a.gif" alt="Demo of Creating Test Users and assigning Roles in Permit.io" class="image--center mx-auto" width="1280" height="720" loading="lazy"></p>
<p>Start up the Nuxt app by running <code>npm run dev</code>. Visit <code>localhost:3000</code> in your browser and explore the role-based authorization in the demo community dashboard.</p>
<p>You can see that when you set the Current User to admin, you can create and delete announcements, but when set to guest, you can only view entities and not manage them. With this, we’ve fully implemented authorization.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732031066634/54310991-3ce9-4736-8735-c2776e4f6d82.gif" alt="Demo of Testing Roles in the Community Dashboard" class="image--center mx-auto" width="1280" height="720" loading="lazy"></p>
<h2 id="heading-summary">Summary</h2>
<p>You can be more efficient in building your application by focusing on business logic and outsourcing crucial parts like authorization.</p>
<p>In this article, you learned how you can use an authorization solution (Permit.io) to implement Role-Based Access Control in a demo community dashboard with Nuxt. You can also use Permit in any other kind of application (not just community dashboards).</p>
<p>When planning authorization, define resources alongside the <em>“</em>actions” users can execute on each resource. After that, define roles as the permissions the users can have.</p>
<p>Cheers!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Authenticate Your React App Using Firebase ]]>
                </title>
                <description>
                    <![CDATA[ Authentication is a fundamental aspect of modern web and mobile applications. It ensures that users can securely access an app while protecting their data. Firebase, a platform developed by Google, offers a simple and efficient way to add authenticat... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/authenticate-react-app-using-firebase/</link>
                <guid isPermaLink="false">66fca32291d1f0e8dbe5d676</guid>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ijeoma Igboagu ]]>
                </dc:creator>
                <pubDate>Wed, 02 Oct 2024 01:34:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Uw_8vSroCSc/upload/a8799e4ad43b3b8fe966910f9171ccd3.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Authentication is a fundamental aspect of modern web and mobile applications. It ensures that users can securely access an app while protecting their data.</p>
<p>Firebase, a platform developed by Google, offers a simple and efficient way to add authentication to your app.</p>
<p>In this article, I’ll walk you through the steps to authenticate your app using Firebase. Whether you're working on a web or mobile application, Firebase provides a straightforward way to integrate various authentication methods. </p>
<p>By the end of this article, you'll have a fully functional authentication system that allows users to sign up, sign in, and manage their accounts securely.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-table-of-contents">Table of Contents</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-use-firebase-for-authentication">Why Use Firebase for Authentication?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-how-to-set-up-a-firebase-project">Step 1: How to Set Up a Firebase Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-how-to-install-firebase-in-your-project">Step 2: How to Install Firebase in Your Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-how-to-initialize-firebase-in-your-app">Step 3: How to Initialize Firebase in Your App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-how-to-set-up-authentication-methods">Step 4: How to Set Up Authentication Methods</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-authentication-method-using-google">Authentication Method Using Google</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-how-to-upload-to-github">Step 5: How to Upload to GitHub</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we begin, you need to have the following:</p>
<ul>
<li><strong>A Google Account</strong>: Firebase is a Google product, and you need a Google account to access the Firebase Console and use Firebase services. If you don’t have a Google account, <a target="_blank" href="https://support.google.com/mail/answer/56256?hl=en">you can create one here</a>.</li>
</ul>
<h2 id="heading-why-use-firebase-for-authentication"><strong>Why Use Firebase for Authentication?</strong></h2>
<p>Firebase Authentication provides backend services and easy-to-use SDKs to authenticate users to your app. It supports various authentication methods, including:</p>
<ul>
<li><p><strong>Email and password authentication</strong></p>
</li>
<li><p><strong>Google, Facebook, Twitter, and GitHub Authentication</strong></p>
</li>
<li><p><strong>Phone Number Authentication</strong></p>
</li>
<li><p><strong>Anonymous Authentication</strong></p>
</li>
</ul>
<p>These features make Firebase an excellent choice for developers who want to implement secure and reliable authentication without dealing with the complexities of building a custom authentication system.</p>
<p>Let’s get started with the setup!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727802131211/b57dce67-663e-4c03-baa2-21668b543d68.jpeg" alt="b57dce67-663e-4c03-baa2-21668b543d68" class="image--center mx-auto" width="545" height="360" loading="lazy"></p>
<h2 id="heading-step-1-how-to-set-up-a-firebase-project">Step 1: How to Set Up a Firebase Project</h2>
<p>Before using Firebase Authentication, you need to set up a Firebase project.</p>
<p><strong>i. Create a Firebase Project</strong></p>
<ul>
<li>Go to the <a target="_blank" href="https://firebase.google.com/">Firebase Console.</a></li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723410569746/560dfa39-e8d5-4b22-bb84-94946daeac08.png" alt="Firebase website" class="image--center mx-auto" width="1920" height="970" loading="lazy"></p>
<ul>
<li>Click "Add Project" and follow the on-screen instructions to create a new project.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727812540013/eceb505e-ea69-43f0-a845-ad26d86d5c26.gif" alt="Creating a project base" class="image--center mx-auto" width="1920" height="971" loading="lazy"></p>
<p>Once your project is created, you’ll be directed to the Firebase project dashboard.</p>
<p><strong>ii. Add Your App to the Project</strong></p>
<ul>
<li><p>In the Firebase console, click on the "Web" icon (&lt;/&gt;) to add a web app to your Firebase project.</p>
</li>
<li><p>Register your app with a nickname, and click "Register app."</p>
</li>
<li><p>You will be provided with a Firebase SDK snippet (Software Development Kit), which you'll need to add to your app.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723412408046/4bf3956f-1d7d-4dff-8a70-72a757c01d2b.gif" alt="Registering your project to firebase" class="image--center mx-auto" width="1920" height="971" loading="lazy"></p>
</li>
</ul>
<h2 id="heading-step-2-how-to-install-firebase-in-your-project"><strong>Step 2: How to Install Firebase in Your Project</strong></h2>
<p>To start with Firebase Authentication, you'll first need to install Firebase in your project. Here's how you can do it:</p>
<ul>
<li><p>In your code editor, open the terminal for your project.</p>
</li>
<li><p>Run the following command to install Firebase:</p>
</li>
</ul>
<pre><code class="lang-javascript">npm install firebase
</code></pre>
<p>This command will add Firebase to your project, allowing you to use its authentication and other features.</p>
<h2 id="heading-step-3-how-to-initialize-firebase-in-your-app"><strong>Step 3: How to Initialize Firebase in Your App</strong></h2>
<p>After installing Firebase, the next step is to initialize it in your project using the configuration snippet provided in the Firebase console, commonly referred to as the Firebase SDK snippet.</p>
<p><strong>To set this up:</strong></p>
<ol>
<li><p>Create a folder named <strong>config</strong> in your project directory.</p>
</li>
<li><p>Inside the folder, create a file called <strong>firebase.js</strong>.</p>
</li>
<li><p>Paste the SDK snippet you obtained from the Firebase console into the <strong>firebase.js</strong> file.</p>
</li>
</ol>
<p>Here’s what your project setup should look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723459271302/4773d484-b5a2-4cbe-9626-76765cafd9b8.png" alt="Pasting the SDK in your project" class="image--center mx-auto" width="1920" height="1079" loading="lazy"></p>
<p>This code initializes Firebase in your app, enabling you to utilize Firebase authentication and other services, such as Firebase storage, for managing your data.</p>
<p><strong>Note:</strong> Ensure you generate your unique application key for your application to function correctly.</p>
<h2 id="heading-step-4-how-to-set-up-authentication-methods"><strong>Step 4: How to Set Up Authentication Methods</strong></h2>
<p>Firebase supports multiple authentication methods, like using Google, Facebook, GitHub, and so on.</p>
<p>But let’s set up email and password authentication as an example:</p>
<ul>
<li><p>Go to "Authentication" in the left-hand menu in the Firebase console.</p>
</li>
<li><p>Click on the "Sign-in method" tab.</p>
</li>
<li><p>Enable "Email/Password" under the "Sign-in providers" section.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723457322104/6914efdc-87cf-4fce-b84f-bd407b6b4918.gif" alt="Authentication using email and password" class="image--center mx-auto" width="1920" height="971" loading="lazy"></p>
<p>  Now that you've enabled email/password authentication, you can create a sign-up and a sign-in function in your app.</p>
<p>  Let’s create a working example of a sign-up function:</p>
<ul>
<li><p>In your project, create a file named <strong>sign-up.jsx</strong>.</p>
</li>
<li><p>Import the function needed to create a user from Firebase. The function you'll use to create a user is <code>createUserWithEmailAndPassword</code>.</p>
</li>
<li><p>Before creating a user, make sure to import the auth instance that is initialized in <strong>firebase.js</strong> into the <strong>sign-up.jsx</strong> file.</p>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-javascript">    <span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../config/firebase'</span>;
    <span class="hljs-keyword">import</span> { createUserWithEmailAndPassword } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/auth'</span>;

    <span class="hljs-keyword">const</span> SignUp = <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// To create the user with email and password</span>
      <span class="hljs-keyword">const</span> handleUser = <span class="hljs-keyword">async</span> (e) =&gt; {
        e.preventDefault();
        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">await</span> createUserWithEmailAndPassword(auth, email, password);
          alert(<span class="hljs-string">'User created successfully'</span>);
        } <span class="hljs-keyword">catch</span> (err) {
          <span class="hljs-built_in">console</span>.error(err);
        }
      };

      <span class="hljs-comment">// ... (rest of your SignUp component)</span>
    };
</code></pre>
<p>    In the return statement, I will use a form, so we need to import the <code>useState()</code> Hook to manage and track changes in the form's input fields.</p>
<pre><code class="lang-javascript">    &lt;div&gt;
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Register your Account<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span></span>
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleCreateUser}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Name<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"name"</span>
            <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{name}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setName(e.target.value)}
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"email"</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"email"</span>
            <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{email}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setEmail(e.target.value)}
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"password"</span>&gt;</span>Password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"password"</span>
            <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{password}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPassword(e.target.value)}
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"confirm_password"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.label}</span>&gt;</span>
            Confirm Password
          <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"confirm_password"</span>
            <span class="hljs-attr">name</span>=<span class="hljs-string">"confirm_password"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{confirmPassword}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setConfirmPassword(e.target.value)}
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"checkbox"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"terms"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"terms"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mr-2"</span> /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"terms"</span>&gt;</span>
              I agree to the <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span>&gt;</span>Terms and Conditions<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Register<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
    &lt;/div&gt;
</code></pre>
<p>    Putting all code together (<strong>Sign-up.jsx</strong>):</p>
<pre><code class="lang-javascript">
    <span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
    <span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../config/firebase'</span>;
    <span class="hljs-keyword">import</span> { createUserWithEmailAndPassword } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/auth'</span>;

    <span class="hljs-keyword">const</span> SignUp = <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> [name, setName] = useState(<span class="hljs-string">''</span>);
      <span class="hljs-keyword">const</span> [email, setEmail] = useState(<span class="hljs-string">''</span>);
      <span class="hljs-keyword">const</span> [password, setPassword] = useState(<span class="hljs-string">''</span>);
      <span class="hljs-keyword">const</span> [confirmPassword, setConfirmPassword] = useState(<span class="hljs-string">''</span>);

      <span class="hljs-keyword">const</span> handleCreateUser = <span class="hljs-keyword">async</span> (e) =&gt; {
        e.preventDefault();
        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">await</span> createUserWithEmailAndPassword(auth, email, password);
          alert(<span class="hljs-string">'User created successfully'</span>);
        } <span class="hljs-keyword">catch</span> (error) {
          <span class="hljs-built_in">console</span>.log(error);
        }
      };

      <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Register your Account<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleCreateUser}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Name<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                <span class="hljs-attr">type</span>=<span class="hljs-string">'text'</span>
                <span class="hljs-attr">id</span>=<span class="hljs-string">'name'</span>
                <span class="hljs-attr">name</span>=<span class="hljs-string">'name'</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{name}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setName(e.target.value)}
              /&gt;
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'email'</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                <span class="hljs-attr">type</span>=<span class="hljs-string">'email'</span>
                <span class="hljs-attr">id</span>=<span class="hljs-string">'email'</span>
                <span class="hljs-attr">name</span>=<span class="hljs-string">'email'</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{email}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setEmail(e.target.value)}
              /&gt;
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'password'</span>&gt;</span>Password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                <span class="hljs-attr">type</span>=<span class="hljs-string">'password'</span>
                <span class="hljs-attr">id</span>=<span class="hljs-string">'password'</span>
                <span class="hljs-attr">name</span>=<span class="hljs-string">'password'</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{password}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPassword(e.target.value)}
              /&gt;
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'confirm_password'</span>&gt;</span>
                Confirm Password
              <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                <span class="hljs-attr">type</span>=<span class="hljs-string">'password'</span>
                <span class="hljs-attr">id</span>=<span class="hljs-string">'confirm_password'</span>
                <span class="hljs-attr">name</span>=<span class="hljs-string">'confirm_password'</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{confirmPassword}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setConfirmPassword(e.target.value)}
              /&gt;
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                  <span class="hljs-attr">type</span>=<span class="hljs-string">'checkbox'</span>
                  <span class="hljs-attr">id</span>=<span class="hljs-string">'terms'</span>
                  <span class="hljs-attr">name</span>=<span class="hljs-string">'terms'</span>
                  <span class="hljs-attr">className</span>=<span class="hljs-string">'mr-2'</span>
                /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'terms'</span>&gt;</span>
                  I agree to the{' '}
                  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">'#'</span>&gt;</span>
                    Terms and Conditions
                  <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">'submit'</span>&gt;</span>Register<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
      );
    };

    <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> SignUp;
</code></pre>
<p>    Now that you've created the sign-up function, it's time to add a sign-in function so users can log into your app.</p>
<p>    Here's how to create a simple sign-in function:</p>
<ul>
<li><p>In your project, create a new file named <strong>sign-in.jsx</strong>.</p>
</li>
<li><p>Import the initialized <code>auth</code> instance from <strong>firebase.js</strong> into <strong>sign-in.jsx</strong>.</p>
</li>
<li><p>Use the <code>signInWithEmailAndPassword</code> function from Firebase to allow users to sign in.</p>
</li>
</ul>
<p>Here’s the structure for the sign-in function:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../config/firebase'</span>;
<span class="hljs-keyword">import</span> { signInWithEmailAndPassword } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/auth'</span>;

<span class="hljs-keyword">const</span> SignIn = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [email, setEmail] = useState(<span class="hljs-string">''</span>);
  <span class="hljs-keyword">const</span> [password, setPassword] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> handleSignIn = <span class="hljs-keyword">async</span> (e) =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> signInWithEmailAndPassword(auth, email, password);
      alert(<span class="hljs-string">'Signed in successfully'</span>);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(error);
    }
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Sign In<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSignIn}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"email"</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{email}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setEmail(e.target.value)}
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"password"</span>&gt;</span>Password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{password}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPassword(e.target.value)}
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Sign In<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> SignIn;
</code></pre>
<p>The visual display of the result from the code above both sign-up and sign-in</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727813072045/5a91493d-69a0-4a90-98b2-61905b23e460.gif" alt="Visual Result of signup and signin put together" class="image--center mx-auto" width="1920" height="971" loading="lazy"></p>
<h2 id="heading-authentication-method-using-google">Authentication Method Using Google</h2>
<p>As mentioned earlier, you can collect users' emails directly through a form before they use your app and other ways to authenticate the users.</p>
<p><strong>To use Google authentication:</strong></p>
<ul>
<li><p>In the Firebase console, navigate to "Authentication" in the left-hand menu.</p>
</li>
<li><p>Click on the "Sign-in method" tab.</p>
</li>
<li><p>Enable "Google" under the "Sign-in providers" section (for this tutorial, we'll stick with Google, though you can choose other providers).</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727813344687/f1838b83-9af9-42a7-bf98-6a7d617cedc3.gif" alt="Enabling Google Auth" class="image--center mx-auto" width="1920" height="971" loading="lazy"></p>
<p>Now that you've enabled Google authentication, you can create a Google sign-up and sign-in function for your app.</p>
<p>Let's go through how to set up a Google sign-up function:</p>
<ul>
<li><p>First, create a file named <strong>Google.jsx</strong> in your project.</p>
</li>
<li><p>Import <code>auth</code> and <code>GoogleAuthProvider</code> from the <strong>firebase.js</strong> file</p>
</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-comment">// Import the functions you need from the SDKs you need</span>
<span class="hljs-keyword">import</span> { initializeApp } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/app'</span>;
<span class="hljs-keyword">import</span> { getAuth, GoogleAuthProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/auth'</span>;


<span class="hljs-keyword">const</span> firebaseConfig = {
  <span class="hljs-attr">apiKey</span>: ....,
  <span class="hljs-attr">authDomain</span>: ....,
  <span class="hljs-attr">projectId</span>:.... ,
  <span class="hljs-attr">storageBucket</span>: .... ,
  <span class="hljs-attr">messagingSenderId</span>: .... ,
  <span class="hljs-attr">appId</span>: ....,
  <span class="hljs-attr">measurementId</span>: ....,
};
<span class="hljs-comment">// Initialize Firebase</span>
<span class="hljs-keyword">const</span> app = initializeApp(firebaseConfig);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> auth= getAuth(app);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> googleProvider = <span class="hljs-keyword">new</span> GoogleAuthProvider(app);
</code></pre>
<ul>
<li>Initialize the Google provider and export it for use in other parts of your application.</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { auth, googleProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'./firebase'</span>;  <span class="hljs-comment">// Adjust the path to your Firebase config file</span>
<span class="hljs-keyword">import</span> { signInWithPopup } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/auth'</span>;
</code></pre>
<ul>
<li>Import the necessary Firebase function to authenticate a user. Use the <code>signInWithPopup</code> method to authenticate users with Google.</li>
</ul>
<p>While there are other authentication methods available, <code>signInWithPopup</code> is preferable as it keeps users within the app, avoiding the need to open a new browser tab.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> signInWithGoogle = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> signInWithPopup(auth, googleProvider);
    alert(<span class="hljs-string">'Signed in successfully with Google'</span>);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error signing in with Google'</span>, error);
  }
};
</code></pre>
<ul>
<li>In your return statement, create a button to trigger the Google sign-in.</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{signInWithGoogle}</span>&gt;</span>Sign in with Google<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
);
</code></pre>
<p>The visual display of the result from the code above:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727813634035/14c00c73-f289-480a-a396-3abd839b3a75.gif" alt="Using signInWithPop()" class="image--center mx-auto" width="1920" height="971" loading="lazy"></p>
<p>Firebase allows you to sign users out of your application easily. Here's how you can implement a sign-out function:</p>
<ul>
<li><p>First, import the <code>signOut</code> function from Firebase.</p>
</li>
<li><p>Once imported, you can call <code>signOut</code> to log the user out of the app.</p>
</li>
</ul>
<p>Here’s a simple example:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">'./config/firebase'</span>; <span class="hljs-comment">// Adjust the path based on your file structure</span>
<span class="hljs-keyword">import</span> { signOut } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/auth'</span>;

<span class="hljs-keyword">const</span> handleSignOut = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> signOut(auth);
    alert(<span class="hljs-string">'User signed out successfully'</span>);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error signing out:'</span>, error);
  }
};
</code></pre>
<p>With this function, users can easily log out of the app.</p>
<p>In the return statement, you'll typically have a button that triggers the <strong>handleSignOut</strong> function when clicked.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Welcome to the app!<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleSignOut}</span>&gt;</span>Sign Out<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
</code></pre>
<p>The visual display of the result from the code above</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727813736679/21c2162b-b376-43b7-87cb-242d78acef38.gif" alt="signOut() visual display" class="image--center mx-auto" width="1920" height="971" loading="lazy"></p>
<p>Make sure your Firebase project is configured to handle authentication correctly, including Google sign-in, to ensure a smooth sign-in and sign-out experience.</p>
<h2 id="heading-step-5-how-to-upload-to-github"><strong>Step 5: How to Upload to GitHub</strong></h2>
<p>Before pushing your project to GitHub, make sure to store your Firebase API key in an environment variable to keep it secure. This will prevent sensitive information from being exposed in your shared code.</p>
<p><strong>Creating a .env file</strong></p>
<ul>
<li>At the root of your application, create a <strong>.env</strong> file.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727813941388/5e647b8c-c76b-4671-b44b-21ac4dcddc89.png" alt="Storing the API keys in .env file" class="image--center mx-auto" width="1920" height="1079" loading="lazy"></p>
<ul>
<li><p>Add your Firebase API key to the <strong>firebase.js</strong> file.</p>
</li>
<li><p>Use <code>import</code> or <code>process.env</code> to access your Firebase API key. Since the app was created with Vite, I used <code>import</code>.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727814253953/514db02f-44f8-44fc-b03c-4cf77cb5c4ba.png" alt="Firebase file" class="image--center mx-auto" width="1920" height="1079" loading="lazy"></p>
<ul>
<li>Finally, update your <strong>.gitignore</strong> file to include the <strong>.env</strong> file. This step also protects other sensitive files and directories, like <strong>node_modules</strong>.</li>
</ul>
<pre><code class="lang-javascript"># Logs
logs
node_modules
.env
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In conclusion, this guide explains how to integrate Firebase Authentication into your app. Firebase simplifies adding authentication features such as email/password and Google login.</p>
<p>By setting up a Firebase project, installing, and initializing it in your app, you can efficiently build secure user sign-up and sign-in functionalities without the need to start from scratch or set up a server.</p>
<p>If you found this article helpful, share it with others who may also find it interesting.</p>
<p>Stay updated with my projects by following me on <a target="_blank" href="https://https//twitter.com/ijaydimples">Twitter</a>, <a target="_blank" href="https://www.linkedin.com/in/ijeoma-igboagu/">LinkedIn</a> and <a target="_blank" href="https://github.com/ijayhub">GitHub</a>.</p>
<p>The code I used for this tutorial article can be found on my <a target="_blank" href="https://github.com/ijayhub/authentication-example-tutorial">GitHub</a>.</p>
<p>Thank you for reading.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
