# Clerk Blog — Company

# Introducing Clerk CLI
URL: https://clerk.com/blog/introducing-clerk-cli.md
Date: 2026-04-22
Category: Company
Description: The Clerk CLI gives your coding agents auth superpowers

As agentic workflows become central to how software gets built and shipped, developers are spending more time inside their terminals and IDEs. Setting up Auth, Organizations, and Billing should be no exception.

Today, as an accompaniment to the Clerk Dashboard, we're introducing the [Clerk CLI](/docs/cli), a command-line tool that brings all of your Clerk workflows into your terminal.

## Bootstrap new projects faster

```bash {{ prompt: '$' }}
clerk init
```

`clerk init` reduces that workflow into a single command.

Run the init command in an empty directory and Clerk creates the project for you — pick your framework, your package manager, name your app, and you have a running application with sign-in, sign-up, and route protection already in place.

Run the init command inside a directory with an existing project and Clerk adapts to what's already there - detecting your framework and package manager and implementing authentication. If you're migrating from NextAuth, Auth0, or Supabase Auth, the CLI detects it and links you to the relevant migration guide.

Eight frameworks are supported today: Next.js, React Router, Astro, Nuxt, TanStack Start, React, Vue, and JavaScript.

## Manage your instance from the terminal

Once your project is up and running, you'll inevitably want to customize the experience by updating your Clerk settings. Enabling [Organizations](/organizations) for B2B, turning on [Billing](/billing) so you can charge your users seamlessly, or requiring MFA for additional security is all now possible from your terminal or IDE.

```bash {{ prompt: '$' }}
clerk config pull
clerk config patch --json '{"session": {"lifetime": 604800}}'
```

For example, `clerk config` lets you pull your instance configuration as JSON, inspect the schema, and push changes with a diff preview before anything is applied. The `--dry-run` flag shows you exactly what would change before committing.

Note, not all settings are available through CLI commands at the time of our first release but for any not yet supported, `clerk open` gets you to the right Dashboard page.

```bash {{ prompt: '$' }}
clerk open webhooks
clerk open # opens the dashboard for your linked app
```

## Talk to the Clerk API directly

The Dashboard is still a great command center for managing your applications but sometimes you need to view or manage resources directly alongside your agents. Fetch all resources within the [Clerk Backend API](/bapi) by using the `clerk api` command.

```bash {{ prompt: '$' }}
clerk api ls
clerk api /users
clerk api /invitations -X POST -d '{"email_address": "john@example.com"}'
```

The CLI includes a lightweight client for the Clerk Backend API. It resolves your secret key from the linked project, so you can perform authenticated requests. Run `clerk api ls` to discover available endpoints, or run `clerk api` with no arguments for an interactive request builder that lets you browse endpoints and compose requests.

## Built for your agents

We built the CLI painstakingly with agents in mind. Whether its allowing token efficient context or reducing round trips by studying common agent workflows, its never been easier to build with Clerk alongside your agents.

And to supercharge them, a bundled skill ships with every release, pinned to the CLI version so agents always have accurate instructions for usage. The `init` command also offers to install framework-specific [skills](https://github.com/clerk/skills), so agents can reference the latest patterns and conventions.

You can install the bundled skill at any time by running:

```bash {{ prompt: '$' }}
clerk skill install
```

## Get started

Ready to try it? Tell your agent to use the [Clerk CLI](/cli) or install it yourself and spin up a new project:

```bash {{ prompt: '$' }}
npm install -g clerk
clerk init
```

```bash {{ prompt: '$' }}
pnpm install -g clerk
clerk init
```

```bash {{ prompt: '$' }}
yarn global add clerk
clerk init
```

```bash {{ prompt: '$' }}
bun add -g clerk
clerk init
```

```bash {{ prompt: '$' }}
brew install clerk/stable/clerk
clerk init
```

```bash {{ prompt: '$' }}
curl -fsSL https://clerk.com/install | bash
clerk init
```

Everything here is just the beginning, and we're continuing to invest in making the terminal a first-class way to work with Clerk. We'd love to hear what commands or workflows would make your life easier — [open an issue on GitHub](https://github.com/clerk/cli/issues).

[Read the docs](/docs/cli)

![Introducing Clerk CLI](./image.png)

---

# Middleware-based route protection bypass
URL: https://clerk.com/blog/middleware-based-route-protection-bypass.md
Date: 2026-04-15
Category: Company
Description: A CVE has been released for a middleware-based route protection bypass affecting applications using createRouteMatcher in middleware or proxy.

Today, we released a CVE for a middleware-based route protection bypass. If your application uses `createRouteMatcher` in middleware or proxy (for Next, Nuxt, or Astro), please upgrade immediately.

Upgrade instructions and more details are available in the [security advisory](https://github.com/clerk/javascript/security/advisories/GHSA-vqx2-fgx2-5wq9).

Please [contact support](https://clerk.com/contact) with any additional questions or concerns.

---

# Postmortem: Clerk System Outage (March 10, 2026)
URL: https://clerk.com/blog/2026-03-10-service-outage-postmortem.md
Date: 2026-03-13
Category: Company
Description: A detailed postmortem of the outage on March 10, 2026, including timeline, root cause analysis, and remediations.

*Clerk has faced several incidents recently and frustration is rightfully mounting. We have failed at our commitment to customers and we are deeply sorry.*

*In turn, this week we have shifted the majority of our engineering team to reliability-focused projects, and our top priority for the foreseeable future is restoring the reliability of our systems. We apologize for the delay in new product features that will result.*

## Incident timeline

- **15:57 UTC:** Our alerting systems notified us of increased latency and elevated 5xx errors across our APIs. This is when the incident was declared internally.
  - Our monitoring showed increased lock contention in the database, which had started about a minute earlier.
  - The team began investigating the cause, but there was no clear smoking gun.
  - As a mitigation step, we attempted to move all reads to the replicas to reduce pressure on the primary database. This did not help.
  - During this time, the database itself remained operational (CPU usage was within normal limits). However, queries and transactions were taking significantly longer to complete, which saturated our compute resources. With compute capacity exhausted, incoming requests began returning 429 responses.
  - Session outage resiliency was automatically triggered at origin, allowing us to continue serving incoming session token requests. However, because compute resources were already saturated and requests were taking too long to process, most session token requests also returned 429 responses.
- **16:10 UTC:** After failing to identify a clear root cause, we enabled Origin Outage Mode to keep session management operational. This worked as expected and sessions were again operational.
  - During the investigation we also observed something unusual: elevated IO wait on reads, without a corresponding increase in writes. At that point we suspected a potential infra issue and opened a ticket with GCP.
- **16:23 UTC:** The issue resolved on its own.
  - After confirming that the system had stabilized, we disabled Origin Outage Mode shortly afterward.
  - GCP informed us of the root cause (detailed below).

## Root cause analysis

The outage was triggered by a failure within our database provider. Though we generally prefer not to name our vendors, we are doing so today because the operation that failed is unique to their offering.

Specifically, the outage was triggered by a failed live migration of our Google Cloud SQL virtual machine. These are not documented within Cloud SQL, but are similar to [live migrations for Google Compute Engine](https://docs.cloud.google.com/compute/docs/instances/live-migration-process).

Google Cloud SQL performs live migrations on a routine basis, and Clerk does not receive advance notice of when they will occur. When they work as intended, they do not impact database availability or workload.

In this case, the live migration did not work as expected. Our database was subject to significantly increased disk latency, which in turn resulted in increased lock contention, and ultimately led to a complete service outage. When the operation completed, our database returned to normal:

![Disk latency spike during the incident](./image.png)

![Database metrics showing recovery after migration completed](./screenshot.png)

## Our philosophy on infrastructure choices

At Clerk, we take responsibility when failures in our upstream providers cause incidents. We understand that customers have an uptime dependency on Clerk, and so it's unacceptable to simply point fingers if our vendors trigger incidents. For every incident we face, we must have a clear line-of-sight towards mitigating that incident in the future.

As a result, our infrastructure choices tend to be very conservative. We prefer battle-tested solutions, and steer away from new or exotic technologies. This approach allows us to migrate and add redundancies to our workloads more readily.

## Additional background on Google Cloud SQL

We chose Google Cloud SQL in 2021 under this framework. We use the Enterprise Plus offering with high availability, which includes [a 99.99% availability SLA inclusive of maintenance](https://docs.cloud.google.com/sql/docs/postgres/editions-intro#edition-features).

The choice served us well for years, but our streak broke in September 2025, when a different live migration caused [a major incident](https://clerk.com/blog/2025-09-18-database-incident-postmortem). From that incident, we learned that the failure mode of live migrations is catastrophic. It causes downtime until the operation completes, and it's unsafe to trigger a replica failover while the migration is ongoing.

On the other hand, the failure of a live migration felt completely novel. We hadn't seen one before, and Google also reacted with extreme concern.

Specifically, Google escalated our ticket to P0 and administratively "pinned" our database to its datacenter, which prevented additional live migrations since they were assumed to be unsafe.

In the months following, Google and Clerk worked together to ensure that future live migrations would not cause an incident. This process included weekly phone calls and involved over 80 Google staff members. At Google's guidance, Clerk was focused on reducing our average queries per request, introducing Google's managed connection pooler, and increasing the Postgres version. Meanwhile, Google worked to identify the issue in their live migration process.

It wasn't until January that both teams were confident that our Cloud SQL database could safely be unpinned. Google and Clerk were on a call together for the first live migration following the unpinning, which succeeded without incident.

The multi-month process was exhaustive and left our team feeling confident that Google had addressed the root cause. We believed that our database would be back to the reliability it enjoyed previously.

Unfortunately, this incident revealed that live migrations are still unsafe.

## Remediations

The clarity around the root cause of this incident means that the remedies are also quite clear.

### Database pinning

We have requested that Google pins our database again to avoid additional live migrations. Unfortunately, at the time of writing, that request has not yet been accommodated. However, they have assured us another migration is unlikely in the near term, and that they are actively investigating what went wrong with this live migration.

### Eliminate live migrations going forward

Our core issue is that Google's live migrations have proven unreliable. While live migrations have the advantage of being zero-downtime when they work, the disadvantage is that they are opaque, and we are stuck trusting Google's word that they will be reliable in the future.

Instead, we believe it will be safer to depend on traditional replica promotion for ALL Postgres database maintenance going forward. Clerk already has an automated process for safe and fast replica promotion, which we used without incident for a Postgres major version upgrade on January 17, 2026.

Although this process requires more setup than a live migration, we believe the extra work is worthwhile compared to relying on the opaque live migration process.

With all that said, live migrations are currently an expectation of using Google Cloud SQL. Unless Google offers us a way to disable them permanently, we will need to migrate to another database provider or operate Postgres in-house to avoid this behavior. We are actively investigating our options.

## Additional concerns

### 429s instead of 500s

During this outage, our system returned 429s instead of 500s. The 429 is bubbled up directly from an internal service, instead of being transformed to a 500. This will be addressed so applications can accurately assess the state of Clerk's systems based on the error code.

### General reliability concerns

We understand the weight of this situation. Incidents like these are completely unacceptable for any service, much less for authentication infrastructure. We have let our customers down and are reacting in-kind.

As shared above, we have shifted the majority of our engineering team to reliability focused projects for the foreseeable future. With this change, we are confident the coming months will bring improved reliability.

## Closing

We understand the seriousness of this moment. Customers depend on Clerk for critical infrastructure, and we have not lived up to that responsibility. Our focus now is straightforward: improve reliability, address the risks made clear by these incidents, and earn back the trust we have damaged. We are sorry, and we will continue to communicate openly as we make progress.

---

# Postmortem: Clerk System Outage (February 19, 2026)
URL: https://clerk.com/blog/2026-02-19-system-outage-postmortem.md
Date: 2026-02-19
Category: Company
Description: A detailed post-mortem of the system outage that occurred on February 19, 2026, including root cause analysis and planned remediations.


On February 19, 2026 at 8:11 AM PST, Clerk experienced a service outage caused by an inefficient query plan. A routine auto analyze led to a query plan flip, which immediately reduced database performance. Slow database queries led to queuing in our request handlers, and ultimately over 95% of traffic returning 429 without being handled. The small portion of requests that reached our database returned 200, but extremely slowly.

Service was restored approximately 90 minutes later when the `ANALYZE` command was re-run manually, and the query returned to its prior plan. Shortly after, the database performance returned to normal levels.

## Recovery timeline

- **16:15 UTC** — Incident reported and escalated internally. Our team initiated a group call and began working to identify the root cause.
- **16:32 UTC** — We identified a customer with an anomalous traffic spike coincident with the outage, and we believed the increased load may have triggered widespread database issues.
- **16:35 UTC** - We added manual blocks for this customer's traffic in an attempt to relieve pressure on the database.
- **16:37 UTC** - Manual blocks failed to restore the system and we continued investigation.
- **16:50 UTC** - We determined that the suspected customer had an overly aggressive retry mechanism that triggered their anomalous spike, but the initial failures that required retry were within Clerk's infrastructure.
- **16:55 UTC** - We determined that our automatic failover for our Session API failed to trigger because the database was technically still online, just degraded. We began investigating alternative failover approaches.
- **17:08 UTC** - A new failover mechanism was enabled to handle session token generation outside of the core Session API, reducing backend load. This mechanism was built and tested over the last few months, but hadn't been instrumented with triggers in production yet.
- **17:10 UTC** — The failover succeeded and allowed many users to access customer applications again, while sign in and account management APIs remained inaccessible.
- **17:25 UTC** - We determined the root cause to be an automatic `ANALYZE` that resulted in an inefficient query plan flip.
- **17:27 UTC** — We manually re-executed `ANALYZE` on the affected table and the query returned to its prior plan. The database began to recover.
- **17:43 UTC** — We observed systems stabilizing and the database back to healthy levels. The failover mechanism was disabled.
- **18:06 UTC** — Stable performance confirmed across the entire system. Incident moved to monitoring status.

## Root cause analysis

In short, the root cause was Postgres' automatic analyze function triggering a "query plan flip."

For more context, before running a particular query, Postgres relies on a system called a query planner to decide how it will efficiently find the requested data. Postgres' query planner is dynamic, so as more data is inserted, it regularly re-analyzes the database to ensure its query plans are still optimal.

For the impacted query, the planner was leveraging an unreliable statistic. Specifically, the planner was trying to determine the percent of values in a column that were NULL. For this column, the true answer was very high (99.9996%).

However, to avoid re-reading all database rows repeatedly, the planner uses a sampling system to estimate data distribution.

When the automatic analyze function ran, the planner's sample size was too low and it only found NULL values, which led it to erroneously conclude that 100% of the values were NULL. It then planned the query assuming that part of it would return zero non-nulls, when in fact it returned over 17,000. This new plan is considered the "query plan flip."

The impacted query is run frequently enough that the unexpected traversal of 17,000 rows caused nearly all database resources to go toward this query. The reduced performance ultimately led to significant queueing, and shedding the majority of requests with status 429.

## Planned remediations

### Alerting improvements

We are adding dedicated alerting for database query plan flips. This class of issue can cause sudden, severe degradation, and we need to detect it immediately rather than relying on downstream symptoms.

This would have helped us avoid the mistake of believing a customer's traffic pattern caused the issue.

### Hardening session token failover

Our previous Session API failover mechanism was designed to trigger during Postgres outages, while the new failover is designed to trigger during any type of failure at origin.

This extra resilience will ensure users stay signed in for a wider variety of failures.

While this new failover proved to be ready during this incident, our reliability team had not yet been fully trained how to enable it, and we have not yet instrumented automatic triggers to enable it. We expect this work to be completed within the next few weeks so it can be used confidently during any future incidents.

### Query plan stability

Immediately following the incident, we increased the statistics target on the relevant table so the query planner can track a larger, more accurate sample. Later that evening, we refactored the query so the planner could take a deterministic approach in query planning.

Now, we are auditing all of our queries to determine if others are at risk for inefficient planning from unstable statistics.

### Incident communication

We heard clearly from our customers that our communication during this incident was not good enough. Status page updates were too infrequent, the initial severity label did not accurately reflect the impact many customers were experiencing, and too much time passed before the first update was posted. We take this feedback seriously.

We are formalizing our incident communication process. This includes designating a dedicated communications lead during incidents, committing to status page updates at a regular cadence even when the situation is unchanged, and ensuring that severity labels on the status page accurately reflect customer impact. We are also improving our incident tooling so that updates are cross-posted to social channels to reach customers faster.

## Closing

We are deeply sorry for the disruption this incident caused to your team, your business, and your users. We understand that you depend on Clerk to be available, and we failed to meet that expectation yesterday, and too many times in recent months.

This is unacceptable, and we will be bringing increased attention to proactively adding monitors, redundancies, and failovers to our overall system. While we've added many throughout the past year, recent events have made it clear that we are not moving fast enough.

Thank you for your patience and continued partnership.

---

# Postmortem: DNS Provider Outage (February 10, 2026)
URL: https://clerk.com/blog/2026-02-10-dns-outage-postmortem.md
Date: 2026-02-10
Category: Company
Description: A detailed post-mortem of the DNS provider outage that occurred on February 10, 2026, including impact analysis and planned remediations.


On February 10, 2026 at 17:06 UTC, Clerk experienced an outage caused by a failure at our DNS provider. During this period, authoritative DNS resolution for Clerk's APIs was unavailable, which prevented some traffic from reaching our infrastructure despite our underlying services remaining healthy.

Although authoritative DNS resolution was unavailable for 2 hours and 32 minutes, successful request volume to our APIs remained above 95% of the expected volume during this period. This was due to a combination of DNS caching, fallback DNS servers, and the emergency guidance issued to customers.

## Impact

- For applications and end users that were unable to resolve our APIs, the Clerk service faced a complete outage.
- For Clerk customers that were unable to resolve our clerk.com, our Dashboard was inaccessible.
- For mail clients that were unable to resolve DKIM and SPF records, authentication emails were undelivered or went to spam.

## Planned remediations

### Failover preparation

Unfortunately, Clerk did not have a failover prepared for the event of a complete outage at our DNS provider. Although DNS provider redundancy had been planned for 2026, it sits behind other infrastructure improvements that we believed were higher risk to the service. At present, we are working to add redundancy to delivery of authentication emails.

We determined DNS to be low risk based on the inherent resilience of DNS's design (e.g. multiple nameservers and long TTLs), and based on our provider's stated approach to resiliency. They operate 4 nameservers across 2 ASNs, with different architectures on each ASN. They hadn't faced a meaningful incident in Clerk's lifetime until Tuesday.

Although we are glad our assessment was accurate about the DNS system design (which we believe was the primary reason this outage impacted less than 5% of our expected traffic), we were disappointed that our provider's strategy for resilience failed. We were aware that their architecture could readily lead to 2/4 nameservers failing, but it was unexpected that all 4 would fail at once.

Immediately following the incident, we began preparing a truly independent DNS provider for failover, which we expect to be complete within a few weeks.

### Decoupling DNS provider and domain registrar

Our DNS provider also offers domain registrar services, and Clerk has registered several domains with them over the years. During the incident, we discovered that one of our critical domains uses this registrar, and that meant that we could not make emergency adjustments to its nameservers.

Going forward, we will ensure that our DNS provider is completely decoupled from our registrar, so that our ability to perform emergency nameserver changes will be unimpacted by an outage at our DNS provider.

### Improved alerting

We were not alerted to this incident promptly because our alerting tools do not explicitly check the uptime of our authoritative nameservers. We believe our uptime checks during the incident were failing to find an issue because they benefited from the long TTLs of DNS records.

For the future, we are improving our alerts to explicitly check the health of our authoritative nameservers. This will allow us to initiate failover processes more quickly.

## Closing

We are deeply sorry for the disruption this incident caused to you and your users. We understand that you depend on Clerk to be available, and we failed to meet that expectation. While we are fortunate the impact was limited in this case, we know that a partial outage erodes the trust you place in us.

We are committed to earning back that trust through action, not words. The remediations outlined above are already underway, and we will continue to invest in making Clerk's infrastructure more resilient. Thank you for your patience and continued partnership.

---

# Clerk ranked #4 fastest-growing software vendor on Ramp’s December 2025 list
URL: https://clerk.com/blog/clerk-ranked-4-fastest-growing-software-vendor-by-ramp.md
Date: 2025-12-04
Category: Company
Description: Recognition highlights Clerk’s rapid customer adoption

We’re excited to share that **Clerk has been ranked the #4 fastest-growing software vendor by customer count** on Ramp’s **[Top SaaS Vendors on Ramp – December 2025](https://ramp.com/velocity/top-saas-vendors-on-ramp-december-2025)** list. The ranking is based entirely on real customer spend and adoption patterns across Ramp’s platform — making this recognition especially meaningful.

Ramp’s monthly analysis tracks which vendors are seeing the fastest acceleration in **new customer acquisition** and **new spend**. Clerk’s placement near the top of the list reflects the growth we’re seeing every day: more teams choosing to trust Clerk with authentication, user management, organizations, and billing.

This year, we’ve seen rapid adoption from AI-native builders, SaaS companies, and enterprises rethinking their identity foundations. As applications become more sophisticated — and as agent-based systems introduce new requirements around access, auditability, and permissions — Clerk has become a key part of the way modern companies manage users and devices.

As we continue investing in authentication, users, organizations, and billing, our focus remains unchanged: giving developers best-practice solutions with unmatched ease of use.

**A sincere thank you to every team that partnered with Clerk this year.** This recognition is a direct reflection of your trust, and a preview of what’s ahead.

---

# Committing to Agent Identity: Clerk raises $50m Series C from Menlo and Anthropic’s Anthology Fund
URL: https://clerk.com/blog/series-c.md
Date: 2025-10-15
Category: Company
Description: Funding will be used to advance Agent Identity, expand products, and elevate developer experience.

We’re excited to share that Clerk has raised a **$50 million Series C**, led by Menlo and Anthropic’s Anthology Fund, alongside Georgian and previous investors.

Clerk started with a simple vision: offer best-practice authentication with spectacularly easy developer experience. Since then, we’ve become the user management solution of choice, and scaled to manage **over 200 million users for over 15,000 applications**.

A key reason developers choose Clerk is because authentication is always evolving, and Clerk helps them stay on the cutting edge. In our short history, we’ve already helped our customers navigate the introduction of both passkeys and crypto wallets on sign-in pages.

Today, everyone’s attention is on AI. It’s clear that “agents” need a new authentication solution: one that lets them act on behalf of humans with fine-grained permissions, and is easily auditable by both their operating human and application administrators.

The path to secure agent identity won’t come from Clerk alone, but we’re eager to help bring it to life. As the IETF works to extend OAuth with agent identity specifications, we’re laying the groundwork to adopt those standards as soon as they’re ready.

Once formalized, Clerk will bring support to our signature `<SignUp />` and `<SignIn />` components, and make agent identity available to our customers with just a few clicks.

Beyond agent identity, this capital will also be used for:

- **Doubling down on developer experience**: maintaining our emphasis on spectacular DX with deeper integrations, improved documentation, and expanded support for vibe coding and agentic workflows
- **Expanding our product surface**: improving our existing products for multi-tenancy and billing, and adding new products to support other business primitives
- **Investing in reliability**: making sure that the infrastructure powering your users is always there when you need it

Of course, this Series C would not have been possible without our customers, partners, and the incredible team at Clerk. We’re endlessly grateful for your support, and look forward to collaborating on this next chapter of both Clerk and authentication itself.

---

# Postmortem: Database Incident (September 14–18, 2025)
URL: https://clerk.com/blog/2025-09-18-database-incident-postmortem.md
Date: 2025-09-18
Category: Company
Description: A detailed post-mortem of the database incident that occurred between September 14-18, 2025, including root cause analysis, timeline, and remediation steps.


Between September 14th and September 18th, 2025, we experienced a database incident that intermittently impacted customer traffic with significant request failures and latency spikes. The issue originated from an automatic database upgrade by our cloud provider, which exposed an interaction with our connection pooling configuration. This document explains the timeline, root cause, contributing factors, and our mitigation of the issue.

## Timeline

- **Sep 14, 05:30 UTC** – Cloud provider auto-upgrades our database (minor version).
- **Sep 14, shortly after** – Internal monitoring detects an increased but still acceptable database load. No impact on latency is observed.
- **Sep 15, 13:08 UTC** – Internal monitoring alerts about significant request failures and increased latency. The incident begins.
- **Sep 15–18** – Engineering teams continually investigate, optimize queries, and tune database parameters.
- **Sep 15, 14:09 UTC** – Request latency normalizes but our database remains unhealthy.
- **Sep 15, 14:58-15:16 UTC** – Significant request failures and latency spikes.
- **Sept 17, 00:22 UTC** – A deploy including a new query optimization appears to eliminate spikes in database load.
- **Sept 17, 01:00 UTC** – Spikes in database load reappear. However, engineers note that the remaining spikes now appear periodic, occurring roughly every 15 minutes. During these periodic spikes, session management was functional due to the session endpoints higher tolerance for latency, as well as the retry mechanism in our SDKs.
- **Sep 17, 07:15-08:10 UTC** - Engineering teams perform a manual minor version upgrade and increase database capacity. User sessions remain active throughout. For a period of 3-6 minutes, new sign ins, sign ups, and other account management activities face failures. The database health improves notably and latency spikes again appear to be eliminated.
- **Sep 17, 15:07-15:14 UTC** - Significant request failures and latency spikes. Engineers again observe periodic spikes in database load every 15 minutes.
- **Sep 18, 03:19 UTC** – Root cause identified, and a fix applied to database connection pooling configuration. After release, database load immediately dropped below pre-incident levels. Latency spikes resolved and stability restored.

## Root Cause

The incident was triggered by an automatic minor version upgrade of our database (Postgres) performed by our cloud provider on September 14th. While this was a minor upgrade, it contained a significant internal change to how Postgres handled connection locks.

### 1. Postgres change to connection lock handling

- In versions prior to the upgrade, Postgres' lock manager granted new connections with O(n²) time complexity, meaning each connection request slowed as more connections were processed.
- This inefficiency inadvertently acted as a natural rate limiter, spacing out how quickly new client connections were created.
- After the upgrade, this bottleneck was removed and [connection granting was optimized](https://www.postgresql.org/docs/release/13.14/) [(commit)](https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=dc9d424cf), meaning large batches of new connections could be granted nearly instantaneously.

### 2. Our Cloud Run and connection pooling configuration

- Our Cloud Run configuration leverages many containers. During a deploy, these containers spin up sequentially, within about a minute.
- Our connection pooler in each Cloud Run container was configured with a static maximum connection lifetime of 15 minutes, which causes each connection to be closed and replaced every 15 minutes.
- Under the old Postgres behavior, new connections were created slowly enough that these expirations were naturally spread out over time.
- Under the new Postgres behavior, all expired connections were recreated simultaneously, leading to synchronized connection recycling.
- Unknowingly, our database was in a new state: Synchronized connection recycling of a single Cloud Run container could be managed by our database, but synchronized connection recycling across all Cloud Run containers creates unmanageable load.

### 3. Back pressure causes synchronization of connection recycling across all Cloud Run containers

- At deploy time, each Cloud Run container start is sufficiently spread out such that the load from each connection recycling is unnoticeable.
- Over time, any back pressure in Clerk's database causes the recycling events of each container to synchronize through a "[bus bunching](https://en.wikipedia.org/wiki/Bus_bunching)" effect. Once the busses are bunched, there is a "[thundering herd](https://en.wikipedia.org/wiki/Thundering_herd_problem)" effect every 15 minutes as each containers refreshes its connections all at once.
- As this effect continues to compound every 15 minutes, more connections begin cycling simultaneously, eventually exhausting the pool of active connections and leading to customer-facing latency spikes.

## Mitigations During the Incident

- **Query optimization**: We optimized several expensive queries, permanently reducing baseline database load. Additionally, we refined several critical indexes and carried out a comprehensive re-indexing, resulting in reduced database overhead.
- **Traffic shaping**: To protect stability, we temporarily applied more aggressive blocking of abusive traffic. While effective, this may have inadvertently resulted in a small number of legitimate requests being rejected (HTTP 429).
- **Cloud provider engagement**: We explored rolling back the auto-upgrade, but our provider was unwilling to revert the database version.

## Why Diagnosis Was Difficult

Although the root cause may seem straightforward in hindsight, several factors combined to make identification challenging:

### 1. Metrics resolution hid the cycling pattern

- Our database connection metrics were sampled at 60-second intervals, but the connection recycling occurred within seconds every 15 minutes.
- This meant our dashboards showed only *occasional spikes* in active connections, rather than the synchronized, repeating pattern.
- Because the spikes appeared sporadic rather than rhythmic, we initially treated them as a symptom of underlying load rather than the initiating cause.

### 2. Overlapping events confused the timeline

- During the same window, we were mitigating a large volume of fraudulent sign ups targeting some of our customers.
- This attack created its own periodic load surges every \~10 minutes, which overlapped with the database's 15-minute connection cycling.
- The similar periods led us to initially suspect the attack was the primary driver of latency spikes, delaying deeper focus on the database layer.

### 3. Confounding jobs and queries

- Our database also runs several recurring background jobs and scheduled queries.
- Some of these happen on 10, 15, and 30-minute intervals, which coincidentally aligned with the timing of observed spikes.
- These jobs, while legitimate contributors to load, became false positives during our investigation, diverting attention and masking the real synchronization issue.

### 4. Mixed symptoms across APIs

- Latency spikes were seen in our Frontend API but not consistently in our Backend API, which has the same connection pooling logic.
- This discrepancy made the issue harder to triangulate, as it suggested selective load / query inefficiency rather than a systemic connection pooling effect.
- Looking back, we believe that we did not observe this with our Backend API, due to the fact that it simply just manages a lot less connections (and the Database could handle the cycling load, even if they all became synchronized).

### 5. Release freeze effect not obvious

- Ironically, we imposed a release freeze (intended to minimize risk during the incident) unintentionally made the issue worse, since normal deployments naturally stagger the connection creation across machines as the fleet of machines rolled out.
- Because this wasn't an intuitive connection, it obscured one of the clearest signals that would have otherwise pointed to connection synchronization.

## Resolution and current status

- We adjusted our connection pooling strategy to include a jitter to prevent synchronized cycling.
- Since the fix, the database has remained stable and is performing better than before the upgrade, due to the performance improvements that were implemented during the incident.

## Additional remediations and closing

Of course, we are incredibly sorry for the downtime this incident caused, but we also know that apologies are not enough. While we are excited this incident is behind us, it's important to stress that we're not moving on from infrastructure improvements. Incidents like this are unacceptable, and we recognize Clerk has faced too many recently.

Since our [June 26 incident](https://clerk.com/blog/postmortem-jun-26-2025-service-outage), our team has been urgently focused on improving our resilience. We've shipped improvements every single week, and internally, we have more confidence in our reliability, and our reliability roadmap, than ever before.

Beyond the narrow resolution to this issue, we have additional remediation work planned. Most notably:

- **Evaluating other database providers:** Over the past month, we have already been evaluating database solutions that offer more control over upgrades and downgrades, and improved performance compared to our current provider.
- **Additional service isolation:** Our ability to keep sessions operational during much of this incident was a direct result of our remediations following the June 26 incident. Now, we want to extend that concept further. For example, isolating Sign Up and Sign In infrastructure from other parts of the system.
- **Continued database optimizations:** Throughout this incident, we implemented a handful of new-to-Clerk optimization techniques to our most costly queries. We'll continue marching down the list to further improve our database performance.
- **Investigating solutions for tenant isolation:** Throughout this incident, we received many requests to isolate one application from others, and we recognize the potential benefits of that approach. We are encouraged by many modern approaches to sharding and multi-tenant infrastructure, and will continue evaluating solutions. In full disclosure, this is likely on a longer time horizon than the other improvements.

We know every incident carries a cost for you and your users, and we take that responsibility seriously. Our focus is on earning back your trust through reliability, not words, and to ensure that Clerk is an infrastructure partner you can always count on.

---

# Introducing Free Trials in Clerk Billing
URL: https://clerk.com/blog/introducing-free-trials-in-clerk-billing.md
Date: 2025-09-02
Category: Company
Description: Clerk Billing now supports free trials to help developers boost conversion rates and reduce buyer friction.

Let’s face it: a frictionless billing system is great, but giving users a risk‑free peek at premium features can turn hesitation into conversion.

Clerk Billing was designed to take the same great developer experience we built for implementing [authentication](/user-authentication) and apply that philosophy to subscription-based billing. It allows developers to drop in a single [`<PricingTable />`](/docs/components/pricing-table) component to display a beautiful pricing table that is typical across hosted SaaS applications. Selecting a tier prompts the user to enter their payment information and once processed, any entitlements configured with their selected plan are flagged on their account for you to easily allow access to gated content.

With the addition of free trials, your users can now more easily experience premium features of your app to decide if it's the right fit for them before automatically converting to paid customers. In this article, we'll explore why you should use free trials for your app and how to implement it with [Clerk Billing](/blog/add-subscriptions-to-your-saas-with-clerk-billing).

## Free trials lead to higher conversions

Free trials reduce buyer friction and drive higher conversion rates by removing the immediate financial commitment. The data is compelling: [SaaS trial-to-paid conversion rates average around **25%**](https://userpilot.com/blog/saas-average-conversion-rate/). In B2B SaaS, [opt-out trials (requiring credit card upfront) can reach nearly **49%** conversion](https://userpilot.com/blog/saas-average-conversion-rate/), while opt-in trials typically convert around 18%.

Beyond metrics, free trials create **product-qualified leads (PQLs)** who've actively engaged with your product. This makes [conversion rates a reliable gauge of real value](https://www.zigpoll.com/content/what-are-the-most-effective-strategies-to-increase-free-trial-to-paid-subscription-conversion-rates-on-a-saas-website) while reducing churn from sign-up to paid status.

### Best practices with free trials

Allowing users to try your product for free is enough to increase conversion rates; however, there are a few considerations to maximize the impact. One practice is to test different trial lengths and models to optimize your conversion rates. By trying a few different trial periods and measuring the conversion rate, you can fine-tune what period leads to the highest revenue increase.

Another practice is to set realistic trial periods. For most SaaS applications, [7–14 days is a great starting point](https://www.zigpoll.com/content/what-are-the-most-effective-strategies-to-increase-free-trial-to-paid-subscription-conversion-rates-on-a-saas-website). If you have a more complex app, extending the period beyond that range could result in higher conversions due to the steps involved in utilizing the SaaS most effectively.

Trial periods present a perfect opportunity to engage users through product education. This can be through a very polished onboarding experience within the app, or something more traditional like using a newsletter platform for a drip campaign during the trial period to provide ways in which your app can be used.

As with most optimizations, success depends on rigorous measurement and iteration. Track key metrics like trial-to-paid conversion rates and analyze performance across different trial lengths and content strategies.

## How to configure free trials using Clerk Billing

If you've not yet used [Clerk Billing](/billing), configuring subscriptions is as simple as defining your subscription plans, associating the proper features with each plan, and adding the [`<PricingTable />`](/docs/components/pricing-table) component to your application.

The following screenshot demonstrates what a three-tier pricing table would look like for a task management app:

![PricingTable in a Next.js app](./1.png)

And the code for this page (built with Next.js) looks like this:

```tsx
import { PricingTable } from '@clerk/nextjs'

export default function Home() {
  return (
    <div className="min-h-screen items-center justify-items-center gap-16 p-8 pb-20 font-sans sm:p-20">
      <PricingTable />
    </div>
  )
}
```

Free trials can now be enabled for individual plans within the Clerk dashboard:

![Free Trials toggle in the Clerk dashboard](./2.png)

Once configured, the `<PricingTable />` component in your app will automatically update to reflect the plans where a free trial is available. You don't even have to make any changes to the code in your application.

![Updated PricingTable](./3.png)

When a user wants to trial a plan, they'll still be prompted for their payment information so that they can transition to a paying customer once the trial period ends. Your customers will automatically be notified when their trial period is coming to an end.

![Credit card capture modal open](./4.png)

If you handle your own messaging, you can also use the [`subscriptionItem.freeTrialEnding`](/docs/billing/events-webhooks#subscription-items) webhook to be notified when a customer's trial is ending so you can notify them or handle it accordingly. Below is a sample of the payload sent when a trial period is ending.

```json
{
  "data": {
    "created_at": 1716883200000,
    "id": "csub_item_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
    "interval": "month",
    "is_free_trial": true,
    "object": "subscription_item",
    "payer": {
      "created_at": 1716883200000,
      "email": "user@example.com",
      "first_name": "John",
      "id": "cpayer_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
      "image_url": "https://img.clerk.com/xxxxxx",
      "instance_id": "ins_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
      "last_name": "Doe",
      "object": "commerce_payer",
      "updated_at": 1716883200000,
      "user_id": "user_2g7np7Hrk0SN6kj5EDMLDaKNL0S"
    },
    "period_end": 1719561600000,
    "period_start": 1716883200000,
    "plan": {
      "amount": 2999,
      "currency": "USD",
      "free_trial_days": 14,
      "free_trial_enabled": true,
      "id": "cplan_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
      "is_default": false,
      "is_recurring": true,
      "name": "Premium Plan with Trial",
      "slug": "premium-trial"
    },
    "plan_id": "cplan_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
    "status": "active",
    "subscription_id": "csub_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
    "updated_at": 1716883200000
  },
  "event_attributes": {
    "http_request": {
      "client_ip": "192.168.1.100",
      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
    }
  },
  "instance_id": "ins_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
  "object": "event",
  "timestamp": 1716883200,
  "type": "subscriptionItem.freeTrialEnding"
}
```

## Conclusion

[Free trials](/docs/billing/free-trials) represent more than just a conversion strategy. They can be the key deciding factor in whether a user tries your product or moves on to your competitor.

Clerk's free trial implementation stays true to our core philosophy by allowing developers the easiest possible path to providing trials for your customers. Your `<PricingTable />` component automatically adapts and payment collection flows seamlessly handle trial-to-paid transitions. All while respecting your customers by informing them of their trial status.

---

# Postmortem: August 28, 2025 - elevated API latency and errors
URL: https://clerk.com/blog/postmortem-aug-28-2025-elevated-latency-errors.md
Date: 2025-08-28
Category: Company
Description: On August 28, 2025, a credential stuffing attack caused elevated API latency and errors. This postmortem details the impact, root cause, and remediations.

On August 28, two short periods of a distributed credential stuffing attack to our authentication endpoints of a specific tenant, led to elevated latency across the Frontend and Backend APIs and elevated errors in the Backend API.

Services remained partially available while we mitigated load and stabilized the underlying infrastructure. Importantly, our mitigation controls kept session token issuance operating normally throughout the incident.

- **Impact window #1:** 14:53–15:15 UTC (≈22 minutes)
- **Impact window #2:** 17:04–17:16 UTC (≈12 minutes)

### Timeline (UTC)

- **14:53** — Alert triggered for high CPU utilization in the storage layer; elevated API latency observed.
- **15:00** — Incident declared; mitigation initiated.
- **15:15** — Metrics returned to baseline.
- **17:04** — Second spike in CPU and API latency detected.
- **17:16** — Metrics returned to baseline.

### Root Cause Analysis

Investigation points to several compounding contributors in the authentication and data-write path:

1. **Automated traffic targeting authentication flows** generated an unusually high volume of sign-in and sign-up attempts.
2. **Write-intensive activity** from those attempts increased contention on hot authentication-related tables.
3. **A recently introduced CDC consumer** (used for near real-time consumption of auth events) lagged under burst conditions, amplifying contention within a segment of the storage tier.

**Observed error rates during the incident windows:** 2.52% of Backend API requests and 0.14% of Frontend API requests returned errors.

There was **no data loss or corruption**. The impact was limited to increased latency and errors.

### Remediations

- We disabled the lagging change-stream processor pending adjustments.
- We are strengthening adaptive protections at the edge and auth layer (rate limiting, anomaly detection, and upstream filtering).
- We are performing schema and query-path improvements on authentication workloads to reduce contention under spikes.
- We will be further strengthening per-customer isolation to contain issues to the originating application and minimize blast radius.

---

# Introducing Mosaic: Bring Your Brand to Every Authentication Flow
URL: https://clerk.com/blog/introducing-mosaic-bring-your-brand-to-every-authentication-flow.md
Date: 2025-08-20
Category: Company
Description: Introducing Mosaic, our new Figma design system that mirrors every Clerk UI component. Design and prototype auth flows that look and feel like your product before writing a single line of code.

At Clerk, we believe authentication should be as elegant and intuitive as the rest of your product. That's why our platform provides developers with drop-in authentication components that are secure, scalable, and easy to implement—just like Stripe, but for auth.

But we also know that authentication is part of your **brand experience**. From sign-up to user profile, every touchpoint needs to reflect your visual identity. And while our UI components are easily customizable for developers, we are now bringing that to designers as well.

Today, we're excited to introduce **Mosaic**, our new Figma design system. Mosaic gives product teams a powerful, visual way to explore and brand Clerk's UI components before writing a single line of code.

## What Is Mosaic?

**Mosaic** is a Figma design system that mirrors every Clerk UI component—exactly as they appear in production. You can use it to:

- Visually prototype authentication flows that look and feel like your product
- Apply your brand's colors, typography, and imagery in minutes
- Seamlessly transition from Figma to production with pixel-perfect consistency

Whether you're a designer creating your first Clerk experience, or a developer collaborating with a design team, Mosaic provides a shared foundation to ensure your auth UI feels like *your* product—not ours.

## Why We Built Mosaic

Clerk has always been about giving you control without the complexity. We provide fully-featured authentication out of the box, and you can customize every aspect of the UI through [themes](/docs/customization/themes), [CSS variables](/docs/customization/variables), or the API.

But we noticed something important: many design teams wanted to *preview and prototype* these customizations visually—before implementation. Some teams were rebuilding Clerk's components manually in Figma. Others were stuck with screenshots or guesswork.

Mosaic solves that problem. It's a ready-to-use, fully branded design kit that helps you explore, customize, and validate Clerk components right in Figma. You no longer have to imagine what your sign-up form will look like—you can see it, test it, and iterate.

## Getting Started with Mosaic

We've designed Mosaic to be as intuitive as possible. Here's how to get started and make Clerk's components your own in just a few steps.

You can access the [Clerk Mosaic UI Components in the Figma Community](https://www.figma.com/community/file/1521965427913384177).

### 1. Watch the Intro Video

If you prefer learning by watching rather than reading, we've put together a concise, 5-minute [**introductory video**](https://youtu.be/zgqyEZc1SvY). It walks you through everything from setup to customization, so you can get up and running quickly.

### 2. Set Your Brand Colors

Before diving into components, start by defining your **core brand colors**.

Open Figma's **Variables panel** and update the following key color variables:

- `Primary`
- `Background`
- `Input`
- `Danger`
- `Success`
- `Warning`
- `Foreground`
- `MutedForeground`
- `PrimaryForeground`
- `InputForeground`
- `Neutral`
- `Border`
- `Shadow`
- `Ring`
- `Muted`

All Mosaic components are linked to these variables. That means once you set your colors, every component will update automatically—keeping your brand consistent throughout.

For developers implementing these designs, you can achieve the same dynamic theming using [Clerk's CSS variables support](/changelog/2025-07-15-clerk-css-variables-support).

Want to tweak the shape of UI elements? You can also adjust the **border radius variable** to match your design language, whether that's soft and rounded or sharp and clean.

### 3. Run the Clerk Mosaic Color Generator Plugin

Once your core colors are defined, run the [**Clerk Mosaic Color Generator plugin**](https://www.figma.com/community/plugin/1459093748129448022/clerk-ui-components-color-palette-generator) in Figma. This tool automatically generates the full color palette needed for Clerk's UI components—shades, tints, contrast-safe variants, and more.

This step ensures visual cohesion and accessibility across your entire auth experience, without manual color tweaking.

### 4. Set Your Avatar

Personalization matters—especially for components that display real user data.

To make your designs feel realistic and aligned with your brand, update the Figma company logo to your company logo. This will be used across the components to showcase your brand. Also, assign your logo's fill color to the `PrimaryForeground` variable to ensure proper contrast and consistency.

### 5. Set Your Typography

Next, bring in your **brand typography**.

In Figma Variables, update the following:

- `fontFamily`
- `fontFamilyButtons`

Mosaic will automatically apply these fonts across all headings, labels, buttons, and body text—giving your auth UI the same voice as the rest of your app.

### 6. Set Your Appearance Mode

With colors, typography, and imagery set, you're now ready to use Mosaic with your own **visual identity**.

Simply pull any UI component into your canvas and set the **appearance mode** you've configured. You'll instantly see each component styled with your brand's look and feel—ready for design reviews, prototyping, or handoff.

![Mosaic components in action](./Slide_16_9_-_1.png)

### 7. Explore Clerk's Component Library

Mosaic includes every core Clerk component, organized and documented within the Figma file. That includes:

- [`<SignIn />`](/docs/components/authentication/sign-in)
- [`<SignUp />`](/docs/components/authentication/sign-up)
- [`<UserProfile />`](/docs/components/user/user-profile)
- [`<UserButton />`](/docs/components/user/user-button)
- [`<OrganizationProfile />`](/docs/components/organization/organization-profile)
- [`<OrganizationSwitcher />`](/docs/components/organization/organization-switcher)
- [`<CreateOrganization />`](/docs/components/organization/create-organization)
- [`<VerifyEmail />`](/docs/components/authentication/verify-email)
- [`<VerifyCode />`](/docs/components/authentication/verify-code)
- [`<ResetPassword />`](/docs/components/authentication/reset-password)

Each component is interactive and pre-wired to respond to your Figma variables, so you can test different states and variations instantly. There's no need to recreate anything from scratch—just customize and go.

When you're ready to implement these components in code, our comprehensive [component documentation](/docs/components/overview) provides everything you need to get started.

(Foundational internal components used to build these aren't exposed—they're there to keep things structured, but you won't need to touch them.)

### 8. Staying Updated

One important note: Mosaic is a **Figma file**, so when you duplicate it to your own workspace, it becomes disconnected from future updates.

If we release a new component or update the design system, you'll need to **pull the latest version manually** and reapply your brand settings (colors, avatar, typography). We'll announce all updates clearly, so you never miss a new feature or visual improvement.

## Final Thoughts

Authentication is the front door to your product—and Mosaic ensures that door looks exactly the way you want it to.

With a simple, visual setup process, deeply customizable styles, and a full suite of production-matching components, Mosaic gives you the tools to design authentication flows that *belong* to your brand from the first pixel to the last.

We're excited to see what you build with it!

---

# Highlights from the MiduDev/Clerk Hackathon
URL: https://clerk.com/blog/highlights-midudev-clerk-hackathon.md
Date: 2025-08-07
Category: Company
Description: Explore the top 5 projects from the MiduDev/Clerk Hackathon, showcasing creativity, technical skills, and community engagement.

We recently teamed up with [MiduDev](https://www.youtube.com/@midudev), one of the most popular Spanish-speaking tech YouTubers, to host a community hackathon. Over 150 projects were submitted, each showing off how developers are building with Clerk in creative and meaningful ways.

After careful review, five projects stood out and took home the top prizes. This article is a breakdown of how each app was judged, but more importantly an examination of each app that was built based on the choices of each developer which lead to their success.

## How projects were judged

While all submissions were evaluated across a variety of dimensions, the scoring emphasized three main criteria:

- **Creativity** – Was the idea unique, unexpected, or imaginative?
- **Clerk Integration** – Was Clerk used in a meaningful and intentional way?
- **User Experience (UX)** – Was the final product polished, intuitive, and a joy to use?

The top 5 finalists nailed all three. Not only was the idea thought out and executed well, they felt cohesive and genuinely useful. Let’s take a look at what made them stand out.

## 1st Place: Key Leap

> \[Key Leap] represents the deepest and most imaginative **Clerk integration** we saw.
>
> — MiduDev

![Key Leap](./image1.png)

[Key Leap](https://keyleap.vercel.app/) was a clear standout because it reimagined what authentication can be. It used Clerk beyond just the drop-in sign-up and sign-in components but used it as the *core mechanic* of an interactive game.

Players progress by meeting access-level challenges, essentially playing with Clerk’s permission model. The experience is deceptively simple but technically deep, making it a brilliant blend of creativity, Clerk integration, and UX.

## 2nd Place: Finanzz

[Finanzz](https://finanzz.vercel.app/) is a mobile-first finance management web application. It uses AI to simplify various processes such as logging expenses using voice and image recognition, automatic transaction categorization, and even learns over time to help the user make better financial decisions.

The developer used Clerk to quickly create a great login experience for users of Finanzz so they could focus on the core logic of the application.

## 3rd Place: SnippetLab

[SnippetLab](https://snippetlab.app/) impressed with its developer-first mindset. It’s a code snippet manager with both a web UI and a powerful CLI for fetching saved snippets. Various social media features have also been integrated such as liking, sharing, and commenting on snippets.

The developer took advantage of Clerk Elements to design authentication forms that matched the theme of the rest of the app while still leveraging our secure user management platform.

## 4th Place: Atomox

![Atomox](./image2.png)

[Atomox](https://atomox.vercel.app/) is a community-driven platform for creating and sharing visual web UI components. Developers can browse components, interact with them directly in the browser, and view the source code that powers each one. Its visual polish and thoughtful UX are only a few elements that made it stand out.

Behind the scenes, Clerk handles user authentication and profile management, giving the app a solid foundation for growing a creative community.

## 5th Place: SoulsPixel

![SoulsPixel](./image3.png)

[SoulsPixel](https://soulpixel.klasinky.com/soul) is a collaborative pixel board enhanced with gamification. Users place pixels on the board to earn achievements, unlock special features, and earn their place on the leaderboard. On top of that, the history of the board can be replayed to see where each pixel was placed over time.

Clerk powers user identity, allowing users to sign-in using Google or GitHub while requiring unique usernames to power the activity feed within the app.

## Conclusion

This hackathon was definitely a showcase of technical skills, but also a celebration of creativity, craft, and community. Every winning project demonstrates that no matter how technical or creative an application is, Clerk is a great choice to seamlessly integrate user management into any application.

We’re grateful to MiduDev for helping bring this event to life, and even more thankful to everyone who submitted a project!

---

# Choosing the right SaaS architecture: Multi-Tenant vs. Single-Tenant
URL: https://clerk.com/blog/multi-tenant-vs-single-tenant.md
Date: 2025-06-27
Category: Company
Description: What's the difference between multi-tenant and single-tenant SaaS architecture? This guide breaks down the pros, cons, and use cases of each model—so you can choose the right B2B SaaS architecture for your app. Learn how multi-tenancy scales efficiently, when single-tenancy is the better fit, and which modern tools make tenant isolation easier than ever.

Building a B2B SaaS application comes with many early decisions. One of the most critical is choosing your SaaS architecture model - namely, whether to use a multi-tenant or single-tenant approach. This choice impacts everything from cost and scalability to security and maintenance. In this article, we'll clarify:

- What is B2B SaaS
- What is multi-tenancy
- How multi-tenant systems work versus single-tenant setups
- The pros and cons of multi-tenant and single-tenant systems
- How to choose the right model for your product's needs

We'll also explore hybrid strategies and introduce modern tools that can help implement tenant isolation and management. The goal is to help developers, early-stage startups, and founders understand the architectural tradeoffs, so you can make an informed decision for your SaaS.

## What is multi-tenant SaaS architecture?

In multi-tenant architecture, a single instance of your application and its underlying infrastructure is shared by multiple customer organizations (tenants). Each tenant's data and accounts are logically isolated, so that customers only see their own data - but they are all running on the same application and database in a shared environment. For example, think of tenants like residents in an apartment building: everyone shares the same structure and utilities, but each apartment is separate and secured from others. This shared model means tenants *share resources like servers, databases, and application instances*, which greatly improves cost efficiency and scalability. New customers can be onboarded quickly since they use the same base infrastructure. Maintenance is easier on the provider's side and development updates ship faster, because there's just one system to update for all customers.

However, multi-tenancy also requires a strong design for tenant isolation. Since data from different customers lives side-by-side in one system, the application must ensure that one tenant cannot access another tenant's data. Techniques like including a tenant ID column on every table, applying Row-Level Security (RLS) policies in the database, or segregating tenants at the schema or database level are commonly used to enforce this isolation. When implemented properly, each tenant's records remain invisible to any other tenant. The trade-off is that individual tenants have less control over the environment - they can't demand unique customizations that affect only their instance without impacting others. Multi-tenant SaaS providers often solve this by offering configuration options that are universally supported rather than one-off custom code per client.

**Key characteristics of multi-tenancy:**

- **Shared application & DB:** Multiple customers use the same application instance and database with logical partitioning for each tenant. The shared resources also mean a noisy neighbor (one tenant over-consuming resources) could impact others if not managed
- **Cost efficiency:** Infrastructure and maintenance costs are amortized across tenants, making it cheaper per customer
- **Scalability:** The vendor can scale the single application to accommodate more tenants easily - adding more computing resources benefits all customers, without spinning up new instances for each
- **Maintenance:** Updates and bug fixes are applied centrally. When the software is updated, all tenants get the new version at once, simplifying maintenance at scale
- **Customization limits:** Tenants generally run on a uniform codebase. Deep customization for one client is limited, since changes affect everyone. Some SaaS allow per-tenant configuration or feature flags, but not separate forks of the code
- **Security considerations:** Extra care is needed to isolate data and requests. A bug in access control could potentially expose data across tenants, so robust authorization checks are critical.

In summary, multi-tenancy is like a high-rise with many apartments: efficient and cost-effective, but with shared infrastructure. It's widely used in modern B2B SaaS platforms because it supports serving many customers on a common, scalable platform.

## What is single-tenant SaaS architecture?

In single-tenant architecture, each customer (tenant) gets their own dedicated instance of the application and database. There is no sharing of resources between tenants - each runs in an isolated environment. This is akin to each customer having their own house, rather than an apartment in a shared building. Because of this isolation, single-tenancy provides a high degree of control and security: a tenant's data and performance are not affected by any other customer, and there's minimal risk of data accidentally leaking between tenants by design. Customers often prefer this model in scenarios where data must be kept completely separate for compliance or when they require extensive customizations.

With a single-tenant SaaS, onboarding a new customer means provisioning a new deployment of your software (potentially a new server, database, and application instance). Each tenant might even get their own subdomain or dedicated environment. Many on-premises enterprise software offerings or managed cloud offerings use single-tenancy, essentially giving each client their own managed copy of the software.

**Key characteristics of single-tenancy:**

- **Dedicated resources:** Each customer has their own application instance, database and sometimes even separate hardware. No other tenant's data resides in that database.
- **Strong isolation:** Because of the physical and logical separation, there is very strong data isolation and security by default. There's no chance of one tenant accidentally querying another tenant's data because it's simply not in the same system. This isolation also eliminates “noisy neighbor” issues - one tenant's heavy usage can't degrade another's performance because their environments are separate.
- **Customization:** With a dedicated environment, clients can often be granted more customization. The provider (or the client) can tweak configurations, custom plugins, or even custom code for that one tenant without affecting others. This is useful for enterprise clients with unique needs.
- **Higher cost:** The downside is cost and overhead. Serving each customer on their own stack means more infrastructure and maintenance per customer, often leading to higher costs that may only be justifiable for high-paying clients. Higher costs can be offset by leveraging in-house developers to manage maintenance and updates. There are fewer economies of scale; 10 customers could mean maintaining 10 separate sets of resources.
- **Maintenance burden:** Updates and fixes need to be applied to each tenant's instance individually or via an automated process that iterates through them. This can make rolling out new features or patches slower and more labor-intensive, since compatibility has to be ensured for each isolated instance. If one tenant decides to stay on an older version, you may end up maintaining multiple versions of the app.
- **Provisioning complexity:** Setting up a new tenant is more complex and time-consuming, as it involves provisioning new servers or containers, running separate installations, etc. Automation can help, but it's inherently more work than simply adding a record in a multi-tenant database.

In summary, single-tenancy gives each customer their own silo - offering maximum control, security, and potential for customization, at the cost of extra overhead. It's often favored by large enterprises or regulated industries that are willing to pay for isolation.

## Multi-tenant vs. single-tenant: Key differences and tradeoffs

The fundamental difference between these models is shared vs. isolated resources. Multi-tenancy is a shared environment (tenants coexist on the same system) whereas single-tenancy is isolated and each tenant stands alone. This leads to a number of tradeoffs in cost, security, flexibility, and more. As mentioned in previous sections, a common analogy is apartments vs. houses: in a multi-tenant “apartment” setup, tenants share utilities and infrastructure; in a single-tenant “house”, each tenant has everything to themselves.

![Multi-tenant apartments vs. single-tenant houses](./apartments-vs-house.png)

Let's break down the key differences between multi-tenant and single-tenant SaaS architectures across several important factors:

| **Aspect**                | **Single-Tenant (One Customer per Instance)**                                                                                                                                                                        | **Multi-Tenant (Many Customers per Instance)**                                                                                                                                                      |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Infrastructure Cost**   | High - dedicated resources for each customer (cost not shared)                                                                                                                                                       | Low - shared infrastructure amortizes cost across tenants                                                                                                                                           |
| **Security & Isolation**  | Very high - data fully isolated in separate systems. Minimal risk of cross-tenant access.                                                                                                                            | High (with proper design) - data is logically isolated, but sharing infrastructure means stricter controls are needed. No other tenant can see your data, but all rely on common security measures. |
| **Customization**         | Easy - each tenant's environment can be customized (even code-level changes) without affecting others.                                                                                                               | Harder - changes affect all tenants, so customization must be via configuration or not at all. Requires smart layering (feature flags, theming, etc.) for per-tenant tweaks.                        |
| **Scalability**           | Difficult - scaling to more customers means spinning up more instances; operational overhead grows linearly with tenants.                                                                                            | Easier - a shared system scales more efficiently; resources are added to one pool that benefits all tenants. Easier to onboard new customers on the same platform quickly.                          |
| **Updates & Maintenance** | Per customer - updates need to be applied to each instance separately. Version drift is possible if some tenants delay updates.                                                                                      | Global - updates deploy to the shared application, so all tenants get the changes at once. Maintenance is centralized (one environment to manage).                                                  |
| **Typical Use Cases**     | Best for heavily regulated or extremely security-conscious apps, and high-paying enterprise clients who need isolation or custom deployments. Often seen in healthcare, finance, or on-premise enterprise offerings. | Best for most modern SaaS targeting a broad market. Ideal when serving many customers with a uniform service (e.g. SMBs, mid-market) where cost efficiency and ease of scaling are crucial.         |

As the comparison shows, single-tenant architecture shines in security, isolation, and flexibility for customization - at the cost of higher infrastructure expenses and maintenance work per client. Multi-tenant architecture excels in efficient resource utilization, scalability, and easier management, but requires careful attention to security and can limit per-tenant customization.

Let's highlight a few of these tradeoffs:

- **Cost Efficiency:** Multi-tenancy is generally far more cost-efficient for the provider (and often the customer) because resources are shared. You're not running 100 separate servers for 100 customers; you might be running a few servers that handle all 100. This lowers hosting costs significantly. Those savings often translate into more affordable pricing for customers as well. Single-tenancy, by contrast, means each customer needs an allocation of compute, memory, storage, etc. that isn't shared, which is why single-tenant or dedicated-hosting plans tend to come at a premium price.
- **Security & Compliance:** While both models can be made secure, single-tenant offers peace of mind through physical isolation, meaning no other users on your database or server. This can simplify compliance with stringent regulations (GDPR, HIPAA, etc.), since data residency and access can be controlled per-customer environment. Multi-tenant systems must enforce strict *logical* isolation to achieve the same effect. That means robust authentication, authorization, and possibly encryption of tenant data. It's entirely possible to make a multi-tenant app highly secure, but it requires careful engineering. The shared nature of multi-tenancy also means trust in the vendor's security measures is essential. A minor misconfiguration could, in worst cases, expose data across tenants, so the margin for error is smaller. For highly regulated industries or customers that demand complete data isolation, a single-tenant (or a hybrid with dedicated databases) might be non-negotiable.
- **Performance & “Noisy Neighbors”:** In a multi-tenant environment, tenants share resources, so one customer's behavior can potentially affect others. For example, if Tenant A suddenly executes a very expensive operation or experiences a traffic spike, it might consume disproportionate CPU/DB resources and slow down Tenant B's experience, the classic “noisy neighbor” problem. Good multi-tenant design mitigates this (through rate limiting, auto-scaling, query optimization, etc.), but it's an inherent risk. In single-tenancy, there are no noisy neighbors. Each tenant has their own dedicated slice of resources, so one tenant's workload can't throttle another. On the other hand,, in single-tenancy, if a customer's instance is idle, those resources sit unused (wasted capacity), whereas multi-tenant pooling would dynamically make that capacity available to others.
- **Customization & Flexibility:** Single-tenant systems allow tailoring the software environment per client. For example, one client could be on a custom version with specific modules enabled, unique branding, or even custom features developed just for them. This is often important for enterprise contracts. Multi-tenant apps usually avoid one-off custom code per tenant. There's one codebase serving all, so customization is typically limited to configuration options or cosmetic branding. Major deviations requested by one customer either have to be built in a configurable way for all customers or politely declined. This keeps the multi-tenant codebase simpler and the upgrade path easier (since you don't have to maintain divergent code), but it can be a deal-breaker for clients who want a highly tailored solution. In early-stage SaaS, supporting heavy customization for individual clients can also dramatically increase complexity, so many startups choose multi-tenancy and enforce uniformity (sometimes at the cost of losing a few big-customization-seeking deals).
- **Maintenance & Deployment:** A multi-tenant SaaS means one deployment to rule them all, simplifying DevOps. You deploy one set of code, run one database (perhaps scaled out with replicas or partitions, but logically one cluster), and monitor one system. Backup, monitoring, and updates are centralized. With single-tenancy, you have to manage potentially dozens or hundreds of deployments. Automation tools (infrastructure as code, container orchestration, etc.) can help manage many instances, but it's inherently more complex. If you discover a critical bug, you might need to roll out the patch to every customer instance. If you update the base software, you have to ensure all those separate environments are updated (and hope none have unique modifications that make the update tricky). Maintaining thousands of separate databases or instances can become a significant operational burden without heavy automation. Many teams eventually look to consolidate or automate such setups because of this overhead.

In practice, most early-stage SaaS startups lean toward multi-tenancy as the default, because it maximizes scalability and minimizes cost and maintenance efforts. It's the model behind successful SaaS products like Salesforce, Slack, or HubSpot. A single codebase serving many clients, with robust tenant isolation built in. That said, *single-tenancy has its place*. If your product deals with extremely sensitive data or if you're targeting enterprise customers that demand dedicated environments (or need on-premise deployments), you may opt for single-tenant or at least offer it as an option.

## Choosing the right model for your SaaS

How should you decide between single-tenant and multi-tenant (or some mix of the two) for your application? Ultimately, it comes down to your product's audience, requirements, and your capacity to manage complexity. Here are some questions and considerations to guide the decision:

- **What is the relationship between your users and their data?** If each customer's data is completely separate and you have many customers with similar needs (classic B2B SaaS), a multi-tenant design makes a lot of sense. You can serve all of them on one platform while keeping data segmented by tenant. On the other hand, if each customer's use of the software is highly unique or requires heavy customization, single-tenant might be more appropriate so you can tailor each instance. Also consider whether customers might ever need to share data across tenants (usually not in B2B SaaS, but in some cases like a multi-org collaboration, it could happen). Cross-tenant data sharing is easier in a multi-tenant app and quite difficult if each tenant is completely isolated on separate systems.
- **Are you selling to large enterprises with unique needs or strict policies?** Big enterprise clients (especially in industries like finance, healthcare, government) often have strict security and compliance requirements. They might even require an isolated environment by policy. Single-tenancy or a hybrid (such as a dedicated database or instance for that client) could be necessary to close those deals. Also, enterprises often request custom features or integrations. A single-tenant deployment might accommodate that without affecting other customers. Conversely, if your target market is startups and small/medium businesses who generally are fine with a standard offering and care more about cost, a multi-tenant SaaS with a one-size-fits-most approach will be more cost-effective and manageable.
- **Do you need fast iteration and uniform updates for all customers?** If you're in a fast-moving space where you plan to deploy new features frequently to all users, multi-tenancy gives you one pipeline. You deploy updates and everyone is on the latest version. This is great for a product-led growth model where everyone should have the latest and greatest features. Single-tenant models can slow down iteration because you might have different customers on different versions, or you have to carefully roll out changes per instance. If offering a consistent, up-to-date experience across the board is a priority, multi-tenancy is attractive.
- **Are infrastructure costs and operational simplicity important at your stage?** Early on, you likely want to minimize DevOps overhead and cost. Multi-tenancy lets you maximize utilization of your resources. You won't be running 10 separate low-traffic servers for 10 customers. You might run one cluster that all 10 share, perhaps at a fraction of the cost. If you're concerned about cloud costs, multi-tenancy is generally more economical. Single-tenancy can become costly, but sometimes those costs can be passed to the customer (e.g., enterprise clients paying for a dedicated environment). If you do go single-tenant, ensure you have the pricing or funding to support potentially under-utilized resources per customer. Also consider if you have the engineering resources to automate deployment and monitoring for many instances. Without automation, a single-tenant approach can quickly overwhelm a small team.

After answering these questions, you may find that the decision is not strictly black-and-white. Many SaaS companies start with a multi-tenant core for the majority of customers, but adapt with some single-tenant elements as needed. For example, you might build a multi-tenant app for most, but offer a premium “dedicated instance” to enterprise customers who require it, effectively a hybrid approach. The good news is that modern software design allows a spectrum of tenancy models. Even within a multi-tenant system, you can achieve a high degree of isolation (for security or performance) by using techniques like separate databases or schemas per tenant, without giving up the efficiencies of a shared application.

In fact, it's possible to get the best of both worlds in many cases: keep your app multi-tenant for ease of development/deployment, but isolate certain resources per tenant. In the next section, we'll look at some of these hybrid strategies and the tools that make them feasible.

## Best tools for tenant isolation

One reason to feel more confident about multi-tenancy today is that there are powerful tools and frameworks that help with tenant isolation and management. You don't have to reinvent the wheel or build a multi-tenant architecture entirely from scratch. Many common challenges (authentication, data partitioning, access control) are solved by modern platforms. Here are a few examples of tools and approaches that support multi-tenant (and hybrid) SaaS architectures:

- [\*\*Clerk](https://clerk.com) (Authentication & User Management):\*\* Handling authentication and user accounts in a multi-tenant app can be complex, especially if users belong to organizations (tenants) and might switch between them. Clerk is a modern auth platform that provides built-in support for [organization-based multi-tenancy](/docs/organizations/overview). It offers pre-built components for managing organizations, switching between orgs, and role-based access control within a multi-tenant application. In other words, Clerk provides the infrastructure for managing users across multiple tenant organizations so you can focus on your app's functionality. For a frontend or full-stack developer, using a service like Clerk means you can easily implement features like inviting users to an organization, organization-specific roles/permissions, and even SSO, without building that from scratch.
- [\*\*Supabase](https://supabase.com/) (Backend with Row Level Security):\*\* Supabase is an open-source Firebase alternative built on PostgreSQL. One effective feature for multi-tenancy is Postgres [Row-Level Security (RLS)](https://supabase.com/docs/guides/database/postgres/row-level-security), which Supabase exposes in a developer-friendly way. RLS allows you to enforce that each query can only see or modify rows belonging to the requester's tenant, at the database level. For example, you can set a policy that `tenant_id` on a row must match the current user's tenant ID for any SELECT/UPDATE to succeed. This means even if a developer makes a mistake in code (like forgetting to add a tenant filter in a query), the database itself won't return another tenant's data. Using Supabase (or vanilla Postgres with RLS) can give you defense-in-depth for data isolation in a multi-tenant schema. It effectively lets you have a single database for all tenants while ensuring strict partitioning of data access. Supabase also handles many other backend concerns (scalability, auth integration, etc.), which can lower the barrier to adopting a secure multi-tenant design.
- [\*\*Prisma](https://www.prisma.io/) (ORM for Multi-Database or Filtered Access):\*\* Prisma is a popular Node.js ORM that can simplify database interactions. It can be helpful in a multi-tenant context in a couple of ways. First, if you choose a separate database per tenant approach (sometimes called the “silo” model), Prisma can manage multiple connections or clients. For instance, dynamically switching the database connection based on the tenant context in your app. The Prisma client can be instantiated with different connection strings at runtime (some teams maintain a map of tenant ID to DB connection). This makes it feasible to implement a database-per-tenant model without a ton of raw SQL boilerplate. Second, if you use a shared database with tenant IDs, Prisma's query capabilities let you easily add a tenant filter to every query (you might use middleware or default scopes). This reduces the chance of forgetting a `WHERE tenant_id =`... clause. Essentially, modern ORMs like Prisma (and others like Sequelize, TypeORM) are aware of multi-tenancy patterns and have documentation and community examples for implementing them. This saves you from writing a lot of repetitive code to enforce tenant separation in queries. Additionally, Prisma's type safety can prevent accidentally mixing up IDs, and so on.
- [\*\*Neon](https://neon.com/) (Serverless Postgres with Branching):\*\* Neon is a cloud Postgres service that offers intriguing features for multi-tenant and hybrid models. Notably, Neon supports [fast branching of databases](https://neon.com/docs/guides/multitenancy). You can create a new database branch quickly and cheaply. This can enable a database-per-tenant strategy with much less overhead than traditionally expected. In classic single-tenancy, having a separate database for each customer was seen as expensive and hard to scale (imagine running thousands of database servers). But Neon's *serverless Postgres* approach (and similar services) can spin up databases on demand and scale them based on usage. Neon's architecture is designed to handle many databases (tenants) efficiently, so you could isolate each tenant at the database level without incurring massive cost or ops burden. Their documentation even highlights the “database per tenant, data isolation without overhead” use case. This means you can achieve the holy grail of each tenant in a separate database (fully isolated, solving noisy neighbors and a lot of compliance issues) while letting Neon handle the scaling, connection pooling, and management behind the scenes. It's an example of how cloud innovations are blurring the line between multi and single tenancy. You can mix and match to get isolation and efficiency.

The overarching theme is that developers today don't need to build multi-tenancy from scratch. Authentication, authorization, and data security (all critical for multi-tenant SaaS) are available as services or frameworks. This not only accelerates development but also often results in a more secure and robust implementation (since these tools are battle-tested). As an early-stage team, leveraging these can let you focus on your core product while still achieving strong tenant isolation.

The bottom line is: choose the architecture that best fits your current needs, knowing that you have tools and options to adjust as you grow. For a young SaaS startup targeting a broad market, multi-tenancy will likely provide a faster path to a scalable and affordable service. As you gain larger customers or encounter specific needs, you can introduce single-tenant elements or tighter isolation where necessary. Modern platforms like Clerk, Supabase, Prisma, and Neon are making it easier than ever to enforce tenant isolation within a multi-tenant framework, giving you confidence that you're not trading off security or reliability for cost and simplicity.

## Conclusion

Choosing between multi-tenant and single-tenant architecture is a foundational decision for a SaaS product. Multi-tenancy offers scalability, cost-efficiency, and ease of management by serving all customers on a unified system. Single-tenancy provides robust isolation, security, and flexibility by giving each customer their own siloed environment. There is no one-size-fits-all answer. The right choice depends on your target customers, the sensitivity of data, compliance requirements, and resources available to your team.

For many B2B SaaS startups, a well-designed multi-tenant architecture is a great default, allowing you to scale up users quickly and deliver a consistent product experience to all. It usually aligns with a fast-moving development cycle and keeping costs under control. But as we've seen, what is multi-tenancy today is not an all-or-nothing proposition; you can achieve a high degree of tenant isolation even in a shared environment using modern techniques and tools. If you do have use cases that call for more isolation (or you're targeting enterprise clients who demand it), you can adopt a hybrid approach. Perhaps dedicating certain resources per tenant or offering a single-tenant option for those who need it.

Architectural tradeoffs are part of every SaaS journey. The good news is that with cloud platforms and frameworks available in 2025, you have a rich toolkit to implement whichever model (or combination) you choose, without reinventing the wheel. By carefully considering your product's needs and leveraging the right tools (from identity management to databases), you can design a SaaS architecture that balances security, performance, and cost-effectiveness. In the end, delivering value to your customers reliably is what matters. Both multi-tenant and single-tenant architectures can achieve that, as long as you understand the nuances and implement them thoughtfully.

---

# Postmortem: June 26, 2025 service outage
URL: https://clerk.com/blog/postmortem-jun-26-2025-service-outage.md
Date: 2025-06-26
Category: Company
Description: Learn more about our service outage, including the timeline of events and our next steps.

On June 26, 2025, all Clerk services were down from 6:16 UTC to 7:01 UTC, caused by an outage of our compute infrastructure that impacted all Clerk customers.

We are deeply sorry for this outage. Clerk is a critical infrastructure component for our customers, and we take our reliability and uptime seriously. We know that any amount of downtime is unacceptable, and regardless of the cause, our system’s reliability is our responsibility and we fell short of our standards and your expectations.

![Graph of request throughput to our services during the outage. (GMT+3)](./graph.png)

*Graph of request throughput to our services during the outage. (GMT+3)*

## Timeline of events

- 6:16 UTC: Downtime begins and the team starts its investigation
- 6:20 UTC: The team determines that there was neither a deploy coincident with the failure,  nor a spike in traffic
- 6:28 UTC: The team identifies that our Google Cloud Run containers are in a continuous restart loop, and receiving `SIGINT` shutdown signals immediately on start
- 6:32 UTC: The team decides to begin preparing a new release, to test if the `SIGINT`s are related to the particular container
- 6:40 UTC: A fresh container is prepared and deployed, and it also immediately receives a `SIGINT`
- 6:41 UTC: Unable to find a root cause, a P1 incident is filed with Google and we begin speaking with their support
- 6:49 UTC: We receive the first indication that there is an incident at Google: *“I’ve inspected your Cloud Run service and we suspect that you’re being impacted by the internal incident. Please allow me some time to confirm more on this while I reach out to the Specialist”*
- 6:50 UTC: We ask Google which incident, since none has been posted on its status page
- 6:55 UTC: Google responds: *”Yes, this seems to be not yet confirmed hence I’m checking with the Cloud Run Specialist to confirm the same”*
- 7:01 UTC: Service is restored
- 7:32 UTC: We receive the first official confirmation of an incident from Google, via an event from their Service Health API:
  ```text
  {
    @type: "type.googleapis.com/google.cloud.servicehealth.logging.v1.EventLog"
    category: "INCIDENT"
    description: "We are experiencing an  issue with Cloud Run beginning at Wednesday, 2025-06-25 23:16 PDT.
    
    Our engineering team continues to investigate the issue.
    
    We will provide an update by Thursday, 2025-06-26 00:45 PDT with current details.
    
    We apologize to all who are affected by the disruption."
    detailedCategory: "CONFIRMED_INCIDENT"
    detailedState: "CONFIRMED"
    impactedLocations: "['us-central1']"
    impactedProductIds: "['9D7d2iNBQWN24zc1VamE']"
    impactedProducts: "['Cloud Run']"
    nextUpdateTime: "2025-06-26T07:45:00Z"
    relevance: "RELATED"
    startTime: "2025-06-26T06:16:42Z"
    state: "ACTIVE"
    symptom: "The impacted customers in the us-central1 region may observe the service issues while using Cloud Run DirectVPC."
    title: "Cloud Run customers are experiencing an issue in us-central1 region"
    updateTime: "2025-06-26T07:32:13.864860Z"
    workaround: "None at this time."
  }
  ```

## What specifically went wrong?

We architected Clerk to be resilient against failures in Google Cloud availability zones, but not entire regions. [Cloud Run is stated to provide zonal redundancy](https://cloud.google.com/run/docs/zonal-redundancy), which implies that this incident was caused by a full regional failure.

On the other hand, if this was truly a regional failure, we would expect many more services to be impacted than just Clerk. While there was [some discussion on Hacker News](https://news.ycombinator.com/item?id=44384860), the blast radius of this event is surprisingly small for a regional failure.

We are awaiting more information from Google about exactly which system failed, and will update this post when it’s received.

**Update (June 27, 12:40AM UTC):** Google has notified us that their root cause analysis would be published by June 30.

**Update (July 8):** Google provided an RCA Summary, titled *"Cloud Run customers are experiencing an issue in “us-central1” region"* (Event ID: `MLFZZXV`)

- On June 25th 23:16 PDT, Cloud Run Direct VPC workloads in the `us-central1` region experienced downtime for a duration of 51 minutes (June 26 00:07 PDT).
- Workloads in our cloud regions are served out of multiple partitions. Every app deployment is randomly assigned to a primary partition (this cannot be controlled by customers, and is not visible to them). In this incident, only one partition was impacted. As a result approximately 15% of Direct VPC workloads in “us-central1” experienced downtime.
- Workloads in a serving partition are typically served from multiple capacity pools. Our capacity management system uses a load balancer configuration for each capacity pool to signal whether or not the capacity in the pool is online. However, for safety reasons the capacity management system is designed to fail open — that is, if the load balancer configuration is not present, customer workloads can still serve from this capacity.
- This incident was the result of the above tooling implementation alongside the design of our scaling for Direct VPC workloads. Post mitigation and confirmation of the issue’s resolution, our product and SRE teams have deep dived into the architecture which led to this incident and is committed to improving our service to ensure there is no recurrence of this issue.

> **Clerk's interpretation** This RCA indicates that a design flaw with Google Cloud Run's zonal redundancy caused our traffic to fail completely, instead of failover into a different capacity pool. This outcome is what we anticipated, and our remediation of regional failovers should prevent similar failures in the future.

## Remediations

When incidents like this happen, we immediately turn our attention toward preventing their recurrence. Regardless of the root cause, it is our responsibility to build a service that is resilient to failures within our infrastructure providers. To that end, we are starting the following remediations:

### Regional failover for compute (immediate)

This incident could have been mitigated with a failover that shifted our Cloud Run traffic to a different region when `us-central1` began failing. Work is starting on this immediately.

### Multi-cloud redundancy for compute

Although Google Cloud Platform (GCP) was remarkably stable for Clerk’s early years, we have faced three major server disruptions since May 2025 that we attribute to GCP incidents. This shows that we need to explore additional redundancy outside of a single cloud vendor.

We will begin investigating multi-cloud redundancy for our compute infrastructure. This would make Clerk resilient to complete service failures of Cloud Run, as well as failure of Google’s Cloud Load Balancer.

### Additional service isolation and redundancy for session management

Any incident in our Session Management service has an outsized impact on our customers, since it results in complete downtime of their service.

Following an incident in February, we isolated our Session Management service from our User Management service, ensuring that bugs in our User Management codebase would not impact the availability of our Session Management service.

Unfortunately, in the event of a compute outage at origin like we saw in this incident, both services still come down.

To further mitigate session management failures, we are exploring architectural changes that will allow Clerk to continue issuing session tokens for a greater variety of incidents. Though a longer-term project, this will include bringing distributed storage and compute to our Session Management service.

## Looking ahead

This list of remediations we are exploring is not exhaustive, and doesn’t represent a final state for our efforts to make Clerk as resilient as possible. We will continue to invest in stability and scalability to make sure our customers can rely on Clerk as a critical service provider.

This was a serious outage, and we know that businesses rely on Clerk. We are again deeply sorry for the impact on our customers and will continue working to improve our reliability going forward.

For any questions, please [contact support](https://clerk.com/contact).

---

# Add subscriptions to your SaaS with Clerk Billing
URL: https://clerk.com/blog/add-subscriptions-to-your-saas-with-clerk-billing.md
Date: 2025-05-20
Category: Company
Description: Learn how to quickly monetize your SaaS with subscriptions powered by Clerk Billing.

Monetizing your application is often the next logical step after building something users love.

Subscription plans are a common strategy to build sustainable revenue into your SaaS, enabling premium features for individual users or teams with active plans. However, implementing subscriptions from scratch can be time-consuming and error-prone due to the complexity of the underlying infrastructure and logic.

**That's why we built Billing**.

Just as Clerk streamlines authentication and user management, it now does the same for subscriptions. You get a polished UI that allows your users to easily select and manage their preferred plan, as well as helper functions to easily gate access to premium features, all without writing custom billing logic from scratch.

In this article, you'll learn what Clerk Billing is, how it works, and how to implement it in a real-world application.

## What Is Clerk Billing?

[**Clerk Billing**](/billing) is our latest offering that brings subscription management directly into your existing authentication stack. With just a few clicks in the [Dashboard](https://dashboard.clerk.com/), you can define subscription plans and their associated features, which are displayed in your application with the drop-in [`<PricingTable />`](/docs/components/pricing-table) component.

Clerk integrates directly with your Stripe account, letting Stripe handle the actual payment processing while Clerk handles the user interface and entitlement logic. During development, you can even work in a sandbox environment without requiring a Stripe account. This mirrors the way Clerk handles SSO, where development instances use shared credentials until you're ready to go live.

## How Billing Works

You'll start in the [Clerk dashboard](https://dashboard.clerk.com/), where you define your **plans** and add **features** to them.

**Features** are essentially flags that indicate what a user has access to. For example, if your “Pro” plan includes advanced analytics, you'd create an “Analytics” feature and assign it to the plan. Features can be shared across multiple plans, allowing you to build a pricing structure thats increases access to your application as the selected plan increases.

You can configure your plans to be billed monthly or offer discounts for annual subscribers. Once your plans are created, you're ready to display them in your app.

### The `<PricingTable />` component

The core UI component used with Billing is the [`<PricingTable />`](/docs/components/pricing-table). It's a single-line component that renders a fully functional plan selector and payment form inside your app.

![The PricingTable component](./pricingtable.png)

When a user selects a plan, a modal drawer will open to collect their payment details. It's a smooth and familiar experience for users and requires no custom form building on your part.

Users can also subscribe to new plans and manage existing subscriptions directly through the [`<UserProfile />`](/docs/components/user/user-profile) component. The new Billing tab also includes invoice history and linked payment sources. This centralizes authentication, profile management, and billing into one cohesive experience.

### Verifying the User's access

One of the most powerful features in Clerk is the [`has()`](/docs/references/backend/types/auth-object#has) helper. Originally built to power B2B access controls, it checks whether a user has a specific role or permission. With Billing, it now supports checking a user's plan or feature (entitlement) access.

```tsx
const { has } = await auth()

const hasPremiumPlan = has({ plan: 'gold' })
const hasWidgets = has({ feature: 'widgets' })
```

This makes it incredibly easy to gate access to premium content or features with a single, readable function call.

### Managing Subscriptions

Once users are subscribed, you can manage their subscriptions directly from the Clerk dashboard. There's a new [**Subscriptions**](https://dashboard.clerk.com/last-active?path=billing/plans) tab where you can search for users, view their subscription status, and even cancel plans if needed.

Cancelled plans won't immediately remove access, they'll simply stop renewing, giving your users access until the current billing cycle ends. You can also view a user's plan details at a glance, which is especially useful for support and admin workflows.

## Implementing Billing in Quillmate

To demonstrate how easy it is to implement Billing into an application, I'm going to add subscriptions to **Quillmate**, a web-based writing platform built with Next.js, Clerk, and Supabase. The Pro plan for Quillmate offers an AI assistant that users can access while writing new articles. If the user is not a subscriber, they will be prompted to subscribe when they attempt to access the chat assistant.

> \[!NOTE]
> You can access the completed version of the project on [GitHub](https://github.com/bmorrisondev/quillmate/tree/billing).

### Creating the plans

I'll start in the [Clerk dashboard](https://dashboard.clerk.com/) and navigate to **Configure > Subscription Plans**, then click **Add User Plan**.

![The Subscription Plans configurate page with a red arrow pointing to the "Add User Plan" button](./1-creating_subscription_plans.png)

In the next screen I'll name the plan (which will auto-populate the slug), add a description, and set the monthly price.

![The plan configuration page](./2-naming-plans.png)

Before saving I'll scroll down a bit to create the feature that's associated with the plan by click **Add Feature**. I'll name the feature “AI Assistant” and provide a description before saving. Take note of the slug as it will be used in the code to check if the user can access this feature.

![The plan configuration page with a red arrow pointing to the "Add Feature" button](./3-add_feature.png)

### Add the pricing table

Now that my plan and feature are created, I can start updating the code. The first thing I'm going to do is create a new page that shows the available subscriptions. This will be a page that users can access at `/subscribe`. The page itself contains some promo text, but the main thing to note is the [`<PricingTable />`](/docs/components/pricing-table) component which is all I need to render the available plans and features:

```tsx {{ filename: 'src/app/subscribe/page.tsx', ins: [1, 47] }}
import { PricingTable } from '@clerk/nextjs'
import React from 'react'
import Link from 'next/link'

function SubscribePage() {
  return (
    <>
      {/* Navigation Bar */}
      <nav className="fixed top-0 left-0 z-20 flex w-full items-center justify-between border-b border-gray-200 bg-white/70 px-4 py-3 backdrop-blur-md dark:border-gray-800 dark:bg-gray-950/70">
        <Link
          href="/"
          className="bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-lg font-semibold text-transparent transition-opacity hover:opacity-80"
        >
          ← Back to Home
        </Link>
      </nav>
      {/* Main Content */}
      <div className="flex min-h-svh items-center justify-center bg-gradient-to-b from-white to-gray-50 pt-20 dark:from-gray-950 dark:to-gray-900">
        <div className="flex w-full max-w-2xl flex-col items-center gap-8 p-8 text-center">
          <h1 className="bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-4xl font-bold text-transparent">
            Unlock AI Superpowers
          </h1>
          <p className="text-xl text-gray-600 dark:text-gray-300">
            Become a member to access exclusive AI features, priority support, and early access to
            new tools. Join our growing community and take your productivity to the next level!
          </p>
          <ul className="mx-auto mb-4 w-full max-w-md text-left text-base text-gray-700 dark:text-gray-200">
            <li className="mb-2 flex items-center gap-2">
              ✅ <span>Unlimited AI queries and content generation</span>
            </li>
            <li className="mb-2 flex items-center gap-2">
              ✅ <span>Early access to new AI-powered features</span>
            </li>
            <li className="mb-2 flex items-center gap-2">
              ✅ <span>Priority email & chat support</span>
            </li>
            <li className="flex items-center gap-2">
              ✅ <span>Member-only resources and tutorials</span>
            </li>
          </ul>
          <div className="mb-4">
            <span className="text-lg font-semibold text-blue-600 dark:text-blue-400">
              Ready to get started? Choose your plan below:
            </span>
          </div>
          <div className="flex w-full justify-center">
            <PricingTable />
          </div>
        </div>
      </div>
    </>
  )
}

export default SubscribePage
```

This page shows a list of available plans and features for Quillmate users:

![The Quillmate subscribe page](./4-quillmate_plans.png)

Selecting **Get Started** under the plan will simply open a drawer where I can enter my payment information to be processed by Stripe!

![The subscribe page with the checkout drawer open](./5-checkout.png)

### Protecting the AI Chat feature

For each article, Quillmate has a floating action button in the lower right that users can click to access the assistant. This feature should only be available to users who subscribe to the Pro plan, or more specifically, a plan with the “AI Assistant” feature.

The code for the floating action button has a simple check that uses the `has` helper from the Clerk SDK to check if the current user has a plan that includes the `ai_assistant` feature, which is the slug of the feature created earlier in this guide:

```tsx {{ filename: 'app/(protected)/components/ChatFAB.tsx', ins: [14, 17] }}
'use client'
import { useState } from 'react'
import { ChatBubbleOvalLeftEllipsisIcon } from '@heroicons/react/24/outline'
import { useAuth } from '@clerk/nextjs'
import SubscriptionModal from './SubscriptionModal'
import ChatInterface from './ChatInterface'

interface ChatFABProps {
  articleId: string
  articleContent: string
}

export default function ChatFAB({ articleId, articleContent }: ChatFABProps) {
  const { has } = useAuth()
  const [open, setOpen] = useState(false)

  const canUseAi = has?.({ feature: 'ai_assistant' })

  return (
    <>
      {/* FAB */}
      <button
        onClick={() => setOpen(true)}
        className="fixed right-4 bottom-12 z-50 rounded-full bg-blue-600 p-4 text-white shadow-lg hover:bg-blue-700 focus:outline-none"
        style={{ display: open ? 'none' : 'block' }}
        aria-label="Open Chat"
      >
        <ChatBubbleOvalLeftEllipsisIcon className="h-7 w-7" />
      </button>

      {/* Subscription Modal */}
      {open && !canUseAi && <SubscriptionModal onClose={() => setOpen(false)} />}

      {/* Chatbox */}
      {open && canUseAi && (
        <ChatInterface
          onClose={() => setOpen(false)}
          articleId={articleId}
          articleContent={articleContent}
        />
      )}
    </>
  )
}
```

As a best practice, we also want to protect any backend API calls that are used by the chat feature. The `has` function can also be used on server-side code as well:

```ts {{ filename: 'src/app/api/chat/route.ts', ins: [3, 7, 10] }}
import { CoreMessage, generateText } from 'ai'
import { openai } from '@ai-sdk/openai'
import { auth } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

export async function POST(req: Request) {
  const { has } = await auth()

  if (!has({ feature: 'ai_assistant' })) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
  }

  const { messages }: { messages: CoreMessage[] } = await req.json()

  const { response } = await generateText({
    model: openai('gpt-4'),
    system: 'You are a helpful assistant. Format all responses as markdown.',
    messages,
  })

  return NextResponse.json({ messages: response.messages })
}
```

When a user is subscribed, they are then able to access the AI chat:

Users can also manage their plan directly from the [`<UserButton />`](/docs/components/user/user-button) component in the new Billing tab:

![The UserButton component with the Billing tab open](./6-user_button.png)

## Conclusion

[Clerk Billing](/billing) takes all the usual friction out of implementing subscriptions—no need to wire up your own Stripe forms, manage customer data, or create custom logic for checking user plans. It's fully integrated into your authentication layer, shares the same DX principles as the rest of Clerk, and gets you from “idea” to “monetized” in record time.

Whether you're just validating a pricing model or launching a full-featured SaaS product, Clerk Billing is built to get you there faster, with fewer moving parts.

---

# Getting started with Clerk Billing
URL: https://clerk.com/blog/intro-to-clerk-billing.md
Date: 2025-05-14
Category: Company
Description: Learn how to build a complete billing experience with Clerk and Stripe, from subscriptions and usage-based pricing to role-based access—no custom UI or webhooks required.

In this episode of Stripe Developer Office Hours, Clerk CTO and co-founder Braden Sidoti shares how you can build a complete billing experience—without webhooks, custom UIs, or Stripe session management. Instead of abstracting Stripe Billing, Clerk connects directly to your Stripe account: Stripe handles payments, and Clerk takes care of the user interface, entitlement logic, and session-aware billing flows.

You'll learn how to set up subscriptions, usage-based pricing, and org-level billing using Clerk's prebuilt components and APIs. Braden also walks through how Clerk supports role-based access, secure upgrades, and customer self-service, all tightly integrated with your existing auth layer.

The conversation also touches on why Clerk approaches infrastructure this way, how to go from prototype to production without glue code, and how tying billing to identity can simplify everything from user onboarding to plan upgrades. If you're looking to ship payments faster and with less complexity, this is a blueprint worth exploring.

### Try Clerk Billing Today

Clerk Billing works in every [country supported by Stripe](https://stripe.com/global) and syncs directly with your existing Clerk application.

---

# Multi-tenant analytics with Tinybird and Clerk
URL: https://clerk.com/blog/tinybird-and-clerk.md
Date: 2025-05-02
Category: Company
Description: How to use Clerk's Tinybird JWT template to secure Tinybird APIs for fast, easy, and secure user-facing analytics in your multi-tenant application.

When you run analytics for internal use, you often don't think much about role-based access control or multi-tenancy. You just connect a BI tool to your database or data warehouse and start running some queries.

But when you're serving analytics to your end users in your product or application, then you have to think about multi-tenancy, rate limiting, and access control down to the database level.

In traditional databases, this can be pretty challenging. It's why products like Clerk are popular; they abstract the complexities of auth and access control, typically to the transactional database that stores information about users, their IDs, and their metadata.

Adding real-time, user-facing analytics to the mix presents some additional challenges. Using Clerk JWT templates and Tinybird real-time analytics APIs with row-level security policies addresses those complexities.

Here's what you'll learn in this post:

1. How Tinybird APIs are secured using static tokens or JWTs
2. How to use the Clerk JWT template for Tinybird
3. How to modify Tinybird API definitions to support Clerk JWTs
4. How to create a React context provider for auth to Tinybird APIs
5. How to update Clerk Middleware to set the token

> \[!NOTE]
> Everything covered below can be gleaned from the [open source Tinybird Clerk JWT template](https://www.tinybird.co/templates/clerk-jwt).

## Getting familiar with Tinybird

[Tinybird](https://www.tinybird.co/) is an analytics backend for your application. You use Tinybird to build [real-time data APIs](https://www.tinybird.co/docs/publish/api-endpoints) over large amounts of data such as logs, event streams, or other time series data. Tinybird gives you the tooling and infrastructure to store, query, and serve analytics and metrics to end users of your application without having to fuss with the complexities of a real-time analytics database.

And when it comes to authentication and multi-tenancy, Tinybird offers some nice perks: Every API you build with Tinybird can be secured by either static tokens or [JSON Web Tokens (JWTs)](https://www.tinybird.co/docs/forward/get-started/administration/auth-tokens#json-web-tokens-jwts). Within those tokens, you can define security policies that limit access based on user metadata.

For example, here are three Tinybird JWTs with claims that limit access at the user, team, or organization level. You'll notice that these look almost identical, but with the `fixed_params` object modified to support the security policy we want to implement.

**User level**

```json
{
  "workspace_id": "31048b76-52e8-497b-90a4-0c6a5513920d",
  "name": "user_123_jwt",
  "exp": 123123123123,
  "scopes": [
    {
      "type": "PIPE:READ",
      "resource": "my_api_endpoint",
      "fixed_params": {
        "user_id": "user123"
      }
    }
  ]
}
```

**Team level**

```json
{
  "workspace_id": "31048b76-52e8-497b-90a4-0c6a5513920d",
  "name": "team_abc_jwt",
  "exp": 123123123123,
  "scopes": [
    {
      "type": "PIPE:READ",
      "resource": "my_api_endpoint",
      "fixed_params": {
        "team_id": "team_abc"
      }
    }
  ]
}
```

**Organization level**

```json
{
  "workspace_id": "31048b76-52e8-497b-90a4-0c6a5513920d",
  "name": "org_acme_jwt",
  "exp": 123123123123,
  "scopes": [
    {
      "type": "PIPE:READ",
      "resource": "my_api_endpoint",
      "fixed_params": {
        "organization_id": "org_acme"
      }
    }
  ]
}
```

Tinybird APIs are defined using SQL queries (called "pipes" in Tinybird parlance), extended with a [Jinja](https://jinja.palletsprojects.com/en/stable/)-like templating functions to add [advanced logic](https://www.tinybird.co/docs/cli/advanced-templates) or [query parameters](https://www.tinybird.co/docs/forward/work-with-data/query-parameters).

For example, consider the pipe called `my_api_endpoint` referenced in the above JWTs, which might look this:

```sql
SELECT
    toStartOfDay(timestamp) AS day,
    sum(some_number) AS total
FROM app_events
WHERE 1
{% if defined(user_id) %}
    AND user_id = {{String(user_id)}}
{% elif defined(team_id) %}
    AND team_id = {{String(team_id)}}
{% else %}
    AND organization_id = {{String(organization_id)}}
{% end %}
```

This pipe uses Tinybird's templating language to define three query parameters: `user_id`, `team_id`, and `organization_id` for the API endpoint. When supplied in the request, those parameters will trigger the query to filter.

For example, the following request would trigger a query against the database filtering only by events belonging to `user123` and return the response:

```bash
curl -d https://api.tinybird.co/v0/pipes/my_api_endpoint?user_id=user123&token=<static_token>
```

Of course, this is not a particularly secure implementation. We're passing both the `user_id` and the static token in the URL. If we were making a request directly from the browser, this would be insecure; the token would be compromised, and the `user_id` could easily be modified to access another user's data.

JWTs solve this for us. They're not stored server-side, so they're less likely to leak. They're validated by the backend service using a secret key and contain an embedded expiration time. The JWT contains data about the requesting party and is passed to the server in the request headers. Nothing gets exposed, the data returned is properly scoped, everybody wins.

Let's see how to use Clerk's JWT templates to secure a Tinybird API.

## Using Clerk JWTs to secure Tinybird endpoints

Everything I share below references the [Tinybird Clerk JWT template](https://www.tinybird.co/templates/clerk-jwt), which includes an open-source code example, video tutorial, and live demo. Feel free to go straight there and follow the guide, or follow along here.

### Setting up the Clerk JWT template

Go to the Clerk dashboard, and select **Configure** > **JWT Template**. Select the **Tinybird JWT template**.

![The Create Jwt Template modal in the Clerk dashboard](./image1.png)

Tinybird JWTs must be signed using the admin token for the workspace where the Tinybird resources are hosted. In Clerk, make sure to enable **Custom signing key** with **HS256** signing algorithm, and paste in the Workspace admin token:

![A screenshot of the Tinybird Clerk JWT template in the Clerk dashboard](./image2.png)

You can also set an optional token lifetime. Tinybird's API endpoints will return a 403 if requested with an expired token.

Modify the claims as needed for your application:

```json
{
  "name": "frontend_jwt",
  "scopes": [
    {
      "type": "PIPES:READ",
      "resource": "<YOUR-TINYBIRD-PIPE-NAME>",
      "fixed_params": {
        "organization_id": "{{org.slug}}",
        "user_id": "{{user.id}}"
      }
    }
  ],
  "workspace_id": "<YOUR-TINYBIRD-WORKSPACE-ID>"
}
```

The example above uses the Clerk shortcodes `org.slug` and `user.id` to reference the unique identifiers for the organization and user stored in Clerk. These get passed to the Tinybird resources secured by the JWT.

### Bonus: Rate limiting

Rate limiting can be an important part of multi-tenant architectures to prevent one or a few users from monopolizing resources. Tinybird JWTs support rate limiting through a `limits` claim:

```json {{ prettier: false }}
{
    "limits": {
        "rps": 10
    },
    …
}
```

When you specify a `limits.rps` field in the payload of the JWT, Tinybird uses the name specified in the payload of the JWT to track the number of requests being made. If the number of requests per second goes beyond the limit, Tinybird starts rejecting new requests and returns an "HTTP 429 Too Many Requests" error.

### Configuring your Tinybird APIs

The only thing you need to do is double-check (or update) your Tinybird APIs and ensure logic exists to filter on the passed parameters. This logic is customizable so you can handle requests in a way that makes sense for your use case. In a multi-tenant architecture, it often makes sense to filter by default at the organization level, making an `organization_id` required in the endpoint, then optionally adding user-level filtering for resources where a user might need to see their specific data:

```sql
SELECT * FROM table
WHERE organization_id = {{String(organization_id, required=True)}}
{% if defined(user_id) %}
    AND user_id = {{UUID(user_id)}}
{% end %}
```

## Configuring a frontend-only app for multi-tenant analytics

Once you've created the JWT template in Clerk and implemented the filtering logic in your Tinybird APIs, it's relatively simple to implement the logic in your application. The example below is for a TypeScript/Next.js app, but can easily be extended to any other language or framework.

### Basic project structure

The core components of this implementation in Next.js are:

- `TinybirdProvider.tsx` - A React context provider that manages the Tinybird JWT token
- `page.tsx` - The main page component that demonstrates token usage

### TinybirdProvider component

The `TinybirdProvider` component is a React context provider that manages the authentication token needed to access Tinybird's API endpoints. This provider automatically fetches and stores the user's token (provided by Clerk on sign-in) in its state and makes it available to any component in the app through the `useTinybirdToken` hook (by using a hook, we can avoid prop drilling across components).

```tsx {{ filename: 'src/app/providers/TinybirdProvider.tsx' }}
'use client'

import { useSession } from '@clerk/nextjs'
import { createContext, useContext, useState, ReactNode, useEffect } from 'react'

interface TinybirdContextType {
  token: string
  setToken: (token: string) => void
}

const TinybirdContext = createContext<TinybirdContextType | undefined>(undefined)

export function TinybirdProvider({ children }: { children: ReactNode }) {
  const [token, setToken] = useState('')
  const { session } = useSession()

  useEffect(() => {
    if (!session) return

    async function populateToken() {
      const token = await session?.getToken({ template: 'tinybird' })
      if (!token) return
      setToken(token)
    }

    populateToken()
  }, [session])

  return <TinybirdContext.Provider value={{ token, setToken }}>{children}</TinybirdContext.Provider>
}

export function useTinybirdToken() {
  const context = useContext(TinybirdContext)
  if (context === undefined) {
    throw new Error('useTinybirdToken must be used within a TinybirdProvider')
  }
  return context
}
```

The provider automatically fetches the token when a user signs in and updates it when the session changes.

### Using the token in your application

To use the Tinybird token in your application, simply wrap your app with the `TinybirdProvider` and use the `useTinybirdToken` hook where needed:

```tsx {{ filename: 'src/app/layout.tsx' }}
import { TinybirdProvider } from './providers/TinybirdProvider'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ClerkProvider>
          <TinybirdProvider>{children}</TinybirdProvider>
        </ClerkProvider>
      </body>
    </html>
  )
}
```

In any component that needs to make Tinybird API calls, use the `useTinybirdToken` hook to get the token and make requests:

```tsx {{ filename: 'src/app/components/MyComponent.tsx', prettier: false }}
import { useTinybirdToken } from './providers/TinybirdProvider'

function MyComponent() {
  const { token } = useTinybirdToken()

  const fetchData = async () => {
    const response = await fetch('https://api.tinybird.co/v0/pipes/your_pipe.json', {
      headers: {
        Authorization: `Bearer ${token}`
      }
    })
    // Handle response
  }

  return (
    // Your component JSX
  )
}
```

This implementation provides a clean, efficient way to handle multi-tenant analytics with Clerk and Tinybird, while keeping the authentication logic on the client side. The provider automatically manages the token, and components can easily access it through the `useTinybirdToken` hook.

## Demo

Want to see how this works in practice? Check out this [live Clerk JWT demo for a Tinybird API.](https://clerk-tinybird.vercel.app/) If you want to check out the code, you can find it in [this repository](https://github.com/tinybirdco/clerk-tinybird).

## Get started

Building real-time analytics features into your application is pretty simple with Clerk and Tinybird. Just create a JWT template, add filtering parameters to your Tinybird APIs, and use Clerk middleware to set the token header in the request.

If you'd like to build a user-facing analytics MVP, [sign up for Tinybird](https://cloud.tinybird.co/signup) (it's free, no time limit) and follow the quick start. You'll be able to build your first API in a few minutes and have it secured in your application just as quickly using Clerk.

---

# How Clerk integrates with a Next.js application using Supabase
URL: https://clerk.com/blog/how-clerk-integrates-nextjs-supabase.md
Date: 2025-03-31
Category: Company
Description: Learn how Supabase works with Next.js to increase security and reduce development hours, and how Clerk integrates with this stack.

Supabase is one of the most popular backend platforms on the web, but it breaks from traditional architecture patterns when accessing data.

Full-stack applications typically have distinct frontend and backend codebases. When a user needs to access or modify data, the frontend proxy requests through the backend to the database. This prevents third parties from obtaining the connection string used for database operations.  In comparison, Supabase allows you to build applications *without* a backend, yet still keep your data safe.

In this article, you’ll learn how Supabase functions with Next.js applications, and how Clerk integrates with this configuration.

This article assumes you have [familiarity with Next.js and Supabase](https://supabase.com/docs/guides/getting-started/quickstarts/nextjs), and have ideally built an application using them both.

## How Supabase works with Next.js applications

Supabase provides a way to access data from the client directly without going through your own backend.

As a full-stack JavaScript framework, Next.js supports accessing Supabase through server-side code, but most developers take advantage of the Supabase API to gain direct access to the underlying data. In this setup, the client will request data from the database through the API and include an authorization token so that the service can associate the request with a specific user.

To protect the data, Supabase uses a feature of Postgres (the underlying database engine) called Row Level Security (RLS). RLS is a way to secure database tables on a per-record by using policies to evaluate each request and determine if the user making a data access/modification request is authorized to do so on the specific rows.

> \[!NOTE]
> To learn a more about how the Supabase API works with RLS, check out [our article comparing Supabase Auth and Clerk](/blog/how-clerk-integrates-with-supabase-auth) where we dive deeper into both implementations.

## How Clerk integrates with Next.js and Supabase

When a user signs in with Clerk, they receive short-lived tokens that are used by backend services to validate requests. In a traditional configuration, this would be the backend code you write for your application. As stated in the previous section, applications using Supabase often bypass any custom backend code and send requests directly to Supabase, so there are a few considerations to be taken so that Clerk will work properly with Supabase.

### Verifying JWTs with the JWKS endpoint

In the traditional configuration, Clerk’s SDKs will automatically verify your request using the public key associated with Clerk application.

Supabase does not support this since you do not control the backend code that powers the Supabase APIs. However, Supabase does support integrating with Clerk as a third party authentication provider, where JWTs sent to the Supabase backend will automatically be verified with your Clerk application using the JWKS endpoint for that application.

A JSON Web Key Set (JWKS) is a JSON object that contains a set of keys used to verify the signatures of JWTs. This object is often publicly available through a standard URL so they can be used by external systems and integrations to perform this verification.

The following diagram shows what this flow looks like:

![JWT Verification Flow](./diagram.png)

1. The user signs in using Clerk
2. Clerk issues JWT to the user
3. User makes request to Supabase, including JWT
4. Supabase verifies with the Clerk application JWKS endpoint
5. Clerk verifies token
6. Supabase response to the user with the requested data

### Setting the correct role

Supabase supports a number of different roles which each provide different levels of access for managing database and storage operations. The role which the request assumes is set in the JWT itself as the `role` claim.

To adhere to Supabase standards, the `role` in the JWT should be set to `authenticated` so that the request has the correct level of security. The following snippet shows what the claims of the JWT are when properly configured:

```json
{
  "app_metadata": {},
  "aud": "authenticated",
  "azp": "http://localhost:3000",
  "email": "brian@clerk.dev",
  "exp": 1742938129,
  "iat": 1742938069,
  "iss": "https://modest-hog-24.clerk.accounts.dev",
  "jti": "01a722552fe233fc649b",
  "nbf": 1742938064,
  "role": "authenticated", // Note the 'role' claim
  "sub": "user_2s2XJgQ2iQDUAsTBpem9QTu8Zf7"
}
```

### Using the Clerk JWT for requests to Supabase

Clerk stores its token in cookies that automatically get sent with every request in the same domain. Without some infrastructure considerations, your Supabase application is likely not going to be accessible on the same domain as your application.

Fortunately, you can obtain the current session token using the `getToken` function of the SDK:

```tsx
import { useSession } from '@clerk/nextjs'

const { session } = useSession()
session.getToken()
```

When creating a Supabase client in your front end, you can provide the token created using the JWT template into the `accessToken` option of the Supabase client:

```tsx
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_KEY!,
  {
    // Use the session.getToken() method from Clerk
    accessToken: () => session?.getToken(),
  },
)
```

This will ensure that each request sent to Supabase will contain the custom token created with the JWT secret so that Supabase can validate it, extract the claims, and use it to access the requested data.

> \[!NOTE]
> Interested in more content featuring Supabase? [Let us know!](https://feedback.clerk.com/roadmap?id=f95553cd-204d-43b8-b2b5-1f84ecf1bd59)

### Using the Clerk user ID in RLS policies

Once the token has been verified, the claims within that token are accessible to Postgres using the built-in `auth.jwt()` function (used to access the JWT claims) and accessing the `sub` claim.

For example, say a `tasks` table has the following RLS policy defined:

```sql
create policy "select_by_user_id" on tasks
for select using (auth.jwt()->>'sub' = user_id);
```

Assuming the Clerk user ID is `user_2s2XJgQ2iQDUAsTBpem9QTu8Zf7`, running a `select` statement will effectively apply a filter to restrict what data is returned as shown below:

```sql
select * from tasks;
-- Turns into this:
select * from tasks where user_id = 'user_2s2XJgQ2iQDUAsTBpem9QTu8Zf7';
```

This can provide an additional layer of security for applications with a dedicated backend, or save the developer from having to build one in the first place, if implemented properly.

## Examining an implementation of Next.js using Clerk and Supabase

To see this implemented in a real-world application, we’ll explore the code for Quillmate. Quillmate is an open-source, web-based writing tool built with Next.js, Supabase, and Clerk.

> \[!NOTE]
> The source code for Quillmate is available [on GitHub](https://github.com/bmorrisondev/quillmate).

### Configuring the integration

To integrate Supabase with Clerk, you’d start in the Supabase Dashboard and navigate to **Authentication** > **Sign In/Up** > **Third Party Auth** and add Clerk as the provider. The modal that appears will prompt you for a **Clerk Domain**, but also contains a link to open the [Supabase Integration Setup page in Clerk](https://dashboard.clerk.com/setup/supabase) to select your application and enable the integration:

![Supabase Integration Setup](./supabase-dash.png)

Once completed, you’ll be provided with the Clerk Domain for your application and instance. You simply need to copy and paste this into the Supabase Dashboard and click **Create connection**.

![Supabase Integration Setup](./connect-supabase.png)

Supabase now knows that when a JWT is received that was issued by Clerk, it should use the JWKS endpoint available on the provided Clerk Domain for verification. On the Clerk side, the session claims have been updated to include `"role": "authenticated"` with all new JWTs created:

![Managed claim](./managed-claim.png)

### Accessing Supabase with a Clerk JWT

The following code demonstrates how the JWT template is accessed when creating the Supabase client. This code uses the React Context API to simplify creating and accessing the client with the Clerk `getToken` function, but you may create the client however you need as long as you can pass in the `getToken` function:

```tsx
'use client'

import { createClient, SupabaseClient } from '@supabase/supabase-js'
import { useSession } from '@clerk/nextjs'
import { createContext, useContext, useEffect, useState } from 'react'

type SupabaseContext = {
  supabase: SupabaseClient | null
  isLoaded: boolean
}

const Context = createContext<SupabaseContext>({
  supabase: null,
  isLoaded: false,
})

type Props = {
  children: React.ReactNode
}

export default function SupabaseProvider({ children }: Props) {
  const { session } = useSession()
  const [supabase, setSupabase] = useState<SupabaseClient | null>(null)
  const [isLoaded, setIsLoaded] = useState(false)

  useEffect(() => {
    if (!session) return

    const client = createClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_KEY!,
      {
        accessToken: () => session?.getToken(),
      },
    )

    setSupabase(client)
    setIsLoaded(true)
  }, [session])

  return (
    <Context.Provider value={{ supabase, isLoaded }}>
      {!isLoaded ? <div>Loading...</div> : children}
    </Context.Provider>
  )
}

export const useSupabase = () => {
  const context = useContext(Context)
  if (context === undefined) {
    throw new Error('useSupabase must be used within a SupabaseProvider')
  }
  return {
    supabase: context.supabase,
    isLoaded: context.isLoaded,
  }
}
```

For the sake of this explanation, the only table of interest is the `articles` table. The `articles` table itself has a relatively simple schema:

```sql
create table if not exists public.articles (
  id bigint primary key generated always as identity,
  title text not null,
  content text not null,
  user_id text not null,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);
```

Quillmate will request a list of records from the `articles` table using the following code. Note that I am not leveraging the user ID within these queries. This is because the Clerk/Supabase integration will automatically parse the user ID from the request.

```tsx
const { data, error } = await supabase
  .from('articles')
  .select('*')
  .order('updated_at', { ascending: false })
```

And is protected from the following RLS policies. Note the use of `auth.jwt()->>'sub'` which extracts the `sub` claim (which is the user ID) from the JWT used on the request and compares it with the `user_id` column in the table.

```sql
CREATE POLICY "Users can view their own articles"
  ON public.articles
  FOR SELECT
  USING (auth.jwt()->>'sub' = user_id);

CREATE POLICY "Users can create their own articles"
  ON public.articles
  FOR INSERT
  WITH CHECK (auth.jwt()->>'sub' = user_id);

CREATE POLICY "Users can update their own articles"
  ON public.articles
  FOR UPDATE
  USING (auth.jwt()->>'sub' = user_id);

CREATE POLICY "Users can delete their own articles"
  ON public.articles
  FOR DELETE
  USING (auth.jwt()->>'sub' = user_id);
```

## Conclusion

Building an application with Supabase can challenge the architectural norms that developers have used for decades. However, once you properly understand how the data is accessed and secured by Supabase, building with this new approach can save development time and potentially increase table security by ensuring that each request is evaluated with RLS on a row-by-row basis.

This includes B2B applications, which is covered in our follow up article on [building multi-tenancy applications with Supabase and Clerk](/blog/multitenancy-clerk-supabase-b2b).

---

# Next.js CVE-2025-29927
URL: https://clerk.com/blog/cve-2025-29927.md
Date: 2025-03-23
Category: Company
Description: On March 21, 2025, Next.js disclosed a critical security vulnerability, CVE-2025-29927, that may impact your application.

On March 21, 2025, Next.js disclosed a critical security vulnerability, [CVE-2025-29927](https://nextjs.org/blog/cve-2025-29927), that may impact your application.

This vulnerability allows attackers to bypass middleware-based authentication and authorization protections, potentially allowing unauthorized access to your application.

## Impacted applications

> \[!IMPORTANT]
> If your application is not using Next.js, or if it is hosted on Vercel or Netlify, it is not impacted.

Yesterday, we mistakenly [announced on X](https://x.com/clerk/status/1903497002828120426) that all applications using Clerk were not impacted. Since then, we have discovered two scenarios where your application may be impacted.

1. You use Clerk's middleware for protecting routes that do not directly read user data. This is most likely to impact static applications that solely rely on middleware for authentication checks. **If you call any of the following methods in routes protected by middleware, your page or endpoint is safe:**
   - `auth()`
   - `getAuth()`
   - `protect()`
   - `currentUser()`

2. Or, you have not upgraded to `@clerk/nextjs@5.2` or higher, which was released in June 2024.

## Patching your application

If your application is impacted, the remediation is to upgrade your `next` package as follows:

- For Next.js 15.x, this issue is fixed in `15.2.3` forward
- For Next.js 14.x, this issue is fixed in `14.2.25` forward
- For Next.js 13.x, this issue is fixed in `13.5.9` forward

If patching to a safe version is infeasible, it is recommended that you prevent external user requests which contain the `x-middleware-subrequest` header from reaching your Next.js application. If your application uses Cloudflare, this can safely be accomplished with a [Managed WAF](https://developers.cloudflare.com/changelog/2025-03-22-next-js-vulnerability-waf/) rule.

Even if you are not impacted, we strongly recommend that you upgrade to the latest versions of `next` and `@clerk/nextjs`.

## Additional support is available

We have also sent an email to the administrators of all Clerk applications that are potentially impacted by this vulnerability. If you have questions, need help determining whether your application is at risk, or need help with mitigation, reply to the email you received or reach out directly to [support@clerk.com](mailto:support@clerk.com)

## Security at Clerk

Our announcement on X that Clerk applications were not impacted was a significant error. We apologize, and will be reflecting on and improving our procedures for zero-day vulnerabilities to ensure it does not happen again.

Going forward, we are pleased that the Next.js team has committed to giving Clerk advance notice on vulnerabilities. We will be seeking similar relationships with other framework authors.

---

# Postmortem: February 6, 2025 service outage
URL: https://clerk.com/blog/postmortem-feb-6-2025-service-outage.md
Date: 2025-02-11
Category: Company
Description: Learn more about our service outage, including the timeline of events and our remediations.

On Thursday, February 6th, 2025, a database query was directly executed to deprecate a feature for 3,700 customers, and an error in the query resulted in immediate downtime for those customers. In addition, the downtime triggered automatic retries elsewhere in our service which nearly overloaded our infrastructure, and created significant delays for our other customers for 4 minutes, until the retry backoff took effect.

The incident lasted a total of 26 minutes, from the initial error to when the query was successfully reversed, and our systems returned to normal.

As a provider of mission-critical infrastructure, we recognize that this outage is unacceptable. After a detailed review of the incident, we have determined several actions that can be taken to mitigate its recurrence. Some have already been implemented, while others will require more significant engineering efforts.

In this postmortem, we discuss the timeline of events, and our complete set of remediations.

## Timeline of events

- **9:43 UTC** — Erroneous update query runs, setting `false` values to `"true"` within a jsonb field.
- **9:45 UTC** — Engineers receive error alerts and begin investigating.
- **9:47 UTC** — First customer outage reports arrive.
- **9:48 UTC** — Internal incident is declared.
- **9:50 UTC** — Status page updated (initially with an incorrect start time).
- **10:00-10:04 UTC** — Engineers begin manually restoring service for customers while a bulk resolution is prepared.
- **10:05 UTC** — Bulk update query is executed to correct the issue.
- **10:06 UTC** — Bulk update query completes, service health is restored.
- **10:10 UTC** — Status page updated to reflect restored status with accurate start/end times.

## Remediations

### Tuning automatic retry mechanisms

One of our retry mechanisms was misconfigured to retry too aggressively on 500-class errors, which increased the blast radius of this event. An adjustment to the mechanism has already been applied, and an audit of other retry mechanisms is being conducted.

### Further limiting direct database access

Direct database access at Clerk is already significantly limited, with only a small subset of our most senior team having this permission. However, our processes indicated they should use their own judgement for when it is safe and appropriate to leverage the capability.

Going forward, these team members will retain access, but our policies will dictate that it is only leveraged in true emergency situations, when downtime is actively impacting our customers. Other changes must be executed from within our change management tooling.

### Mandating staged rollouts for all changes to critical infrastructure

In 2024, Clerk’s platform team developed several new mechanisms for staged rollouts. As Clerk has grown, we have seen a healthy culture where our engineers *demand* that staged rollout infrastructure is in place. In many cases, we’ve delayed launches to build more mechanisms where they are missing.

In our review since the incident, we confirmed that the vast majority of changes to our critical systems leverage staged rollouts. However, when our team noted exceptions, it was always because the change was considered simple, including the one that led to this incident.

In addition, our review revealed that different projects have approached building cohorts for staged rollouts differently.

Going forward, we will be mandating that all changes to critical infrastructure require staged rollouts. We will also codify a process for building and ordering cohorts, which will incorporate the number of active users an application is supporting, and the subscription plan that applications are enrolled on.

### Improving SDK resilience for session management service outages

Clerk’s session management service is designed with a once-per-minute JWT refresh. We leverage this design in three critical areas of our service:

- **Session revocation:** When a session is revoked administratively – either by the user or by an application administrator – the revocation is achieved by blocking new JWTs from being generated. Using a short-lived JWT means we can guarantee revocation within one minute.
- **Abuse detection and prevention:** CAPTCHAs during sign up have become less effective recently as AI has gained the ability to solve them. At the same time, freemium and trial pricing have become commonplace. We’re engaged in a constant cat-and-mouse game with these attackers, and have found that our once-per-minute session refresh mechanism is a much more effective place to detect and prevent abuse than sign up.
- **XSS mitigation:** JavaScript-accessible JWTs are an expectation of many application architectures, despite seeming antithetical to web security best practices on its face. The concern is that script-accessible JWTs can be exfiltrated during XSS attacks, which would allow continued use of the JWT even after the XSS is patched. Clerk can safely allow script access because our JWTs expire every minute, which ensures that successful exfiltration would not meaningfully extend an XSS attack.

In normal operation, our once-per-minute refresh is an implementation detail that most of our customers are not aware of. However, in the event of an outage like Thursday’s, it means our customers have a strong uptime dependency on Clerk.

Going forward, we would like to eliminate as much of this strong uptime dependency as possible. We believe we can update our SDKs so that if our session management service goes down, existing sessions are maintained throughout the outage, while new session creation, session revocation, abuse prevention, and XSS mitigation are not operational. This would result in future outages having less impact on our customers.

In the interest of full disclosure, we want to highlight that this is not a simple adjustment and will take time to develop. As a simple example of a challenge, we will need to ensure the `/.well-known/jwks.json` endpoint is hardened to avoid the downtime, and/or we need provide a mechanism to self-host the JWT public key. Regardless of the effort it takes, we are placing high priority on this project.

### Completely decoupling session management from user management

At a high level, Clerk operates two services: user management, which covers sign up, sign in, and user profiles, and session management, which only handles sessions. These two systems started tightly coupled, but have naturally decoupled with time as they represent significantly different workloads:

- User management requires relatively low read and write, but it has many moving pieces. There are many different settings, and our customers use thousands of different permutations of those settings. In addition, we’re frequently introducing new settings and modifying existing settings as authentication evolves.
- Session management is the opposite. It’s extremely high read, low write, and has relatively few moving pieces.

In this incident, an error in our user management service brought down our session management service.

Going forward, we plan to decouple session management from user management as much as possible. They will still be tightly *integrated*, since sign up and sign in lead to the creation of a session, but downtime in user management should not lead downtime in session management.

### Eliminating the use of JSON column types for structured and typed data

Some application settings are stored in JSON column types. These columns have been used primarily for convenience, with types being enforced at our compute layer. In this incident, strict typing was not enforced for the query because it was executed directly against the database, which led to the outage.

Going forward, in addition to further limiting direct database access, we are ceasing additional use of JSON column types for structured and typed data. Instead, we will use strongly typed database columns, which would have prevented the erroneous query from being executed. Over time, we will also migrate and deprecate our existing usage of JSON column types.

## Looking Ahead

We regret the impact this incident had on our customers. At Clerk, reliability is a top priority, and this postmortem reflects our commitment to transparency and continuous improvement.

Some fixes are already in place, while others—like enhanced SDK resilience and service decoupling—are being prioritized to prevent future incidents and strengthen our platform.

For any questions, please [contact support](/contact)

---

# Implement Role-Based Access Control in Next.js 15
URL: https://clerk.com/blog/nextjs-role-based-access-control.md
Date: 2025-02-07
Category: Company
Description: Learn Role-Based Access Control (RBAC) by building a complete Q&A platform.

Assigning permissions to individual users is a complex task, especially when you have a large number of users.

[Role-Based Access Control (RBAC)](/glossary#role-based-access-control-rbac) is a popular approach to managing access permissions in software applications, allowing you to assign different roles and permissions to different users.

This article will guide you through building a Q\&A platform using Next.js and Neon, and show you how to implement authentication and RBAC with [Clerk](/nextjs-authentication).

## What is Role-Based Access Control?

Before we get started, let's understand RBAC and how it will be implemented in the Q\&A platform.

RBAC is a security method that allows users to interact with features of an application or system based on their roles and the permissions granted to those roles.

This approach simplifies access management by grouping permissions into roles instead of assigning them to individual users, making it easier to maintain and scale as your application grows.

By implementing RBAC, organizations can enforce the [principle of least privilege](https://www.cyberark.com/what-is/least-privilege/), ensuring users only have access to the resources necessary for their specific responsibilities.

Below is a breakdown of the permissions for each role in the Q\&A platform:

| Role        | Description                                                               | Permissions                                                                                                                            |
| ----------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Viewer      | Users not signed in or logged into the Q\&A platform.                     | • View questions and answers                                                                                                           |
| Contributor | Registered users who can ask or answer questions in the Q\&A platform.    | Everything from the Viewer role + • Post questions• Answer questions• Edit own questions and answers• Delete own questions and answers |
| Moderator   | Users with additional permissions to manage content in the Q\&A platform. | Everything from the Contributor role + • Admin dashboard access• Approve and disapprove questions• Approve and disapprove answers      |
| Admin       | Users with full control over the Q\&A platform.                           | Everything from the Moderator role + • Edit others' questions and answers• Delete others' questions and answers• Manage user roles     |

With that in mind, let us jump straight into building the Q\&A platform and implement RBAC using Clerk.

> \[!IMPORTANT]
> Clerk provides two approaches to RBAC.
>
> [Organizations](/docs/organizations/overview) is ideal for B2B applications like GitHub or Notion, where your customers are teams or companies who need to invite team members and manage their roles. It includes built-in role management and components for role-based rendering.
>
> [User metadata](/docs/references/nextjs/basic-rbac), demonstrated in this guide, is better suited for B2C applications with simple permission structures where individual users have different access levels but don't belong to teams.

## What you'll build

Before writing any code, let's take a look at how our platform works.

The landing page enables users to sign up or sign in using the **Start Exploring** button. It features a clean design with a navigation menu and welcome message.

Once signed in, users can ask questions and provide answers. Each question displays the author's name, timestamp, and interaction options. Users can edit or delete their own content, and view answers from other contributors.

![Homepage of a Q\&A Platform with a centered welcome message, navigation menu at the top showing Home, Q\&A, and Admin options, and a user profile icon. The main content features a large 'Welcome to Our Q\&A Platform' heading, followed by a subtitle encouraging community participation and a 'Start Exploring' button. The page has a clean, minimal design with a white background and footer copyright text.](./home-page.png)

![Q\&A Platform page showing a question input field at the top with an 'Ask' button. Below are two example questions: 'What is React used for?' and 'How many days are in a week?' Each question displays the author's name (Brian Morrison), timestamp, and has edit and delete icons. Questions include their answers with similar metadata and interaction options. The page maintains the same header with navigation menu and user profile icon as the homepage.](./qa-page.png)

Administrators can review and moderate all submitted content through the Admin Dashboard. The page displays questions and answers with approval status indicators, allowing admins to approve or reject content using simple checkmark and X icons.

![Admin Dashboard showing moderation controls for Q\&A content. The page displays questions and answers with approval status indicators - green 'Approved' tags for accepted content and red 'Disapproved' tags for rejected content. Each entry has approve/reject buttons (green checkmark and red X icons). The dashboard includes a 'Set Roles' button at the top right. Questions shown include 'What is React used for?' and 'How many days are in a week?' with their respective answers and moderation statuses. The page maintains the consistent header with navigation and user profile.](./admin-dashboard.png)

Finally, administrators can manage user permissions through the role management page. Using a search interface, admins can find users and assign appropriate roles (Admin, Moderator, Contributor, or Viewer) or remove existing roles.

![User role management page with a search bar and Submit button at the top. Below shows a user profile for Brian Morrison with their current role listed as admin. A row of role management buttons includes 'Make Admin', 'Make Moderator', 'Make Contributor', 'Make Viewer', and a red 'Remove Role' button. The page maintains the standard Q\&A Platform header with navigation menu and user profile icon.](./user-roles-page.png)

## Building the frontend

In this section, we will build the frontend of the Q\&A platform. In order to follow along, you should have a basic understanding of React or Next.js, as well as Node.js installed.

> If you're new to Next.js authentication, check out our [Ultimate Guide to Next.js Authentication](/blog/nextjs-authentication) for foundational concepts.

### Install dependencies

Start by running the following command in your terminal to bootstrap a Next.js application, accepting the default options as they are presented:

```bash
npx create-next-app@latest qa-app
cd qa-app
```

Next, run the following commands to initialize shadcn/ui, once again accepting the default configuration options as they are presented:

```bash
npx shadcn@latest init
```

Add the necessary components to build the UI components of the Q\&A platform:

```bash
npx shadcn@latest add button input card separator badge
```

Finally, install Lucide React, which will be used to render the icons in the UI components:

```bash
npm install lucide-react
```

With the dependencies in place, let's create the components that used with the layout, starting with the type definitions we'll use throughout the process.

### Creating the header and updating the homepage

The header component will allow our users to navigate to different parts of the application, as well as hold the Clerk `UserButton` component (to be done later in this guide).

Create the `src/components/Header.tsx` file and paste in the following code:

```tsx {{ filename: 'src/components/Header.tsx' }}
'use client'

import Link from 'next/link'
import { Button } from '@/components/ui/button'

const Header = () => {
  return (
    <header className="border-b bg-white">
      <div className="container mx-auto px-4">
        <div className="flex h-16 items-center justify-between">
          <Link href="/" className="text-xl font-bold">
            Q&A platform
          </Link>
          <nav>
            <ul className="flex space-x-4">
              <li>
                <Link href="/">
                  <Button variant="ghost">Home</Button>
                </Link>
              </li>
              <li>
                <Link href="/qa">
                  <Button variant="ghost">Q&A</Button>
                </Link>
              </li>

              <li>
                <Link href="/admin">
                  <Button variant="ghost">Admin</Button>
                </Link>
              </li>
            </ul>
          </nav>
        </div>
      </div>
    </header>
  )
}

export default Header
```

With the header component in place, replace the code in `app/page.tsx` with the following, which will update the homepage to use the header component and match the demo:

```tsx {{ filename: 'app/page.tsx' }}
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import Header from '@/components/Header'

export default function Home() {
  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="flex-grow">
        <section className="flex w-full items-center justify-center py-12 md:py-24 lg:py-32 xl:py-48">
          <div className="container px-4 md:px-6">
            <div className="flex flex-col items-center space-y-4 text-center">
              <div className="space-y-2">
                <h1 className="text-3xl font-bold tracking-tighter sm:text-4xl md:text-5xl lg:text-6xl/none">
                  Welcome to Our Q&A platform
                </h1>
                <p className="mx-auto max-w-[700px] text-gray-500 md:text-xl dark:text-gray-400">
                  Join our community to ask questions, share knowledge, and learn from others.
                </p>
              </div>
              <div className="space-x-4">
                <Link href="/qa">
                  <Button size="lg">Start Exploring</Button>
                </Link>
              </div>
            </div>
          </div>
        </section>
      </main>
      <footer className="flex w-full items-center justify-center bg-gray-100 py-6 dark:bg-gray-800">
        <div className="container px-4 md:px-6">
          <p className="text-center text-sm text-gray-500 dark:text-gray-400">
            (c) 2024 Q&A platform. All rights reserved.
          </p>
        </div>
      </footer>
    </div>
  )
}
```

### Create the Q\&A section

Next, you'll build out the Q\&A section, where signed-in users can ask and answer questions and anonymous users can view questions. We'll start by creating a few shared components that will be used throughout the application before creating the page that will display the Q\&A section.

Let's start by creating a file named `types/types.d.ts` to define the types we'll be using. Populate it with the following code:

```ts {{ filename: 'types/types.d.ts' }}
interface Answer {
  id: number | null
  ans: string
  approved?: boolean | null
  contributor: string
  contributorId: string
  questionId: number
  timestamp?: string // ISO 8601 string format
}

interface Question {
  id: number | null
  quiz: string
  approved: boolean | null
  answers: Answer[]
  contributor: string
  contributorId: string
  timestamp?: string // ISO 8601 string format
}

type Roles = 'admin' | 'moderator' | 'contributor' | 'viewer'
```

Now you'll create the component that renders the form where users can ask questions. Create the `src/components/QuestionForm.tsx` file and paste in the following:

```tsx {{ filename: 'src/components/QuestionForm.tsx' }}
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'

interface QuestionFormProps {
  onSubmit: (question: string) => void
}

export default function QuestionForm({ onSubmit }: QuestionFormProps) {
  const [quiz, setQuiz] = useState('')
  const [showSubmitText, setShowSubmitText] = useState(false)

  useEffect(() => {
    if (showSubmitText) {
      setTimeout(() => {
        setShowSubmitText(false)
      }, 7000)
    }
  }, [showSubmitText])

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    if (quiz.trim()) {
      onSubmit(quiz)
      setQuiz('')
      setShowSubmitText(true)
    }
  }

  return (
    <form onSubmit={handleSubmit} className="mb-6">
      <div className="flex gap-2">
        <div className="flex-grow">
          <Input
            type="text"
            name="quiz"
            id="quiz"
            value={quiz}
            onChange={(e) => setQuiz(e.target.value)}
            placeholder="Ask a question..."
            className="flex-grow"
          />
          <div className="h-4 text-sm text-green-500 transition-all">
            {showSubmitText ? 'Your question has been submitted for review.' : ''}
          </div>
        </div>
        <Button type="submit">Ask</Button>
      </div>
    </form>
  )
}
```

Next, modify the `src/lib/utils.ts` file to add a helper function used to format dates in a more friendly way, which will be used in the `QuestionItem` and `AnswerItem` components you'll create in a moment:

```ts {{ filename: 'src/lib/utils.ts', ins: [[8, 17]], del: [] }}
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

export function formatDate(dateString: string) {
  const date = new Date(dateString)
  return date.toLocaleString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
  })
}
```

Now create a `QuestionItem` component that manages and displays a question and its answers. Moreover, the component allows adding, editing, and deleting of questions or answers.

Create the `src/components/QuestionItem.tsx` file and paste the following code into the file:

```tsx {{ filename: 'src/components/QuestionItem.tsx' }}
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card'
import { Pencil, Trash2 } from 'lucide-react'
import { Separator } from '@/components/ui/separator'
import AnswerItem from './AnswerItem'
import { formatDate } from '@/lib/utils'

interface Props {
  question: Question
  onEditQuestion: (id: number, newText: string) => void
  onDeleteQuestion: (id: number) => void
  onAddAnswer: (questionId: number, answerText: string) => void
  onEditAnswer: (answerId: number, newText: string) => void
  onDeleteAnswer: (answerId: number) => void
}

export default function QuestionItem({
  question,
  onEditQuestion,
  onDeleteQuestion,
  onAddAnswer,
  onEditAnswer,
  onDeleteAnswer,
}: Props) {
  const [answer, setAnswer] = useState('')
  const [isEditing, setIsEditing] = useState(false)
  const [editedQuestion, setEditedQuestion] = useState(question.quiz)
  const [showSubmitText, setShowSubmitText] = useState(false)

  useEffect(() => {
    if (showSubmitText) {
      setTimeout(() => {
        setShowSubmitText(false)
      }, 7000)
    }
  }, [showSubmitText])

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (answer.trim()) {
      if (question.id !== null) {
        onAddAnswer(question.id, answer)
        setShowSubmitText(true)
      }
      setAnswer('')
    }
  }

  const handleQuestionEdit = () => {
    if (editedQuestion.trim() && editedQuestion !== question.quiz) {
      if (question.id !== null) {
        onEditQuestion(question.id, editedQuestion)
      }
      setIsEditing(false)
    }
  }

  const handleAnswerEdit = async (answerId: number | null, newText: string) => {
    if (answerId !== null && question.id !== null) {
      await onEditAnswer(answerId, newText)
    }
  }

  const handleAnswerDelete = async (answerId: number | null) => {
    if (answerId !== null && question.id !== null) {
      await onDeleteAnswer(answerId)
    }
  }

  return (
    <Card>
      <CardHeader>
        {isEditing ? (
          <div className="flex gap-2">
            <Input
              value={editedQuestion}
              onChange={(e) => setEditedQuestion(e.target.value)}
              className="flex-grow"
            />
            <Button onClick={handleQuestionEdit}>Save</Button>
            <Button variant="outline" onClick={() => setIsEditing(false)}>
              Cancel
            </Button>
          </div>
        ) : (
          <div>
            <div className="mb-2 flex items-center justify-between">
              <CardTitle>{question.quiz}</CardTitle>

              <div>
                <Button variant="ghost" size="icon" onClick={() => setIsEditing(true)}>
                  <Pencil className="h-4 w-4" />
                </Button>
                <Button
                  variant="ghost"
                  size="icon"
                  onClick={() => question.id !== null && onDeleteQuestion(question.id)}
                >
                  <Trash2 className="h-4 w-4" />
                </Button>
              </div>
            </div>
            <div className="text-sm text-gray-500">
              <span>{question.contributor}</span>
              <span> • </span>
              <span>{question.timestamp && formatDate(question.timestamp)}</span>
            </div>
          </div>
        )}
      </CardHeader>
      <CardContent>
        <h3 className="mb-2 font-semibold">Answers:</h3>
        {question.answers && question.answers.filter((a) => a.approved !== false).length > 0 ? (
          <ul className="space-y-4">
            {question.answers
              .filter((a) => a.approved !== false)
              .map((answer, index, filteredAnswers) => (
                <li key={answer.id}>
                  <AnswerItem
                    answer={answer}
                    onEditAnswer={(newText) => handleAnswerEdit(answer.id, newText)}
                    onDeleteAnswer={() => handleAnswerDelete(answer.id)}
                  />
                  {index < filteredAnswers.length - 1 && <Separator className="my-2" />}
                </li>
              ))}
          </ul>
        ) : (
          <p className="text-gray-500">No answers yet.</p>
        )}
      </CardContent>

      <CardFooter>
        <form onSubmit={handleSubmit} className="w-full">
          <div className="flex gap-2">
            <div className="flex-grow">
              <Input
                type="text"
                value={answer}
                onChange={(e) => setAnswer(e.target.value)}
                placeholder="Add an answer..."
              />

              <div className="h-4 text-sm text-green-500 transition-all">
                {showSubmitText ? 'Your answer has been submitted for review.' : ''}
              </div>
            </div>
            <Button type="submit">Answer</Button>
          </div>
        </form>
      </CardFooter>
    </Card>
  )
}
```

Your editor may be displaying an error regarding the `AnswerItem` component, which does not yet exist. This component is used to render answers for each question.

Create the `src/components/AnswerItem.tsx` file and paste the following code into the file:

```tsx {{ title: 'src/components/AnswerItem.tsx' }}
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Pencil, Trash2 } from 'lucide-react'
import { formatDate } from '@/lib/utils'

interface Props {
  answer: Answer
  onEditAnswer: (newText: string) => void
  onDeleteAnswer: () => void
}

function AnswerItem({ answer, onEditAnswer, onDeleteAnswer }: Props) {
  const [isEditing, setIsEditing] = useState(false)
  const [editedAnswer, setEditedAnswer] = useState(answer.ans)

  const handleEdit = () => {
    if (editedAnswer.trim() && editedAnswer !== answer.ans) {
      onEditAnswer(editedAnswer)
      setIsEditing(false)
    }
  }

  return (
    <div>
      {isEditing ? (
        <div className="flex w-full gap-2">
          <Input
            value={editedAnswer}
            onChange={(e) => setEditedAnswer(e.target.value)}
            className="flex-grow"
          />
          <Button onClick={handleEdit}>Save</Button>
          <Button variant="outline" onClick={() => setIsEditing(false)}>
            Cancel
          </Button>
        </div>
      ) : (
        <div className="space-y-2">
          <div className="flex items-start justify-between">
            <p>{answer.ans}</p>

            <div>
              <Button variant="ghost" size="icon" onClick={() => setIsEditing(true)}>
                <Pencil className="h-4 w-4" />
              </Button>
              <Button variant="ghost" size="icon" onClick={onDeleteAnswer}>
                <Trash2 className="h-4 w-4" />
              </Button>
            </div>
          </div>
          <div className="text-sm text-gray-500">
            <span>{answer.contributor}</span>
            <span> • </span>
            <span>{answer.timestamp && formatDate(answer.timestamp)}</span>
          </div>
        </div>
      )}
    </div>
  )
}

export default AnswerItem
```

Now you'll create the page that manages and displays questions and answers, handles form submissions, and interacts with the user. Note that the code below contains function placeholders that will be implemented later in this article to interact with the database.

Create the `app/qa/page.tsx` file and paste the following code into the file:

```tsx {{ filename: 'app/qa/page.tsx' }}
'use client'

import { useState, useEffect } from 'react'
import QuestionForm from '../../components/QuestionForm'
import QuestionItem from '@/components/QuestionItem'
import Header from '../../components/Header'

export default function QAPage() {
  const [questions, setQuestions] = useState<Question[]>([])

  useEffect(() => {
    fetchQuestions()
  }, [])

  // These placeholders will be populated later in this guide
  const fetchQuestions = async () => {}
  const addQuestion = async (question: string) => {}
  const editQuestion = async (id: number, newText: string) => {}
  const deleteQuestion = async (id: number) => {}
  const addAnswer = async (questionId: number, answer: string) => {}
  const editAnswer = async (answerId: number, newText: string) => {}
  const deleteAnswer = async (answerId: number) => {}

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <QuestionForm onSubmit={addQuestion} />
        {Array.isArray(questions) && (
          <div className="space-y-4">
            {questions.map((question) => (
              <QuestionItem
                key={question.id}
                question={question}
                onEditQuestion={editQuestion}
                onDeleteQuestion={deleteQuestion}
                onAddAnswer={addAnswer}
                onEditAnswer={editAnswer}
                onDeleteAnswer={deleteAnswer}
              />
            ))}
          </div>
        )}
      </main>
    </div>
  )
}
```

### Creating the admin area

With the home page and Q\&A page created, it's time to create the admin area. The admin area will be used to manage questions and answers.

Start by creating the `src/components/QuestionCard.tsx` which is used by the admin page for approving and managing questions and answers. Paste the following into that file:

```tsx {{ filename: 'src/components/QuestionCard.tsx' }}
'use client'

import { Button } from '@/components/ui/button'
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import { CheckCircle, XCircle } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import { formatDate } from '@/lib/utils'

interface Props {
  question: Question
  onQuestionApproved: (id: number) => void
  onQuestionDisapproved: (id: number) => void
  onAnswerApproved: (answerId: number) => void
  onAnswerDisapproved: (answerId: number) => void
}

export default function QuestionCard({
  question,
  onQuestionApproved,
  onQuestionDisapproved,
  onAnswerApproved,
  onAnswerDisapproved,
}: Props) {
  return (
    <Card>
      <CardHeader>
        <CardTitle className="flex flex-col">
          <div className="mb-2 flex items-start justify-between">
            <div className="flex items-center space-x-2">
              <span className="text-xl">{question.quiz}</span>
              <ApprovalBadge approved={question.approved} />
            </div>

            <div>
              <Button
                variant="ghost"
                size="icon"
                onClick={() => question.id !== null && onQuestionApproved(question.id)}
              >
                <CheckCircle className="h-4 w-4 text-green-500" />
              </Button>
              <Button
                variant="ghost"
                size="icon"
                onClick={() => question.id !== null && onQuestionDisapproved(question.id)}
              >
                <XCircle className="h-4 w-4 text-red-500" />
              </Button>
            </div>
          </div>
          <div className="text-sm text-gray-500">
            <span>{question.contributor}</span>
            <span> • </span>
            <span>{question.timestamp && formatDate(question.timestamp)}</span>
          </div>
        </CardTitle>
      </CardHeader>
      <CardContent>
        <h3 className="mb-2 font-semibold">Answers:</h3>
        {question.answers && question.answers.length > 0 ? (
          <ul className="space-y-4">
            {question.answers.map((answer) => (
              <li key={answer.id} className="flex flex-col">
                <div className="mb-2 flex items-start justify-between">
                  <div className="flex items-center space-x-2">
                    <span>{answer.ans}</span>
                    <ApprovalBadge approved={answer.approved ?? null} />
                  </div>
                  <div>
                    <Button
                      variant="ghost"
                      size="icon"
                      onClick={() => {
                        if (question.id !== null && answer.id !== null) {
                          onAnswerApproved(answer.id)
                        }
                      }}
                    >
                      <CheckCircle className="h-4 w-4 text-green-500" />
                    </Button>
                    <Button
                      variant="ghost"
                      size="icon"
                      onClick={() => {
                        if (question.id !== null && answer.id !== null) {
                          onAnswerDisapproved(answer.id)
                        }
                      }}
                    >
                      <XCircle className="h-4 w-4 text-red-500" />
                    </Button>
                  </div>
                </div>
                <div className="text-sm text-gray-500">
                  <span>{answer.contributor}</span>
                  <span> • </span>
                  <span>{answer.timestamp && formatDate(answer.timestamp)}</span>
                </div>
              </li>
            ))}
          </ul>
        ) : (
          <p className="text-gray-500">No answers yet.</p>
        )}
      </CardContent>
    </Card>
  )
}

function ApprovalBadge({ approved }: { approved: boolean | null }) {
  if (approved === true) {
    return (
      <Badge variant="outline" className="border-green-300 bg-green-100 text-green-800">
        Approved
      </Badge>
    )
  } else if (approved === false) {
    return (
      <Badge variant="outline" className="border-red-300 bg-red-100 text-red-800">
        Disapproved
      </Badge>
    )
  } else {
    return (
      <Badge variant="outline" className="border-yellow-300 bg-yellow-100 text-yellow-800">
        Pending
      </Badge>
    )
  }
}
```

Create the `app/admin/page.tsx` file, used to render the area for admins and moderators, and paste the following code into the file:

```tsx {{ filename: 'app/admin/page.tsx' }}
'use client'

import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import Link from 'next/link'
import Header from '@/components/Header'
import QuestionCard from '@/components/QuestionCard'

export default function AdminPage() {
  const [questions, setQuestions] = useState<Question[]>([])

  useEffect(() => {
    fetchQuestions()
  }, [])

  // These placeholders will be populated later in this guide
  const fetchQuestions = async () => {}
  const onQuestionApproved = async (id: number) => {}
  const onQuestionDisapproved = async (id: number) => {}
  const onAnswerApproved = async (answerId: number) => {}
  const onAnswerDisapproved = async (answerId: number) => {}

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <h1 className="mb-6 text-3xl font-bold">Admin Dashboard</h1>
        <div className="mb-4 flex justify-end">
          <Button>
            <Link href="/admin/set-user-roles">Set Roles</Link>
          </Button>
        </div>
        <div className="space-y-4">
          {questions.map((question) => (
            <QuestionCard
              key={question.id}
              question={question}
              onQuestionApproved={onQuestionApproved}
              onQuestionDisapproved={onQuestionDisapproved}
              onAnswerApproved={onAnswerApproved}
              onAnswerDisapproved={onAnswerDisapproved}
            />
          ))}
        </div>
      </main>
    </div>
  )
}
```

### Testing the application

With all of our pages created, you can test the application by running the following command in the terminal, which will start the dev server and allow you to view the application in your browser:

```bash
npm run dev
```

By default it runs on `localhost:3000`, but may use a different port if `3000` is already in use. Use the provided URL to access the application.

## Adding Clerk for authentication and authorization

Now let's add authentication to the application using Clerk. In your browser, go to [the Clerk dashboard](https://dashboard.clerk.com/) to create an account if you don't already have one, which will automatically walk you through setting up your first application with Clerk. If you already have an account, sign in and create a new application, which will also guide you through setting up Clerk.

Follow steps 1-3 shown in the onboarding guide to install and configure Clerk in your Next.js application. Return to this page once you are finished to continue the tutorial.

### Setting up Clerk in the application

At this point, the Clerk SDK should be installed, and the middleware should be defined per the quickstart instructions. Next, you'll need to wrap the application with the `<ClerkProvider>` which will allow Clerk to protect pages that require authentication.

Update the `app/layout.tsx` file to wrap the application with the `<ClerkProvider>`:

```tsx {{ filename: 'app/layout.tsx', ins: [4, 27, 33], del: [] }}
import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import './globals.css'
import { ClerkProvider } from '@clerk/nextjs'

const geistSans = Geist({
  variable: '--font-geist-sans',
  subsets: ['latin'],
})

const geistMono = Geist_Mono({
  variable: '--font-geist-mono',
  subsets: ['latin'],
})

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
          {children}
        </body>
      </html>
    </ClerkProvider>
  )
}
```

Add the following environment variables to your `.env.local` file, which tell Clerk where to redirect users when they sign up or sign in:

```env {{ filename: '.env.local' }}
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/
```

Next, update the `header` component to include a `UserButton` if the user is signed in, or a `SignInButton` if the user is not signed in:

```tsx {{ filename: 'src/app/components/header.tsx', ins: [3, [34, 43]], del: [] }}
'use client'

import { SignInButton, SignedIn, SignedOut, UserButton } from '@clerk/nextjs'
import Link from 'next/link'
import { Button } from '@/components/ui/button'

const Header = () => {
  return (
    <header className="border-b bg-white">
      <div className="container mx-auto px-4">
        <div className="flex h-16 items-center justify-between">
          <Link href="/" className="text-xl font-bold">
            Q&A platform
          </Link>
          <nav>
            <ul className="flex space-x-4">
              <li>
                <Link href="/">
                  <Button variant="ghost">Home</Button>
                </Link>
              </li>
              <li>
                <Link href="/qa">
                  <Button variant="ghost">Q&A</Button>
                </Link>
              </li>

              <li>
                <Link href="/admin">
                  <Button variant="ghost">Admin</Button>
                </Link>
              </li>

              <SignedIn>
                <li className="flex items-center">
                  <UserButton />
                </li>
              </SignedIn>
              <SignedOut>
                <li className="flex items-center rounded bg-black px-2 font-bold text-white">
                  <SignInButton mode="modal" />
                </li>
              </SignedOut>
            </ul>
          </nav>
        </div>
      </div>
    </header>
  )
}

export default Header
```

### Test it out

If your application is no longer running, start it up again with `npm run dev` and use the updated header to log into the application. This will let you create a user account and redirect you back to the home page.

Once logged in, notice how the header includes the `UserButton` instead of the `SignInButton`.

## Configuring RBAC with Clerk

Now let's add RBAC to the application using Clerk metadata. The role for a specific user will be set in the Clerk metadata, which is arbitrary data that is stored alongside a user that can be accessed and modified through the Clerk API, as well as directly in the Dashboard.

### Set a role for the user

Log into the Clerk dashboard and navigate to the **Users** page and select your user account. Scroll down to the *User metadata* section and select **Edit** next to the *Public* option.

Add the following JSON and select Save to manually add the admin role to your own user account in order for it to have all the system permissions. Later in the tutorial, you will add a basic admin tool to change a user's role.

```json
{
  "role": "admin"
}
```

### Include the user role with the Clerk metadata

Next, you'll need to update the token created by Clerk to include the metadata when it's created. This will allow you to check the role of the user without having to make an additional API call.

In the Clerk Dashboard, navigate to the **Sessions** page. Under the *Customize session token* section, select **Edit**. In the modal that opens, enter the following JSON and select **Save**.

```json
{
  "metadata": "{{user.public_metadata}}"
}
```

### Declare the role types for the Metadata

Go back to the project and create a global type file to add type definitions for the metadata. Create the `types/global.d.ts` file and paste the following code into the file:

```ts {{ filename: 'types/global.d.ts' }}
export {}

declare global {
  interface CustomJwtSessionClaims {
    metadata: {
      role?: Roles
    }
  }
}
```

### Updating your middleware

The middleware is used to check each request it comes in and apply authentication logic. Let's update the middleware to check the role of the user and redirect them to the appropriate page.

Update `src/middleware.ts` as follows:

```ts {{ filename: 'src/middleware.ts', ins: [4, 5, [8, 17]], del: [7] }}
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

// The route matcher defines routes that should be protected
const isAdminRoute = createRouteMatcher(['/admin(.*)'])

export default clerkMiddleware()
export default clerkMiddleware(async (auth, req) => {
  // Fetch the user's role from the session claims
  const userRole = (await auth()).sessionClaims?.metadata?.role

  // Protect all routes starting with `/admin`
  if (isAdminRoute(req) && !(userRole === 'admin' || userRole === 'moderator')) {
    const url = new URL('/', req.url)
    return NextResponse.redirect(url)
  }
})

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Always run for API routes
    '/(api|trpc)(.*)',
  ],
}
```

### Setting user roles from the application

Now we can define a new page in the application that will let admins set user roles. We'll start by creating the server actions that will be used to set the user role by passing in the role name.

Create the `src/app/admin/set-user-roles/actions.ts` file and paste the following code into the file:

```ts {{ filename: 'src/app/admin/set-user-roles/actions.ts' }}
'use server'

import { clerkClient } from '@clerk/nextjs/server'
import { checkRole } from './utils'

export async function setRole(formData: FormData): Promise<void> {
  const client = await clerkClient()

  try {
    const res = await client.users.updateUser(formData.get('id') as string, {
      publicMetadata: { role: formData.get('role') },
    })
    console.log({ message: res.publicMetadata })
  } catch (err) {
    throw new Error(err instanceof Error ? err.message : String(err))
  }
}

export async function removeRole(formData: FormData): Promise<void> {
  const client = await clerkClient()

  try {
    const res = await client.users.updateUser(formData.get('id') as string, {
      publicMetadata: { role: null },
    })
    console.log({ message: res.publicMetadata })
  } catch (err) {
    throw new Error(err instanceof Error ? err.message : String(err))
  }
}
```

Finally, we'll create a page that allows admins to search through users using the Clerk Backend API and the above server actions to set their role.

Create a file called `src/app/admin/set-user-roles/page.tsx` and paste in the following code to populate the page:

```tsx {{ filename: 'src/app/admin/set-user-roles/page.tsx' }}
// import { SearchUsers } from "./SearchUsers";
import { clerkClient } from '@clerk/nextjs/server'
import { removeRole, setRole } from './actions'
import Header from '@/components/Header'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'

export default async function AdminDashboard(params: {
  searchParams: Promise<{ search?: string }>
}) {
  const query = (await params.searchParams).search

  const client = await clerkClient()
  const users = query ? (await client.users.getUserList({ query })).data : []

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <form className="mb-6">
          <div className="flex flex-col gap-2">
            <label htmlFor="search">Search for users</label>
            <div className="flex gap-2">
              <Input id="search" name="search" type="text" className="flex-grow" />
              <Button type="submit">Submit</Button>
            </div>
          </div>
        </form>
        {users.map((user) => (
          <div key={user.id} className="flex min-h-screen flex-col">
            <div className="space-y-4 rounded-md bg-white p-4 shadow-md">
              <div className="text-lg font-semibold text-gray-800">
                {user.firstName} {user.lastName}
              </div>

              <div className="text-sm text-gray-600">
                {
                  user.emailAddresses.find((email) => email.id === user.primaryEmailAddressId)
                    ?.emailAddress
                }
              </div>

              <div className="text-sm font-medium text-blue-600">
                Role: {user.publicMetadata.role as string}
              </div>
              <div className="mt-2 flex space-x-4">
                <form action={setRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <input type="hidden" value="admin" name="role" />
                  <Button type="submit">Make Admin</Button>
                </form>

                <form action={setRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <input type="hidden" value="moderator" name="role" />
                  <Button type="submit">Make Moderator</Button>
                </form>

                <form action={setRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <input type="hidden" value="contributor" name="role" />
                  <Button type="submit">Make Contributor</Button>
                </form>

                <form action={setRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <input type="hidden" value="viewer" name="role" />
                  <Button type="submit">Make Viewer</Button>
                </form>

                <form action={removeRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <Button
                    type="submit"
                    className="rounded-md bg-red-600 px-4 py-2 text-white transition hover:bg-red-700"
                  >
                    Remove Role
                  </Button>
                </form>
              </div>
            </div>
          </div>
        ))}
      </main>
    </div>
  )
}
```

Add three more users to the Q\&A platform, then go to Admin page and click the **Set Roles** button. Search for the users you added and set their roles by clicking either the **Make Admin**, **Make Moderator**, **Make Contributor**, or **Make Viewer** button.

## Integrate with Postgres using Neon

In this section, you will learn how to integrate Neon Postgres with Clerk in the Q\&A platform, using `drizzle-orm` and `drizzle-kit` to interact with the database.

### Creating the database

Start by creating a new Neon database. Open your browser and go to neon.tech. Create an account if you don't already have one, then create a new database. Once the database is created, you'll be presented with a Quickstart screen. Select the **Copy snippet** button to copy it to your clipboard:

![The Neon Quickstart screen](./neon-connection-string.png)

Paste it into your `.env.local` file as `DATABASE_URL` like so:

```env {{ filename: '.env.local' }}
DATABASE_URL=postgresql://neondb_owner:***************@ep-black-boat-a8ryq543-pooler.eastus2.azure.neon.tech/neondb?sslmode=require
```

### Install the dependencies

Now you'll need to install the following dependencies:

- `drizzle-orm` - The ORM that the application will use to interact with the database.
- `drizzle-kit` - The tool that will generate migrations and interact with the database.
- `@neondatabase/serverless` - The driver that will be used to connect to the database.

Run the following command to install the dependencies:

```bash
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit 
```

### Setting up the database schema

Next you'll create the schema file which `drizzle-orm` will use to interact with the database, while `drizzle-kit` will be used to apply schema changes to the database.

Create a new file called `src/db/schema.ts` and paste in the following code:

```ts {{ filename: 'src/db/schema.ts' }}
import { pgTable, serial, text, boolean, timestamp, integer } from 'drizzle-orm/pg-core'
import { relations } from 'drizzle-orm'

// Questions table
export const questions = pgTable('questions', {
  id: serial('id').primaryKey(),
  quiz: text('quiz').notNull(),
  approved: boolean('approved'),
  contributor: text('contributor').notNull(),
  contributorId: text('contributor_id').notNull(),
  timestamp: timestamp('timestamp', { withTimezone: true }).defaultNow(),
})

// Answers table
export const answers = pgTable('answers', {
  id: serial('id').primaryKey(),
  ans: text('ans').notNull(),
  approved: boolean('approved'),
  contributor: text('contributor').notNull(),
  contributorId: text('contributor_id').notNull(),
  questionId: integer('question_id')
    .notNull()
    .references(() => questions.id),
  timestamp: timestamp('timestamp', { withTimezone: true }).defaultNow(),
})

// Define relationships using Drizzle's relations function
export const questionsRelations = relations(questions, ({ many }) => ({
  answers: many(answers),
}))

export const answersRelations = relations(answers, ({ one }) => ({
  question: one(questions, {
    fields: [answers.questionId],
    references: [questions.id],
  }),
}))
```

Create the `src/db/index.ts` file and paste in the following code, which is used by the application to establish a connection to the database:

```ts {{ filename: 'src/db/index.ts' }}
import { neon } from '@neondatabase/serverless'
import { drizzle } from 'drizzle-orm/neon-http'
import { questions, answers, questionsRelations, answersRelations } from './schema'

if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL must be a Neon postgres connection string')
}
const sql = neon(process.env.DATABASE_URL!)

export const db = drizzle(sql, {
  schema: { questions, answers, questionsRelations, answersRelations },
})
```

In the root of the project, create the `drizzle.config.ts` used by `drizzle-kit` to manage the database schema:

```ts {{ filename: 'drizzle.config.ts' }}
import { defineConfig } from 'drizzle-kit'
import { loadEnvConfig } from '@next/env'

loadEnvConfig(process.cwd())

if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL must be a Neon postgres connection string')
}

export default defineConfig({
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL,
  },
  schema: './src/db/schema.ts',
})
```

Finally, run the following command from your terminal to push the schema to the Neon database:

```bash
npx drizzle-kit push
```

If you go to the tables section in your Neon dashboard, you should see that two tables named `questions` and `answers` were created.

### Defining database interactions

Now that the database schema is set up, you can start defining database interactions. We're going to start with the database calls used by the main Q\&A section of the app.

Create `src/app/qa/actions.ts` and paste in the following:

```ts {{ filename: 'src/app/qa/actions.ts' }}
'use server'
import { db } from '@/db/index'
import { questions, answers } from '@/db/schema'
import { and, desc, eq } from 'drizzle-orm'
import { currentUser } from '@clerk/nextjs/server'

// Fetches all questions, available to authenticated and anonymous users
export async function getAllQuestions(): Promise<Question[]> {
  const data = await db
    .select()
    .from(questions)
    .where(eq(questions.approved, true))
    .orderBy(desc(questions.timestamp))
  const res: Question[] = data.map((question) => ({
    id: question.id,
    quiz: question.quiz,
    approved: question.approved,
    contributor: question.contributor,
    contributorId: question.contributorId,
    timestamp: question.timestamp?.toISOString(),
  }))
  for (const question of res) {
    const answerData = await db
      .select()
      .from(answers)
      .where(and(eq(answers.questionId, question.id as number), eq(answers.approved, true)))
      .orderBy(desc(answers.timestamp))
    question.answers = answerData.map((answer) => ({
      id: answer.id,
      ans: answer.ans,
      approved: answer.approved,
      contributor: answer.contributor,
      contributorId: answer.contributorId,
      questionId: answer.questionId,
      timestamp: answer.timestamp?.toISOString(),
    }))
  }
  return res
}

// Creates a new question, available only to authenticated users
export const createQuestion = async (quiz: string) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  await db.insert(questions).values({
    quiz: quiz,
    contributor: user.fullName as string,
    contributorId: user.id,
  })
}

// Creates a new answer, available only to authenticated users
export const createAnswer = async (answer: string, questionId: number) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  await db.insert(answers).values({
    ans: answer,
    contributor: user.fullName as string,
    contributorId: user.id,
    questionId: questionId,
  })
}

// Deletes a question, available only to the question's contributor
export const deleteQuestion = async (id: number) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  try {
    const result = await db
      .delete(questions)
      .where(and(eq(questions.id, id), eq(questions.contributorId, user.id)))
    return result
  } catch (error) {
    console.error('Error deleting question:', error)
    throw new Error('Failed to delete question')
  }
}

// Deletes an answer, available only to the answer's contributor
export const deleteAnswer = async (id: number) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  try {
    await db.delete(answers).where(and(eq(answers.id, id), eq(answers.contributorId, user.id)))
  } catch (error) {
    console.error('Error deleting answer:', error)
    throw new Error('Failed to delete answer')
  }
}

// Updates a question, available only to the question's contributor
export const updateQuestion = async (id: number, newText: string) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  try {
    await db
      .update(questions)
      .set({ quiz: newText })
      .where(and(eq(questions.contributorId, user.id), eq(questions.id, id)))
  } catch (error) {
    console.error('Error updating question:', error)
    throw new Error('Failed to update question')
  }
}

// Updates an answer, available only to the answer's contributor
export const updateAnswer = async (id: number, newText: string) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  try {
    await db
      .update(answers)
      .set({ ans: newText })
      .where(and(eq(answers.contributorId, user.id), eq(answers.id, id)))
  } catch (error) {
    console.error('Error updating answer:', error)
    throw new Error('Failed to update answer')
  }
}
```

Now we can wire up the placeholder functions in `src/app/qa/actions.ts` with the new database interactions we just created.

Update the `src/app/qa/page.tsx` file like so:

```tsx {{ filename: 'src/app/qa/page.tsx', ins: [7, [25, 58]], del: [[16, 23]] }}
'use client'

import { useState, useEffect } from 'react'
import QuestionForm from '../../components/QuestionForm'
import QuestionItem from '@/components/QuestionItem'
import Header from '../../components/Header'
import * as actions from '../../app/qa/actions'

export default function QAPage() {
  const [questions, setQuestions] = useState<Question[]>([])

  useEffect(() => {
    fetchQuestions()
  }, [])

  // These placeholders will be populated later in this guide
  const fetchQuestions = async () => {}
  const addQuestion = async (question: string) => {}
  const editQuestion = async (id: number, newText: string) => {}
  const deleteQuestion = async (id: number) => {}
  const addAnswer = async (questionId: number, answer: string) => {}
  const editAnswer = async (questionId: number, answerId: number, newText: string) => {}
  const deleteAnswer = async (questionId: number, answerId: number) => {}

  const fetchQuestions = async () => {
    const questions = await actions.getAllQuestions()
    setQuestions(questions)
  }

  const addQuestion = async (quiz: string) => {
    await actions.createQuestion(quiz)
    fetchQuestions()
  }

  const editQuestion = async (id: number, newText: string) => {
    await actions.updateQuestion(id, newText)
    fetchQuestions()
  }

  const deleteQuestion = async (id: number) => {
    await actions.deleteQuestion(id)
    fetchQuestions()
  }

  const addAnswer = async (questionId: number, answer: string) => {
    await actions.createAnswer(answer, questionId)
    fetchQuestions()
  }

  const editAnswer = async (answerId: number, newText: string) => {
    await actions.updateAnswer(answerId, newText)
    fetchQuestions()
  }

  const deleteAnswer = async (answerId: number) => {
    await actions.deleteAnswer(answerId)
    fetchQuestions()
  }

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <QuestionForm onSubmit={addQuestion} />
        {Array.isArray(questions) && (
          <div className="space-y-4">
            {questions.map((question) => (
              <QuestionItem
                key={question.id}
                question={question}
                onEditQuestion={editQuestion}
                onDeleteQuestion={deleteQuestion}
                onAddAnswer={addAnswer}
                onEditAnswer={editAnswer}
                onDeleteAnswer={deleteAnswer}
              />
            ))}
          </div>
        )}
      </main>
    </div>
  )
}
```

Now the server actions will prevent users from editing questions or answers that do not belong to them, but we can create a better user experience by making sure only the person who posted the question or answer can edit or delete it. The `useUser` hook from Clerk can be used to get the current user's information.

Update the `QuestionItem` component like so:

```tsx {{ filename: 'src/components/QuestionItem.tsx', ins: [9, 28, 94, 107], del: [] }}
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card'
import { Pencil, Trash2 } from 'lucide-react'
import { Separator } from '@/components/ui/separator'
import AnswerItem from './AnswerItem'
import { formatDate } from '@/lib/utils'
import { useUser } from '@clerk/nextjs'

interface Props {
  question: Question
  onEditQuestion: (id: number, newText: string) => void
  onDeleteQuestion: (id: number) => void
  onAddAnswer: (questionId: number, answerText: string) => void
  onEditAnswer: (answerId: number, newText: string) => void
  onDeleteAnswer: (answerId: number) => void
}

export default function QuestionItem({
  question,
  onEditQuestion,
  onDeleteQuestion,
  onAddAnswer,
  onEditAnswer,
  onDeleteAnswer,
}: Props) {
  const { user } = useUser()
  const [answer, setAnswer] = useState('')
  const [isEditing, setIsEditing] = useState(false)
  const [editedQuestion, setEditedQuestion] = useState(question.quiz)
  const [showSubmitText, setShowSubmitText] = useState(false)

  useEffect(() => {
    if (showSubmitText) {
      setTimeout(() => {
        setShowSubmitText(false)
      }, 7000)
    }
  }, [showSubmitText])

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (answer.trim()) {
      if (question.id !== null) {
        onAddAnswer(question.id, answer)
        setShowSubmitText(true)
      }
      setAnswer('')
    }
  }

  const handleQuestionEdit = () => {
    if (editedQuestion.trim() && editedQuestion !== question.quiz) {
      if (question.id !== null) {
        onEditQuestion(question.id, editedQuestion)
      }
      setIsEditing(false)
    }
  }

  const handleAnswerEdit = async (answerId: number | null, newText: string) => {
    if (answerId !== null && question.id !== null) {
      await onEditAnswer(answerId, newText)
    }
  }

  const handleAnswerDelete = async (answerId: number | null) => {
    if (answerId !== null && question.id !== null) {
      await onDeleteAnswer(answerId)
    }
  }

  return (
    <Card>
      <CardHeader>
        {isEditing ? (
          <div className="flex gap-2">
            <Input
              value={editedQuestion}
              onChange={(e) => setEditedQuestion(e.target.value)}
              className="flex-grow"
            />
            <Button onClick={handleQuestionEdit}>Save</Button>
            <Button variant="outline" onClick={() => setIsEditing(false)}>
              Cancel
            </Button>
          </div>
        ) : (
          <div>
            <div className="mb-2 flex items-center justify-between">
              <CardTitle>{question.quiz}</CardTitle>

              {user?.id === question.contributorId && (
                <div>
                  <Button variant="ghost" size="icon" onClick={() => setIsEditing(true)}>
                    <Pencil className="h-4 w-4" />
                  </Button>
                  <Button
                    variant="ghost"
                    size="icon"
                    onClick={() => question.id !== null && onDeleteQuestion(question.id)}
                  >
                    <Trash2 className="h-4 w-4" />
                  </Button>
                </div>
              )}
            </div>
            <div className="text-sm text-gray-500">
              <span>{question.contributor}</span>
              <span> • </span>
              <span>{question.timestamp && formatDate(question.timestamp)}</span>
            </div>
          </div>
        )}
      </CardHeader>
      <CardContent>
        <h3 className="mb-2 font-semibold">Answers:</h3>
        {question.answers && question.answers.filter((a) => a.approved !== false).length > 0 ? (
          <ul className="space-y-4">
            {question.answers
              .filter((a) => a.approved !== false)
              .map((answer, index, filteredAnswers) => (
                <li key={answer.id}>
                  <AnswerItem
                    answer={answer}
                    onEditAnswer={(newText) => handleAnswerEdit(answer.id, newText)}
                    onDeleteAnswer={() => handleAnswerDelete(answer.id)}
                  />
                  {index < filteredAnswers.length - 1 && <Separator className="my-2" />}
                </li>
              ))}
          </ul>
        ) : (
          <p className="text-gray-500">No answers yet.</p>
        )}
      </CardContent>

      <CardFooter>
        <form onSubmit={handleSubmit} className="w-full">
          <div className="flex gap-2">
            <div className="flex-grow">
              <Input
                type="text"
                value={answer}
                onChange={(e) => setAnswer(e.target.value)}
                placeholder="Add an answer..."
              />

              <div className="h-4 text-sm text-green-500 transition-all">
                {showSubmitText ? 'Your answer has been submitted for review.' : ''}
              </div>
            </div>
            <Button type="submit">Answer</Button>
          </div>
        </form>
      </CardFooter>
    </Card>
  )
}
```

And the `AnswerItem` component:

```tsx {{ filename: 'src/components/AnswerItem.tsx', ins: [6, 15, 44, 53], del: [] }}
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Pencil, Trash2 } from 'lucide-react'
import { formatDate } from '@/lib/utils'
import { useUser } from '@clerk/nextjs'

type Props = {
  answer: Answer
  onEditAnswer: (newText: string) => void
  onDeleteAnswer: () => void
}

function AnswerItem({ answer, onEditAnswer, onDeleteAnswer }: Props) {
  const { user } = useUser()
  const [isEditing, setIsEditing] = useState(false)
  const [editedAnswer, setEditedAnswer] = useState(answer.ans)

  const handleEdit = () => {
    if (editedAnswer.trim() && editedAnswer !== answer.ans) {
      onEditAnswer(editedAnswer)
      setIsEditing(false)
    }
  }

  return (
    <div>
      {isEditing ? (
        <div className="flex w-full gap-2">
          <Input
            value={editedAnswer}
            onChange={(e) => setEditedAnswer(e.target.value)}
            className="flex-grow"
          />
          <Button onClick={handleEdit}>Save</Button>
          <Button variant="outline" onClick={() => setIsEditing(false)}>
            Cancel
          </Button>
        </div>
      ) : (
        <div className="space-y-2">
          <div className="flex items-start justify-between">
            <p>{answer.ans}</p>
            {user?.id === answer.contributorId && (
              <div>
                <Button variant="ghost" size="icon" onClick={() => setIsEditing(true)}>
                  <Pencil className="h-4 w-4" />
                </Button>
                <Button variant="ghost" size="icon" onClick={onDeleteAnswer}>
                  <Trash2 className="h-4 w-4" />
                </Button>
              </div>
            )}
          </div>
          <div className="text-sm text-gray-500">
            <span>{answer.contributor}</span>
            <span> • </span>
            <span>{answer.timestamp && formatDate(answer.timestamp)}</span>
          </div>
        </div>
      )}
    </div>
  )
}

export default AnswerItem
```

Next let's create a set of server actions used by the admin area to manage questions and answers. Notice in the following code we dont need to check the user's role, because we are using the Clerk middleware to protect this route.

Create the `src/app/admin/actions.ts` file and add the following content:

```tsx {{ filename: 'src/app/admin/actions.ts' }}
'use server'
import { db } from '@/db/index'
import { questions, answers } from '@/db/schema'
import { eq, desc } from 'drizzle-orm'

export const getAllQuestionsWithAnswers = async () => {
  const questionsData = await db.select().from(questions).orderBy(desc(questions.timestamp))
  const res: Question[] = questionsData.map((question) => ({
    id: question.id,
    quiz: question.quiz,
    approved: question.approved,
    contributor: question.contributor,
    contributorId: question.contributorId,
    timestamp: question.timestamp?.toISOString(),
  }))
  for (const question of res) {
    const answerData = await db
      .select()
      .from(answers)
      .where(eq(answers.questionId, question.id as number))
      .orderBy(desc(answers.timestamp))
    if (!answerData) continue
    question.answers = answerData.map((answer) => ({
      id: answer.id,
      ans: answer.ans,
      approved: answer.approved,
      contributor: answer.contributor,
      contributorId: answer.contributorId,
      questionId: answer.questionId,
      timestamp: answer.timestamp?.toISOString(),
    }))
  }
  return res
}

export const approveQuestion = async (id: number) => {
  try {
    await db.update(questions).set({ approved: true }).where(eq(questions.id, id))
  } catch (error) {
    console.error('Error approving question:', error)
    throw new Error('Failed to approve question')
  }
}

export const disapproveQuestion = async (id: number) => {
  try {
    await db.update(questions).set({ approved: false }).where(eq(questions.id, id))
  } catch (error) {
    console.error('Error disapproving question:', error)
    throw new Error('Failed to disapprove question')
  }
}

export const approveAnswer = async (id: number) => {
  try {
    await db.update(answers).set({ approved: true }).where(eq(answers.id, id))
  } catch (error) {
    console.error('Error approving answer:', error)
    throw new Error('Failed to approve answer')
  }
}

export const disapproveAnswer = async (id: number) => {
  try {
    await db.update(answers).set({ approved: false }).where(eq(answers.id, id))
  } catch (error) {
    console.error('Error disapproving answer:', error)
    throw new Error('Failed to disapprove answer')
  }
}
```

Now let's do the same thing to the `admin` page as the `qa` page. Update the `src/app/admin/page.tsx` file like so:

```tsx {{ filename: 'app/admin/page.tsx', ins: [[8, 14], [30, 53]], del: [[23, 28]] }}
'use client'

import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import Link from 'next/link'
import Header from '@/components/Header'
import QuestionCard from '@/components/QuestionCard'
import {
  approveQuestion,
  disapproveQuestion,
  getAllQuestionsWithAnswers,
  approveAnswer,
  disapproveAnswer,
} from './actions'

export default function AdminPage() {
  const [questions, setQuestions] = useState<Question[]>([])

  useEffect(() => {
    fetchQuestions()
  }, [])

  // These placeholders will be populated later in this guide
  const fetchQuestions = async () => {}
  const onQuestionApproved = async (id: number) => {}
  const onQuestionDisapproved = async (id: number) => {}
  const onAnswerApproved = async (answerId: number) => {}
  const onAnswerDisapproved = async (answerId: number) => {}

  const fetchQuestions = async () => {
    const questions = await getAllQuestionsWithAnswers()
    setQuestions(questions)
  }

  const onQuestionApproved = async (id: number) => {
    await approveQuestion(id)
    fetchQuestions()
  }

  const onQuestionDisapproved = async (id: number) => {
    await disapproveQuestion(id)
    fetchQuestions()
  }

  const onAnswerApproved = async (answerId: number) => {
    await approveAnswer(answerId)
    fetchQuestions()
  }

  const onAnswerDisapproved = async (answerId: number) => {
    await disapproveAnswer(answerId)
    fetchQuestions()
  }

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <h1 className="mb-6 text-3xl font-bold">Admin Dashboard</h1>
        <div className="mb-4 flex justify-end">
          <Button>
            <Link href="/admin/set-user-roles">Set Roles</Link>
          </Button>
        </div>
        <div className="space-y-4">
          {questions.map((question) => (
            <QuestionCard
              key={question.id}
              question={question}
              onQuestionApproved={onQuestionApproved}
              onQuestionDisapproved={onQuestionDisapproved}
              onAnswerApproved={onAnswerApproved}
              onAnswerDisapproved={onAnswerDisapproved}
            />
          ))}
        </div>
      </main>
    </div>
  )
}
```

## Test it out!

After completing all the steps throughout this guide, you can now start up the application once more to test out all the features!

Here are a couple of things to try:

- Create a new question as a user of each role.
- Try approving and disapproving questions and answers.
- Try editing and deleting questions and answers.
- Explore the data in the Neon database.

## Conclusion

In this tutorial, you have learned how to integrate Clerk for authentication, configure RBAC using metadata, and enforce role-based restrictions to ensure users only access features appropriate to their roles. Also, you learned how to integrate Neon Postgres database with Drizzle ORM for seamless data management and how to conditionally render UI based on user roles.

By following this tutorial, developers can build secure applications by implementing Role-Based Access Control (RBAC) with Clerk.

Here is the [source code](https://github.com/bmorrisondev/qa-app) (remember to give it a star ⭐).

---

# How to set environment variables in Node.js
URL: https://clerk.com/blog/how-to-set-environment-variables-in-nodejs.md
Date: 2024-12-27
Category: Company
Description: Explore the best practices and techniques you can use to set environment variables in Node.js, ensuring your app runs smoothly across different environments.

As software developers, we're constantly juggling the need for flexibility, scalability, and security in our applications.

One important aspect of achieving this balance is the proper use of environment variables. In this post, you'll learn various ways to set environment variables in Node.js, exploring best practices for their usage, as well as discussing key considerations for maintaining the security and integrity of your application. From validating environment variables at startup to avoiding committing sensitive files with Git, we'll cover essential guidelines for making the most of environment variables while minimizing potential risks.

Whether you're a seasoned developer or just starting out with Node.js, this piece aims to provide valuable insights and actionable advice for ensuring your applications are secure, maintainable, and scalable.

## What are environment variables?

[Environment variables](/glossary#environment-variables) are key-value pairs stored outside your codebase, often in a configuration file or system settings. They hold data like API keys, database credentials, or other environment-specific settings. This ensures sensitive information is not hardcoded into your application, keeping it secure and easier to manage across different environments, such as development, testing, and production.

In Node.js development, environment variables are an important concept to understand because they allow you to configure your application dynamically without modifying the code. For example, you can use the same codebase but point to different databases or APIs depending on whether you’re in a development or production environment. This flexibility improves security, simplifies deployment, and makes your app more adaptable.

Unlike regular JavaScript variables, environment variables are typically not defined in your code. Instead, they are stored in the operating system or external files (like `.env`) and accessed using `process.env`. Regular variables are scoped to your application, while environment variables exist independently and can influence multiple applications on the same system.

## Accessing environment variables in Node.js

In Node.js, the `process.env` object is used to access and manage environment variables. To retrieve a variable, you reference it using `process.env.VARIABLE_NAME`. For example, `process.env.API_KEY` fetches the value of `API_KEY`. You can technically set the value of an environment variable within the code, but this is generally counterproductive and defeats the purpose of using environment variables.

The following snippet shows how the `API_KEY` would be referenced in an Express API:

```ts
const express = require('express')
const app = express()

// Access API key from environment variables
const apiKey = process.env.API_KEY

if (!apiKey) {
  console.error('Error: API key is not defined.')
  process.exit(1)
}

app.get('/', (req, res) => {
  res.send('API key is successfully loaded.')
})

// Start the server
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`)
})
```

## Setting environment variables in Node.js

Now that you understand what environment variables are used for and how to access them, let's dive into the various ways to set them.

### 1. Using `dotenv`

One popular method for setting environment variables is the `dotenv` package. This package allows you to separate your environment-specific variables from your code and load them easily into your application.

You can specify the key-value pairs in a `.env` file like so:

```makefile
PORT=3000
DB_USERNAME=dbuser

```

You can then import the package and run the `config()` function which will import the values from `.env` by default:

```ts
import * as dotenv from 'dotenv'
dotenv.config()
```

Then, in your Node.js code, you can access these variables using the `process.env` object:

```ts
console.log(process.env.PORT) // Output: 3000
console.log(process.env.DB_USERNAME) // Output: dbuser
```

You can also load different environment variable files using the `path` parameter:

```ts
dotenv.config({ path: './path/to/another.env' })
```

While `dotenv` is very useful for development, other methods should likely be considered for running in a production environment.

### 2. Setting on the system level

On a Unix-based system (such as Linux or macOS), you can set environment variables at the system level by adding them to your shell configuration file such as  `~/.bashrc` for Bash or  `~/.zshrc` for ZShell. This will apply to all processes running in that shell session.

For example, add the following lines to your `~/.bashrc` file:

```{{ filename: '~/.bashrc' }}
export PORT=3000
export DB_USERNAME=myuser

```

Then, restart your terminal or run `source ~/.bashrc` to apply the changes. You can then access these variables in your Node.js application using the `process.env` object.

If you are running your application as a system process, the same values can be added to `/etc/environment` for global system access.

### 3. Setting in a launch script

You can also set environment variables at launch time by creating a script that sets the variables and then runs your Node.js application. For example, you can create a `launch.sh` file:

```bash
#!/bin/bash
export PORT=3000
export DB_USERNAME=myuser
node app.js
```

Make the script executable with `chmod +x launch.sh`, then run it using `./launch.sh`. This will set the specified environment variables and then run your Node.js application.

### 4. Setting in PM2

PM2 (Process Manager 2) is a popular process manager for Node.js applications. You can set environment variables when starting your application with PM2:

```bash
pm2 start app.js --env PORT=3000 --env DB_USERNAME=myuser
```

This will set the `PORT` and `DB_USERNAME` environment variables when running your application. You can also use a configuration file named `ecosystem.config.js` and specify values for different environments:

```js {{ filaname: 'ecosystem.config.js' }}
module.exports = {
  apps: [
    {
      name: 'my-app',
      script: 'app.js',
      instances: 1,
      env: {
        NODE_ENV: 'development',
        API_KEY: 'dev-key',
      },
      env_production: {
        NODE_ENV: 'production',
        API_KEY: 'prod-key',
      },
    },
  ],
}
```

Then, you can specify the environment to use when starting PM2:

```bash
pm2 start ecosystem.config.js --env production

```

### 5. Setting in Docker

When running a Node.js application in a Docker container, there are multiple ways that environment variables can be set. When defining the `Dockerfile`, you can use the `ENV` keyword to specify default values for environment variables:

```
FROM node:22

WORKDIR /app

ENV PORT=3000
ENV DB_USERNAME=myuser

COPY package*.json ./

RUN npm install

COPY . .

CMD ["node", "app.js"]

```

This will set the environment variables when building and running your Docker image.

When running the container, you can specify them in the command to override the default values as well:

```bash
docker run -e PORT=5173 my-image

```

Finally, when using `docker-compose` to run containers, you can specify the environment variables in the YAML:

```yaml
version: '3.8'

services:
  app:
    image: node:22
    container_name: my-app
    environment:
      - PORT=5173
```

## Best practices for using environment variables in Node.js applications

When working with environment variables in your Node.js application, there are some best practices you can follow to ensure security, maintainability, and scalability. Here are some key guidelines:

### Use descriptive names and document their purpose

When naming your environment variables, use descriptive names that clearly indicate their purpose. It's also a good idea to mention them in the project's README file with a short description documenting what each variable is used form, making it easier for other developers (or you in the future) to understand.

### Validate environment variables on app startup

Before using any environment variables, make sure they are properly set by validating them during startup. This can be done by checking if the variable exists and has a value. If not, you can default to a specific value or throw an error.

For example:

```jsx
const env = process.env
if (!env.USERNAME) {
  throw new Error('USERNAME environment variable is required')
}
```

### Avoid committing `.env` files with Git

By default, `.env` files are committed to your project's version control system (such as Git). If you are using a `.env` file to store environment variables, make sure to exclude your VCS from tracking the file. In Git, it's as simple as adding the following line to your `.gitignore` file:

```{{ filename: '.gitignore' }}
build/
dist/
node_modules

.env

```

### Consider using a KMS to increase security

To further increase the security of your environment variables, consider using a Key Management System (KMS). A KMS provides an additional layer of protection by encrypting and securely storing your sensitive data. This is especially important if you're working with sensitive information such as API keys or encryption keys.

### Specify default values

When defining environment variables, it's a good idea to specify default values for those that may not be set. This ensures that your application will continue to function even if certain environment variables are missing or unset. The default values should not contain sensitive information though.

For example, storing the connection string for a hosted database can be considered a risk. However, assuming that the project setup involves running the database locally, specifying `localhost` for the connection string would not be risky.

### Never expose environment variables in the front end

Most frameworks and tools have naming conventions developers can use to inject environment variables in to the development and building process. For example, you can use the `NEXT_PUBLIC_` prefix and any client-side code will be able to access those variables.

One of the most critical best practices is to never expose sensitive environment variables directly to the front-end. API keys and database connection strings should only ever be accessible from the server. When building with front end frameworks, make sure you understand how to securely use environment variables to avoid leaking secrets.

## How Clerk uses environment variables in Node.js projects

Clerk offers various SDKs supporting different Node.js configurations, with [Express](/docs/quickstarts/express) being our latest.

We use [environment variables](/docs/deployments/clerk-environment-variables) to configure the SDK and associate it with an application in the Clerk dashboard, which also loads any configuration items from the dashboards and allows users of that application to authenticate:

```{{ filename: '.env' }}
CLERK_PUBLISHABLE_KEY=pk_test_YmlnLXdlYXNlbC00NC5jbGVyay5hY2NvdW50cy5kZXYk
CLERK_SECRET_KEY=sk_test_frHrr**************************************

```

This allows Node.js projects to make [backend requests](/docs/backend-requests/handling/nodejs) to Clerk, as well as [front end projects to use Express](/blog/securing-node-express-apis-clerk-react) for validating API requests.

## Conclusion

In today's interconnected world, keeping your application's secrets safe is more crucial than ever. Environment variables are a fundamental part of any modern software project, but they can also be a significant security risk if not handled properly.

In this article, we've explored the best practices for using environment variables in Node.js applications, from validating their existence to avoiding exposing sensitive information directly to the front end. By following these guidelines and being mindful of potential risks, you'll be well on your way to creating secure and maintainable software that's ready for production.

---

# Mitigating OAuth’s recently discovered Open Response Type vulnerability
URL: https://clerk.com/blog/open-response-type-vulnerability.md
Date: 2024-08-07
Category: Company
Description: How Clerk mitigated the recently discovered Open Response Type vulnerability

On July 29, security researchers at Salt [released details](https://salt.security/blog/over-1-million-websites-are-at-risk-of-sensitive-information-leakage---xss-is-dead-long-live-xss)
of an OAuth vulnerability that can reliably be chained with any XSS vulnerability
to establish a long-lived account takeover. This is a general OAuth vulnerability, not particular to Clerk’s implementation.

At Clerk, we believe it’s our responsibility to protect our customers from vulnerabilities
like this, so we worked quickly to understand the attack and devise a mitigation.
Fortunately, we discovered that **Clerk’s default configuration is not susceptible to this
chaining, and over 99.7% of our customers were already protected.** For the last 0.3%, we
released an update today that mitigates the attack.

In this post, we dive into the technical details of the vulnerability, why most customers were protected by default, and how we mitigated the attack for the last few.

## Understanding the Open Response Type vulnerability: a clever variation of the Open Redirect

### The OAuth happy path

The OAuth flow starts by redirecting a user to the “authorization URL” of an identity
provider, with `redirect_uri` and `response_type` in the query param, as well as an application identifier and the scope of authorization requested. Here’s a sample for Google:

```plaintext {{ mark: [2] }}
https://accounts.google.com/o/oauth2/auth
  ?redirect_uri=https%3A%2F%2Fmyapp.com%2Foauth_callback
  &response_type=code
  &client_id={oauth_client_id}
  &scope={requested_authorization_scopes}
```

On Google’s website, the user is asked to authorize the requested scope, which
normally only includes profile information so the user can be signedin.

After authorization, the user is redirected back to the `redirect_uri` using
the given `response_type`. In this case, the response type is `code`, which means
a single-use secret code is appended to the `redirect_uri` as a query param:

```
https://myapp.com/oauth_callback?code={secret_code}
```

### The Open Redirect vulnerability

An “Open Redirect” is when an attacker tricks a user into visiting an
authorization URL with a nefarious value passed to `redirect_uri`.
For example, instead of passing `https://myapp.com/oauth_callback`,
they can pass `https://attackapp.com/oauth_callback`. This accomplishes two things:

1. `https://myapp.com/oauth_callback` doesn’t receive the secret code,
   so its single-use nature does not provide any protection.
2. The attacker gets access to a secret code.  With it, they can open
   a new browser on their own machine, and navigate to the valid `redirect_uri`
   with the code appended. This tricks the application into granting a
   session for the victim to the attacker.

Thankfully, the OAuth specification guides implmentors on how to prevent Open Redirects, and Google and
other identity providers maintain a strict allowlist of acceptable `redirect_uri`s. The allowlist ensures that
the user, *and the secret code,* will not be sent to a URL the attacker controls.

### The Open Response Type vulnerability

In OAuth, the response type dictates the format and mechanism for how
the identity provider passes sensitive information back to the application.
With `code`, a single-use token is passed by query param.
With `token`, an access token is passed by the URL’s hash fragment.

Unlike `redirect_uri`s, the `response_type` parameter is not strictly
bound to a per-application allowlist. This lack of enforcement is pivotal
to the attack, which is why we’re calling it the “Open Response Type”
vulnerability. Here is [Salt’s explanation](https://salt.security/blog/over-1-million-websites-are-at-risk-of-sensitive-information-leakage---xss-is-dead-long-live-xss) of how the parameter was leveraged:

> Note that we changed the original OAuth link to Google - we used the response
> type “code,token” instead of only “code”, which makes Google send the code in
> the hash fragment (#code=...). This enables us to read the code from the URL
> and ensure Hotjar doesn’t consume the code, which can be consumed only once.

After changing the response type to `code,token`, the secret code is returned
in the fragment instead of the query param. In all likelihood, the application
implementing OAuth has not prepared for a secret code to be in the fragment,
so it doesn’t consume it, and it remains in the URL instead.

Put another way: the open nature of `response_type` means an attacker has a
reliable way to get a valid, unused secret code in the URL bar.

Thankfully, getting the code in the URL bar alone is not sufficient, since the
attacker also must be able to read its value. This is not normally possible,
but it is when an XSS vulnerability is present. Depending on the page the XSS
is present on, it may be as easy as reading `window.location.href`.
In Salt’s case, they opened a new tab to the Authorization URL via `window.open`,
then polled the tab’s `location` until the OAuth flow completed.

### The impact

You might be wondering: **Do we really need to worry about Open Response Type
if it needs to be chained with an XSS attack?**

The answer is **absolutely yes**. When managing user sessions on the web, it’s
critical to mitigate the effects of XSS as much as possible. After an XSS is
patched, it should be impossible for the attacker to continue acting on behalf
of any users.

This is why developers use the `HttpOnly` directive when managing session cookies:
it ensures that session tokens cannot be extracted for the attacker to continue
using after the XSS is patched. Without this feature, developers would need to
revoke sessions after an XSS attack and force users to sign in again.

The challenge with the Open Response Type vulnerability is that it completely
bypasses the protections afforded by `HttpOnly`. The attacker is able to generate
new sessions, and those sessions will continue to be valid after the XSS is patched.

## Mitigating the Open Response Type vulnerability

The Open Response Type vulnerability relies on two behaviors:

1. The user must land on a page with an unused code in the URL bar.
2. The application must have an open XSS vulnerability that allows them to
   programmatically read the URL bar.

Below, we list two techniques that can be used to suppress these behaviors.

### Mitigation 1: Process the OAuth code on a separate origin

At the top of this post, we mentioned that over 99.7% of Clerk customers
were protected by default. That is because Clerk forces developers to set
the OAuth `redirect_uri` to be an endpoint on our API, which is normally
hosted on a subdomain like `https://clerk.yourdomain.com`. When the Open
Response Type attack is attempted against a Clerk customer, the user would
land on URL on this subdomain, like:

```
https://clerk.yourdomain.com/v1/oauth_callback#code={secret_code}
```

Fortunately, this subdomain does not serve HTML, so it’s exceedingly unlikely
to be the source of an XSS. An XSS on any other subdomain cannot read the URL
of a tab on the Clerk subdomain, since it breaks the web’s cross-origin rules.

If your application also leverages a separate origin for processing the OAuth
code (e.g. `identity.yourdomain.com` or `auth.yourdomain.com`), it will also
be protected as long as that origin never introduces an XSS.

While this technique helps, it’s hard to guarantee that you will never introduce
an XSS on the origin processing the OAuth code. For that reason, we recommend
using Mitigation 2 instead.

### Mitigation 2: Eliminate unexpected fragments

Some Clerk customers use a reverse proxy to host our API directly on their
primary domain, so OAuth codes are not processed on a separate origin. For
these users, and for a more robust solution overall, we needed to find an
additional mitigation.

Our solution is to eliminate unexpected fragments from the URL bar before
an XSS vulnerability has the chance to read them. To do this, we’ve added an extra redirect to any OAuth failure that eliminates
the fragment.

Again, let’s consider a user that lands on this URL as a result of an Open
Response Type attack:

```
https://clerk.yourdomain.com/v1/oauth_callback#code={secret_code}
```

Fragments aren’t sent to the server, so our server cannot tell that `code`
is even present. But, this URL is expecting `code` in the query param, and
since it’s missing, Clerk is going to return an error.

Instead of simply returning the error, we are now issuing a redirect first.
We issue the redirect with status=301 and a Location header that has a
trailing #, which clears any fragment that might exist:

```
Location: https://clerk.yourdomain.com/v1/oauth_callback#
```

Since we use Clerk at Clerk, you can confirm the behavior by visiting the
URL on our domain with a fake code – the code will be stripped away.
(You’ll notice we add an extra query param to prevent an infinite redirect.)

```
https://clerk.clerk.com/v1/oauth_callback#code=fakerandomcode
```

This behavior is easy to implement in your own OAuth handler and will
effectively protect you against Open Response Type attacks.

---

# Welcoming Colin from Zod as our inaugural Open Source Fellow
URL: https://clerk.com/blog/zod-fellowship.md
Date: 2024-06-11
Category: Company
Description: Clerk is funding the development of Zod 4 with a new Open Source Fellowship program.

Today, we are delighted to announce the launch of our Open Source Fellowship program, created to foster continued innovation in the open source software community. Our inaugural recipient is [Colin McDonnell](https://x.com/colinhacks), the creator of [Zod](https://zod.dev).

With nearly 10 million weekly npm downloads, Zod is an extremely widely-used TypeScript schema declaration and validation library. It should come as no surprise that we use it to help build Clerk, and so we're thrilled to be playing a minor role in its continued evolution.

Specifically, Clerk is sponsoring the development of Zod 4, providing financial support for Colin to dedicate a few months to this next version. In connection with this funding, Colin has agreed to help increase visibility of Clerk to Zod's many users, by sharing our brand within its documentation, readme, RFCs, and his public profile on X.

For more details from Colin's perspective, including more details about the enhancements coming in Zod 4, we encourage reading [Zod's announcement](https://zod.dev/blog/clerk-fellowship). We are enthusiastically aligned with Colin's vision for new approaches to funding open source, including his transparency and candor about the terms of the fellowship.

---

# Comparing Authentication in React.js vs. Next.js
URL: https://clerk.com/blog/comparing-authentication-react-nextjs.md
Date: 2024-03-15
Category: Company
Description: We compare authentication in React.js and Next.js, emphasizing the ease of securing user data with Clerk.

Authentication in React and Next.js is a critical topic. This tutorial will compare authentication in [React.js](/react-authentication), known for dynamic interfaces, and [Next.js](/nextjs-authentication), a framework that optimizes React for production. We'll cover the importance of authentication for securing user data and access to protected components, and dive into the practical differences between using React and Next.js for this purpose.

Additionally, we'll discuss how Clerk can simplify the authentication process with framework-specific solutions for both [React](/react-authentication) and [Next.js](/nextjs-authentication). For practical experience, we've provided a [code repository on GitHub](https://github.com/theodesp/react-vs-nextjs-auth) with examples to deepen your understanding of authentication in React and Next.js.

Let's get started.

## Core Differences between React and Next.js

Let's delve into the real differences between React and Next.js when it comes to authentication and how they affect our decision-making process.

### Server-side rendering vs client-side rendering

React.js is a library for creating user interfaces on web and native platforms, as described in its official documentation. It's primarily a client-side tool for developing UI applications, where UI refers to the interface on the user's device or browser for interacting with websites or applications.

React.js doesn't have built-in server-side rendering capabilities, limiting its function to client-side rendering. However, future updates, such as React Server Component, aim to improve server-side support, though this involves complex nuances.

On the other hand, Next.js is a React framework that enhances React.js with additional features and capabilities. Next.js is a full-stack framework with features including:

- Server-side Rendering (SSR): Where pages are generated on the server and sent to the client.
- React Server Component support (RSC)
- Routing: A Built-in routing system.
- Static Export (SSG): Pre-rendering pages at build time to serve static HTML.
- Automatic Build Size Optimization: Optimizes build sizes automatically.
- Incremental Static Regeneration: Re-rendering static pages on-demand without rebuilding the entire site.

Even when utilizing Next.js, which facilitates server-side rendering with React, developers encounter restrictions on certain authentication operations. We'll expand deeper into these limitations as we examine the actual code examples.

### React Routing capabilities

React.js does not handle page routing for you. It’s up to the developer to choose how to navigate through pages or redirects in a concise manner. That's why developers opt-in to use a third party tool like [React-router](https://reactrouter.com/en/main) or [TanStack Router](https://tanstack.com/router/latest) to satisfy their needs.

Next.js on the other hand has routing and it’s fixed or built-in with no option to override it at least on the server side. As a matter of fact, Next.js currently supports two routing methods:

**Pages** creates routes within the pages folder.

**App Router** organizes routes within the app folder.

Although both routing options can be used together, they fundamentally work differently so they are not compatible with each other without certain modifications.

## Example: How to Implement Authentication in React

We now take a closer look on how to Implement Authentication in React from scratch. You can work your way through this section of the tutorial with the code examples as located in the react-auth folder.

### Overview of client-side authentication

Focusing on React as a client-side library, our overview will center on creating authentication components like login and logout forms and managing the visibility of sensitive information.

Client-side authentication primarily involves showing or hiding content based on a user's authentication status. Unauthenticated users are redirected to a [login page](/blog/building-a-react-login-page-template) to enter credentials for accessing protected routes. This common approach in applications is standard and typically trouble-free.

However, it involves intricate technicalities that are not apparent to the end user. Developers must handle authentication securely and efficiently on the client side, including in React applications. We will outline key considerations for client-side authentication before diving into coding specifics.

### Considerations for client-side authentication

When adopting authentication solutions for your client-side app, the first step is defining criteria for identifying users. Additionally, you need to consider the following:

**Security Concerns**

To ensure security, it's essential to manage the generation, storage, and retrieval of Personally Identifiable Information (PII), session identifiers, or tokens that could be misused for user impersonation. Given that sensitive information is handled client-side, it's critical to mitigate potential security risks effectively.

**State Management**

If you store session keys in the user's device or browser, you must establish how to manage them in the application's state. This involves determining the appropriate data structure for storing authentication tokens or session information, implementing mechanisms to synchronize state changes across components, and handling authentication-related events such as login and logout actions.

In many cases, React should be used as a thin client aiming to retain minimal data. The responsibility for managing session revalidation or token updates, in accordance with the security protocols of the organization, is typically delegated to the server, which instructs the client accordingly.

Considering the above parameters, let's explore in practice how to implement these considerations in React and how easy it is to miss important details. The code for the whole tutorial section is located in the `react-auth` folder of the repo.

### Example of setting up authentication in React

Let’s show now how to set up authentication in React. We first initiate a new React Project using [Vite](https://vitejs.dev) since `create-react-app` is deprecated:

```bash
$ npm init @vitejs/app react-auth --template react-ts
$ cd react-auth
```

#### Step 1: Setup the main App Router page

We'll use React Router to manage navigation within our application. If you haven't already installed React Router, you can do so using npm:

```bash
$ npm install react-router-dom
```

Next, let's define the main application router in our `App.tsx` file:

```tsx {{ title: 'App.tsx' }}
import React from 'react'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
import Login from './Login'
import Profile from './Profile'
import AuthProvider from './providers/AuthProvider'
import Protected from './components/Protected'

function App() {
  return (
    <div className="App">
      <Router>
        <AuthProvider>
          <Routes>
            <Route path="/login" element={<Login />} />
            <Route element={<Protected />}>
              <Route path="/profile" element={<Profile />} />
            </Route>
          </Routes>
        </AuthProvider>
      </Router>
    </div>
  )
}
export default App
```

Inside the `<AuthProvider />` component, we define routes for the login page (`/login`) and the user profile page (`/profile`). The `<Protected/>` component ensures that the `/profile` route is only accessible to authenticated users. If a user tries to access the profile page without authentication, they will be redirected to the login page.

Since we haven’t defined what the `AuthProvider`, `Protected` or `Login` components do yet let's proceed to explore the client side components next.

#### Step 2: Define the Login Component

Let's examine the structure and style of the login page component, which includes a form for users to input their credentials for authentication.:

```tsx {{ title: 'Login.tsx' }}
import React, { useState } from 'react'
import { useAuth } from './hooks/useAuth'
import './Login.css'
const Login: React.FC = () => {
  const [credentials, setCredentials] = useState({ username: '', password: '' })
  const auth = useAuth()
  const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (formValid(credentials)) {
      await auth.loginAction(credentials)
    }
  }

  const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target
    setCredentials((prev) => ({
      ...prev,
      [name]: value,
    }))
  }

  const formValid = (state: typeof credentials) => {
    return state.username && state.password
  }

  return (
    <div className="login-container">
      <form onSubmit={handleLogin} className="login-form">
        <input
          type="text"
          placeholder="Username"
          name="username"
          value={credentials.username}
          onChange={handleInput}
        />
        <input
          type="password"
          placeholder="Password"
          name="password"
          value={credentials.password}
          onChange={handleInput}
        />
        <button className="login-button" type="submit">
          Login
        </button>
      </form>
    </div>
  )
}
export default Login
```

In the Login Page, we use  the `useState` hook to manage the state of the input fields (credentials). The `handleLogin` function is called when the form is submitted, and it triggers the login action by calling the `loginAction` function from the `useAuth` hook. The `handleInput` function updates the state as the user types in the input fields.

#### Step 3: Define the AuthProvider and Custom Hooks

Now that we have our login page component ready, let's implement the authentication logic using the `AuthProvider` and custom hooks. The `AuthProvider` component will manage the authentication state and provide authentication-related functions to its child components using React context:

```tsx {{ title: 'AuthProvider.tsx' }}
import React, { createContext, PropsWithChildren, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import useToken from '../hooks/useToken'
import appConfig from '../app.config'

interface UserData {
  username: string
}

export interface Credentials {
  username: string
  password: string
}

interface LoginData {
  token: string
  user: UserData
}

interface AuthContextType {
  token: string | null
  user: UserData | null
  loginAction: (data: Credentials) => void
  logoutAction: () => void
}

export const AuthContext = createContext<AuthContextType | undefined>(undefined)

const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
  const [user, setUser] = useState<UserData | null>(null)
  const { token, setToken } = useToken()
  const navigate = useNavigate()

  const loginAction = async (credentials: Credentials) => {
    try {
      // Mocking authentication request
      const response = await fetch(`${appConfig.AUTH_API_URL}/login`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(credentials),
      })
      if (!response.ok) {
        throw new Error('Invalid credentials')
      }
      const data: LoginData = await response.json()
      setUser(data.user)
      setToken(data.token)
      navigate('/profile')
    } catch (error) {
      console.error('Login error:', error)
    }
  }

  const logoutAction = () => {
    setUser(null)
    setToken(null)
    navigate('/login')
  }

  const authContextValue: AuthContextType = {
    token,
    user,
    loginAction,
    logoutAction,
  }

  return <AuthContext.Provider value={authContextValue}>{children}</AuthContext.Provider>
}
export default AuthProvider
```

Here the `loginAction` function handles user authentication by sending a POST request to the server with the user's credentials. Upon successful authentication, it updates the user state with the authenticated user data and stores the authentication token in local storage. It then navigates the user to the profile page.

The `logoutAction` function clears the user state and removes the authentication token from local storage, effectively logging out the user and redirecting them to the login page.

The `useAuth` and `useToken` hooks are  shown next:

```tsx {{ title: 'useAuth.ts' }}
import { useContext } from 'react'
import { AuthContext } from '../providers/AuthProvider'

export const useAuth = () => {
  const authContext = useContext(AuthContext)
  if (!authContext) {
    throw new Error(
      'useAuth hook was called outside of context, make sure your app is wrapped with AuthProvider',
    )
  }
  return authContext
}
```

The `useAuth` hook allows components to access the authentication context provided by the AuthProvider.

Next, let's define the `useToken` hook:

```tsx {{ title: 'useToken.ts' }}
import { useState } from 'react'

const ACCESS_TOKEN_KEY = 'access_token'
export default function useToken() {
  const getToken = () => {
    const token = localStorage.getItem(ACCESS_TOKEN_KEY)
    return token
  }

  const [token, setToken] = useState<string | null>(getToken())

  const saveToken = (data: string | null) => {
    if (!data) {
      localStorage.removeItem(ACCESS_TOKEN_KEY)
    } else {
      localStorage.setItem(ACCESS_TOKEN_KEY, data)
      setToken(data)
    }
  }

  return {
    token,
    setToken: saveToken,
  }
}
```

The `useToken` hook provides a simple interface for managing token storage and retrieval using local storage.

Tokens facilitate authenticated server requests, but relying solely on token authentication has limitations and security risks. Simple token systems often lack secure methods to refresh or revoke tokens, exposing applications to unauthorized access if tokens are compromised. Moreover, storing tokens in local storage makes them vulnerable to cross-site scripting (XSS) attacks, where malicious scripts could steal tokens, risking security breaches.

Serving this API should be done over HTTPS to ensure there are no Man In The Middle attacks (MitM) that could compromise the token value.

Setting those arguments aside, the last component is the `Protected` component that ensures certain routes are only accessible to authenticated users. This component will act as a guard, preventing unauthorized access to protected routes by redirecting unauthenticated users to the login page:

```tsx {{ title: 'Protected.tsx' }}
import React from 'react'
import { Navigate, Outlet } from 'react-router-dom'
import { useAuth } from '../hooks/useAuth'

const Protected: React.FC = () => {
  const { token } = useAuth()

  // If the user is not authenticated, redirect to the login page
  if (!token) {
    return <Navigate to="/login" />
  }

  // If the user is authenticated, render the child routes
  return <Outlet />
}
export default Protected
```

The `Protected` component utilizes the `useAuth` hook to tap into the authentication context and obtain the authentication token. If authentication is confirmed (indicated by the presence of the token), the Protected component displays the child routes. This setup permits the rendering of nested routes within protected areas for authenticated users.

#### Step 4: Implementing authentication endpoints on the server

Nevertheless you still need to have a server to handle client-side authentication with React. In our case, since we are building things from scratch we will have to consider the simplest option.

Here is what the server code looks like:

```tsx {{ title: 'auth-server.js' }}
import express from 'express'
import bodyParser from 'body-parser'
const app = express()
const PORT = 5000
// Middleware to parse JSON request bodies
app.use(bodyParser.json())

// Mock user data
const users = [
  { username: 'theo', password: 'password1', user: 'Theo' },
  { username: 'alex123', password: 'password2', user: 'Alex' },
]
// Authentication endpoint: POST /auth/login
app.post('/auth/login', (req, res) => {
  const { username, password } = req.body
  const user = users.find((u) => u.username === username && u.password === password)
  if (!user) {
    return res.status(401).json({ message: 'Invalid username or password' })
  }
  // Generate authentication token
  const token = generateToken(user)
  res.json({ token, user: user.user })
})
function generateToken(user) {
  return `generated-token-for-${user.username}`
}

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`)
})
```

This server sets up a route : `/auth/login` for user authentication. It uses a simple array of user objects as a mock user database and a fake token generator for generating an access token.

Once you have our server set up, we can proceed in testing the whole authentication workflow.

#### Step 5: Running the Development Environment

To locally test our authentication setup, we must run the Vite development server for the React application alongside the mock auth server. This can be done by inserting a custom script into our `package.json` file.

First, let's install the required dependencies:

```bash
$ npm install --save-dev concurrently
```

Next, let's update our `package.json` file with a script that starts both the Vite development server and the mock authentication server:

```json {{ title: 'package.json', prettier: false }}
"scripts": {
  "start": "concurrently \"vite\" \"node auth-server.js\""
}
```

Running `npm start` will simultaneously initiate the Vite development server and the authentication server, enabling local testing of our authentication setup as if it were on a live server.

That required significant effort! We've merely begun with a basic setup that doesn't cover user sessions or storing user information in a database—both crucial for production-level authentication.

Next, we'll explore how to enhance our approach with a Next.js implementation.

## Example: How to Implement Authentication in Next.js

In this part, we'll delve into how to incorporate authentication in a Next.js application using NextAuth.js. This package simplifies adding authentication to Next.js projects by providing ready-made support for well-known providers such as Google, GitHub, and Facebook, reducing the manual configuration often seen in React authentication.

Next.js's API routes feature further streamlines this process, allowing us to manage authentication logic directly within our project without a separate server. This enhances our solution's maintainability. The complete code for this tutorial can be found in the `nextjs-auth` folder of the repository.

#### Step 1: Install NextAuth.js

First, let's install Next with NextAuth.js and its dependencies:

```bash
$ npx create-next-app@latest nextjs-auth
$ cd nextjs-auth
$ npm install next-auth@beta @auth/core
```

#### Step 2: Configure NextAuth.js

Next, create a new file named `auth.ts` somewhere in your application. This file will handle authentication requests and configurations. In this tutorial, we are using GitHub as the authentication provider so you need to make sure you register a [new GitHub App](https://github.com/settings/apps/new) to get the provider credentials. Also make sure to set up the correct callback URL. Here is mine for reference:

![NextAuth GitHub configuration](./github.png)

*Setting up a new GitHub App*

```tsx {{ title: 'lib/auth.ts' }}
import NextAuth from 'next-auth'
import Github from '@auth/core/providers/github'
export const {
  handlers: { GET, POST },
  auth,
  signIn,
  signOut,
  unstable_update,
} = NextAuth({
  debug: process.env.NODE_ENV === 'development',
  secret: process.env.NEXTAUTH_SECRET,
  session: {
    strategy: 'jwt',
  },
  providers: [
    GitHub({
      clientId: process.env.GITHUB_ID || '',
      clientSecret: process.env.GITHUB_SECRET || '',
    }),
  ],
})
```

Then export the auth handlers to the  `app/api/auth/[...nextauth]/route.ts` as well. This will handle all the API requests for the `next-auth` package and any configured OAuth callbacks.

```tsx
export { GET, POST } from '@/lib/auth'
```

#### Step 3: Use the provided auth functions to enforce authentication

The `auth` function effectively fetches the current user session. If the user is unauthenticated, it returns null, allowing us to check and redirect as necessary.

Below is an example of implementing protected routes using this approach:

```tsx {{ title: 'pages/api/protected.ts' }}
import { auth } from '@/lib/auth'
export default auth((req) => {
  if (req.auth) {
    return Response.json({ data: 'Success' })
  }
  return Response.json({ message: 'Not authenticated' }, { status: 401 })
})
```

And here is how to protect pages:

```tsx {{ title: 'app/profile/page.tsx' }}
import { redirect } from 'next/navigation'
import { authOptions } from '@/lib/auth'
export default async function Page() {
  const session = await auth()
  if (!session) {
    redirect('/login')
  }
  return <pre>{JSON.stringify(session, null, 2)}</pre>
}
```

#### Step 4: Setup the Login page

To log in using GitHub or any other configured provider, utilize the `signIn` function exported from `auth.ts`.

```tsx {{ title: '(auth)/login/page.tsx' }}
import { Login } from '@/components/Login'
import { auth } from '@/lib/auth'
import { redirect } from 'next/navigation'

export default async function LoginPage() {
  const session = await auth()
  if (!session?.user) return <Login provider="github" />
  redirect('/profile')
}
```

The `Login` component interacts with server actions as follows:

```tsx {{ title: 'components/Login.tsx' }}
import { signIn } from '@/lib/auth'
export function Login({ provider, ...props }: { provider?: string }) {
  return (
    <form
      action={async () => {
        'use server'
        await signIn(provider)
      }}
    >
      <button {...props}>Login</button>
    </form>
  )
}
```

#### Step 5: Testing the login with GitHub flow

The final step involves testing the entire authentication process with GitHub. Start by navigating to the `/login` page to view the main `Login` button. Upon clicking the `Login` Button, you'll be redirected to GitHub, where you can complete the authentication login process.

Integrating [authentication with Next.js](/nextjs-authentication) and the NextAuth package marked a substantial improvement over the React example. Yet, configuring it revealed certain complexities.

Primarily, the [NextAuth documentation](https://next-auth.js.org/getting-started/introduction) focuses on authentication setup for the pages router, necessitating a visit to the beta documentation for `app` folder insights, leading to discrepancies in examples.

Moreover, not configuring the `secret` and `session` strategy for the GitHub provider in the NextAuth configuration led to difficult-to-diagnose 500 errors. This indicates that developers must still dedicate significant effort to achieve a smooth authentication process with Next.js. The final part of this tutorial will explore whether a more streamlined approach exists.

## How do React and Next.js Authentication differ from each other?

This guide highlights that both React and Next.js offer flexibility in authentication options without enforcing a specific approach, leaving the decision to developers. However, implementing client-side authentication with React still requires a server.

Below is a table illustrating the key differences between authentication in React and Next.js:

| Aspect                   | React.js      | Next.js                                                                                 |
| ------------------------ | ------------- | --------------------------------------------------------------------------------------- |
| Authentication Libraries | External Only | External Only                                                                           |
| Session Management       | No            | [Yes](https://clerk.com/blog/complete-guide-session-management-nextjs)                  |
| Server Side Code         | No            | Yes                                                                                     |
| Middleware support       | No            | [Yes](https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime) |

While Next.js offers somewhat better support for authentication compared to React, leveraging external solutions for authentication and authorization remains essential. These solutions must be compatible with Next.js's newest features, like the App router, for smooth integration and to fully utilize the framework's capabilities.

Considering the effort involved, would you prefer a more straightforward approach? Explore how Clerk simplifies authentication challenges for both React and Next.js.

## Simplifying Authentication with Clerk

This part of the tutorial is brief, thanks to Clerk's streamlined solution for handling authentication in React and Next.js, app-router support included.

You just need to follow these four simple steps to set it up!

### Clerk with React

Clerk offers a dedicated library for this purpose, and you can easily follow the steps outlined in their [quickstart guide](https://clerk.com/docs/quickstarts/react) to get started.

### Clerk with Next.js

**Step 1: Install Clerk**

```bash
npm install @clerk/nextjs
```

**Step 2: Setup Clerk Secret Keys**

```env {{ title: '.env.local' }}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=<CLERK_PUBLISHABLE_KEY>
CLERK_SECRET_KEY=<CLERK_SECRET_KEY>
```

**Step 3: Setup the ClerkProvider on the root component**

Add the `<ClerkProvider/>` on the root layout component:

```tsx {{ title: 'app/layout.tsx' }}
import { ClerkProvider } from '@clerk/nextjs'
import './globals.css'
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}
```

Then configure the `authMiddleware` to protect routes:

```tsx {{ title: 'middleware.ts' }}
import { authMiddleware } from '@clerk/nextjs'
export default authMiddleware({
  publicRoutes: ['/'],
})
export const config = {
  matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api)(.*)'],
}
```

**Step 4: Utilize the relevant Authentication components where you see fit**

Here is a full example of using `<UserButton />` which allows users to manage their account information and log out:

```tsx {{ title: 'app/page.tsx' }}
import { SignedIn, SignedOut, SignInButton, UserButton } from '@clerk/nextjs'
function Header() {
  return (
    <header
      style={{
        display: 'flex',
        justifyContent: 'space-between',
        padding: '10px',
      }}
    >
      <h1>My App</h1>
      <SignedIn>
        <UserButton afterSignOutUrl="/" />
      </SignedIn>
      <SignedOut>
        <SignInButton />
      </SignedOut>
    </header>
  )
}
export default function Home() {
  return (
    <main>
      <Header />
    </main>
  )
}
```

We didn't encounter any difficulties implementing essential user authentication features like login and logout with Clerk. Clerk simplifies the management of authentication state and sessions, making it straightforward and efficient.

This concise tutorial demonstrated Clerk's ability to effortlessly manage authentication in both React.js and Next.js, positioning it as the ideal solution for authentication requirements.

If Clerk's features have caught your attention, we recommend trying it as your primary authentication solution. For additional tutorials and resources, check out our [docs](https://clerk.com/docs). Sign up for the [free plan](https://clerk.com/pricing) to explore everything Clerk provides.

---

# Introducing Webhook Workflows with Inngest & Svix
URL: https://clerk.com/blog/clerk-inngest-svix-webhooks.md
Date: 2024-01-24
Category: Company
Description: We are excited to announce that Clerk has teamed up with Inngest and Svix to integrate with external systems reliably.

In this post we will dive into how the collaboration between Clerk, [Inngest](https://www.inngest.com), and [Svix](https://www.svix.com) enhances your authentication workflows using Clerk’s new Inngest webhook transformation template.

## Building with Clerk Webhooks

Clerk webhooks (powered by Svix) allow you to receive event notifications from Clerk. When a `user.created` event is triggered, you may want to:

- Sync user data to your database.
- Kick off an account provisioning workflow.
- Start a trial in Stripe.
- Send a welcome email or start a drip campaign.
- Add the user to your product newsletter in Mailchimp.

Today, with Clerk’s new Inngest webhook transformation template, you can easily use Clerk webhook events to trigger Inngest functions.

Inngest is a reliability layer for your application. Using it comes with a few key benefits such as [managing concurrency](https://www.inngest.com/docs/guides/concurrency), [automatic retries](https://www.inngest.com/docs/functions/retries), [parallel execution](https://www.inngest.com/docs/guides/fan-out-jobs), [complex workflows](https://www.inngest.com/docs/reference/functions/step-run), or [task scheduling](https://www.inngest.com/docs/reference/functions/step-sleep). This approach eliminates concerns about operating and scaling webhooks or error handling.

## See it in Action

The code below features the `inngest.createFunction` method, which inserts a new user into the database. It will be triggered whenever a `clerk/user.created` event occurs.

```javascript
const syncUser = inngest.createFunction(
  { id: 'sync-user-from-clerk' },
  { event: 'clerk/user.created' }, // ← This is the function's triggering event
  async ({ event }) => {
    const user = event.data // The event payload's data will be the Clerk User json object
    const { id, first_name, last_name } = user
    const email = user.email_addresses.find((e) => e.id === user.primary_email_address_id).email
    await database.users.insert({ id, email, first_name, last_name })
  },
)
```

You can also have multiple functions react to the same event. The code below uses `step.sleep` to send a welcome email, then wait for three days, then follow up with a message offering a free trial:

```javascript
const sendOnboardingEmails = inngest.createFunction(
  { id: 'onboarding-emails' },
  { event: 'clerk/user.created' },
  async ({ event, step }) => {
    // ← step is available in the handler's arguments
    const { user } = event.data
    const { first_name } = user
    const email = user.email_addresses.find((e) => e.id === user.primary_email_address_id).email

    await step.run('welcome-email', async () => {
      await emails.sendWelcomeEmail({ email, first_name })
    })

    // wait 3 days before second email
    await step.sleep('wait-3-days', '3 days')

    await step.run('trial-offer-email', async () => {
      await emails.sendTrialOfferEmail({ email, first_name })
    })
  },
)
```

To learn how to integrate Inngest into your workflows, check out [Clerk's official integration guide](/docs/integrations/webhooks/inngest) or refer to [Inngest documentation](https://www.inngest.com/docs).

## **Onward and Upward**

As we continue to relentlessly improve the Developer Experience of our products, we are excited to see what developers build using the Inngest integration.

---

# Clerk raises $30M Series B from CRV and Stripe
URL: https://clerk.com/blog/series-b.md
Date: 2024-01-18
Category: Company
Description: New funding will accelerate expansion beyond authentication, into authorization

We’re excited to share that Clerk has raised $30M in Series B funding led by [CRV](https://www.crv.com), with participation from [Stripe](https://stripe.com), and existing investors [Andreessen Horowitz](https://a16z.com) and [Madrona](https://www.madrona.com). We will use this capital to expand our service beyond *authentication*, which identifies who a user is, and into *authorization*, which determines the permissions a user has.

To build our authorization service, we are partnering with Stripe to tightly integrate Stripe Billing. Together, we will create the easiest way to subscribe users to plans and grant access to features based on the plan that was purchased — all with no webhooks required.

> “The `<SignIn/>` and `<UserProfile/>` components from Clerk have already set a new standard in authentication, and proven that components are the new APIs. We’re thrilled to support Clerk as they apply their solutions to the authorization market.” - Reid Christian, General Partner at CRV

Our Series B was led by Reid Christian at CRV, who we’ve been trading notes with for years. Reid and CRV are no strangers to modern developer tools, having invested in Vercel in 2015, during the earliest days of Next.js. We’re thrilled to be working with him for this next phase of Clerk and beyond.

Of course, none of this would be possible without our foundations in authentication, which we’ve been bolstering since 2019. Thanks to the dedication of our team, Clerk has experienced tremendous growth since our [Series A](/blog/series-a) last year, and is now managing over 16 million end-users! And, we’re facilitating over one new sign-up per second across verticals like SaaS, fintech, e-commerce, and more.

Our mission is to simplify the way you keep your users and their data safe, without forcing you to sacrifice high-quality user experience. We’ve been stalwart in our commitment to that mission for authentication, and you can be confident we will maintain our principles as we expand into authorization.

If you want to get in touch, find us on X at [@clerk](https://x.com/clerk). On behalf of the entire Clerk team, thank you so much for your continued support. We can’t wait to continue this journey with all of you, and to show you what we’re building next!

---

# Clerk in 2023: A Year in Review
URL: https://clerk.com/blog/clerk-2023-year-review.md
Date: 2024-01-04
Category: Company
Description: Wow! We had an incredible year in 2023, here is a list of everything we shipped...

This was a exciting year for Clerk, packed with new features and a growing community around Clerk’s user authentication platform. Let's rewind and celebrate some of the milestones that marked our journey!

## January

![Clerk 2023 Year Review tutorial illustration](./95c27737706e26d7bb2a24cd81b1dee3046269d8-2400x1260.png)

### Enhanced DX & Security

As part of Clerk’s mission to revolutionize DX, the focus was on removing roadblocks and streamlining workflows. To enhance security, Clerk implemented several key improvements, including eliminating third-party cookies, which can be used for malicious acts such as cross site scripting. Additionally, Clerk improved API key management for stronger access control; all of this means developers can build more secure applications with ease, using Clerk.

### Upgraded Dashboard & Documentation

Clerk made it easier for developers to find answers and manage their Clerk integrations with a more intuitive interface and comprehensive documentation.

### Gatsby SDK & Community RedwoodJS SDK

Expanded Clerk's compatibility with popular web development frameworks, making it even easier for developers to integrate user authentication into their projects. The major thing to highlight here is Clerk’s growing community and passion for enabling Clerk to work with the tools they love! Learn more in this [changelog post](/changelog/2023-01-27#gatsby-v-5).

### The Popular Starter Template t3-turbo-and-clerk Powered by Clerk

[This open-source template](https://github.com/clerk/t3-turbo-and-clerk) showcases the power of Clerk for building web and mobile apps with robust authentication.

## February

### Improved Next.js Middleware Integration

Clerk’s [new Middleware](/changelog/2023-02-10#next-js-middleware-protection-strategy) allowed for seamless integration with the popular Next.js framework, allowing developers to easily [add user authentication to your Next.js applications](/nextjs-authentication).

### Fastify & RedwoodJS v4 Integrations

Further broadened Clerk's reach by offering official integrations with [Fastify](/docs/quickstarts/fastify#use-clerk-with-fastify) and [RedwoodJS](/docs/quickstarts/redwood#use-clerk-with-redwood-js) frameworks, empowering developers to choose the tools they love.

### Documentation Overhaul

Revamped Clerk's documentation with better organization, search functionality, performance, and authoring experience, making it easier for developers of all levels to learn and use Clerk.

## March

![Clerk 2023 Year Review tutorial illustration](./2d1af91c70bf05d829a3d890a0db78bc4c95b8fd-2400x1260.png)

### $15M Series A Led by Madrona & Clerk.com Domain!

This significant investment and domain move solidified Clerk's position as a leading authentication provider and fueled Clerk’s continued growth. Read more about our Series A in this [announcement post](/blog/series-a).

### SDK Performance Boost through Lazy Loading

Optimized performance by ensuring Clerk's pre-built components only load when needed, preventing initial render time delays and ensuring a smooth user experience.

### Dedicated Chrome Extensions SDK

Opened up possibilities for secure user authentication in the booming Chrome extension market. Check out the [starter repo](https://github.com/clerk/clerk-chrome-extension-starter) on GitHub.

### T3 Stack Tutorial by Theo Browne Showcases Clerk, Vercel & PlanetScale

The [T3 Stack Tutorial](https://www.youtube.com/watch?v=YkOSUVzOAA4\&t) highlighted the seamless integration of Clerk with other popular tools like Vercel and PlanetScale, demonstrating how developers can build powerful and scalable applications with a unified tech stack.

## April

### Clerk Becomes an Identity Provider

Clerk IdP enables large enterprise companies juggling Authentication between all their vendors to create a nexus through Clerk for Single Sign-On functionality.

### Improved Phone & Email Input, Customizable Hosted Pages through Dashboard

Enhanced the user experience with more intuitive input fields and the ability to personalize the look and feel of login and signup pages, boosting brand consistency.

### Full Expo 48 Support

Made Clerk a perfect fit for mobile developers using Expo, a popular framework for building cross-platform apps, and streamlined the creation of custom login flows within Expo apps. Learn more in [this changelog entry](/changelog/2023-04-07#expo-48-support) or explore our [Expo authentication](/expo-authentication) solution.

## May

![Clerk 2023 Year Review tutorial illustration](./50919943d3ffac47193796b588967026f710ae57-2400x1260.png)

### Next.js 13.4 & App Router Support

Clerk swiftly followed the release of Next.js 13.4 and App Router stable by [offering full support](/blog/nextjs-13-4) on May 5th. This made Clerk one of the first development tools and the first authentication provider to fully leverage the power of Next.js, React Server Components, and Edge middleware.

### Enhanced Password Security & UX

May saw a [significant upgrade to password functionalities](/blog/a-new-password-experience) in Clerk applications. Setting and resetting passwords became smooth and user-friendly, while breach detection and complexity tests ensured stronger security.

Developers gained complete control over password security through the Dashboard, allowing them to tailor password policies and requirements to their specific needs.

### Stepping into the Public Spotlight

Clerk's presence in the community exploded in May. We transitioned from sporadic YouTube mentions to sponsoring and presenting at major conferences.

CEO Colin Sidoti's [keynote at Reactathon](https://www.youtube.com/watch?v=enUuBY3HXh4), advocating for the power of well-designed components, and VP of Engineering Sokratis Vidros's talk at CityJS on [building for JavaScript edge runtimes](https://www.youtube.com/watch?v=_ypxvJ0oU-Y), solidified Clerk's position as a thought leader in the industry.

### "How We Roll" Launch

May also marked the launch of the ["How We Roll" blog series](/blog/how-we-roll-passwords), offering a behind-the-scenes look at Clerk's technology and how it delivers a seamless user experience.

### UX Tweaks & Optimization

Recognizing the power of well-crafted UI components, Clerk focused on subtle but impactful UX improvements. Highly customizable user avatars with hover effects and subsequent image size optimizations are prime examples of this dedication to user experience.

## June

### SAML

In June, [SAML support](/docs/authentication/enterprise-connections/overview) was released to public beta, allowing companies to leverage Clerk to seamlessly integrate their internal tools and frontends with their SAML services. Just a few projects leveraging Clerk’s SAML offering include, [Airflip](https://www.airflip.com) a modern tool for procurement teams, [Lawhive](https://lawhive.co.uk) a powerful platform for finding litigators in the U.K., and [Casa](https://clibrain.com) a LLM specialized in usage in Spanish.

### Bot Detection

Clerk’s ownership of the authentication flows allows [enhanced bot detection](/changelog/2023-07-07#enhanced-bot-detection-for-ui-components). Clerk’s hosted pages were retrofitted with layers of bot detection capabilities in June. Clerk also enabled self-service user delete, an important user privacy feature that every app collecting user data should implement.

## July

### Bot Detection in the JavaScript SDK

Clerk’s bot detection capabilities were quickly brought to the JavaScript SDK, including the pre-built components and hooks, ensuring that you can be much more confident about your users not being bots. This capability is a must-have for applications providing free trial or usage credits, user generated content, or social interactions, and comes with Clerk out-of-the-box with no additional configuration required.

### API Improvements for Sorting Users & Organizations

The ownership of user management also comes with a responsibility - user data being stored with Clerk should not become a bottleneck when developing applications, and developers should be able to query the data however they want. July saw improvements to the APIs for querying users and organizations with [advanced filtering and sorting capabilities](/changelog/2023-07-21#filtering-and-sorting-for-users-organizations-members), and better customization of the session token with custom user data.

## August

### Detection for Disposable Emails & Subaddressing

The defenses put in place to fight the bots got even better in August, when Clerk added [detection for disposable emails and subaddressing](/docs/authentication/configuration/restrictions#block-email-subaddresses). These capabilities make Clerk especially useful to AI products for which bots can be very costly.

### Reverse Proxy Support

Another key infrastructure upgrade quickly followed - support for [proxying the requests to Clerk’s Frontend API](/docs/advanced-usage/using-proxies#proxying-the-clerk-frontend-api) through a reverse proxy server!

The reverse proxy support unlocked an entire realm of capabilities, since your application can now be deployed on any domain (preview, staging, tenant subdomains etc) and have full access to authentication, as well as synchronization of signed in sessions.

### “How We Roll” Conclusion

The blog series “How We Roll” closed out with its [10th chapter](/blog/how-we-roll-roundup), rounding up prior chapters and summarizing how Clerk maximizes developer experience, application security, and user experience. The community made some amazing contributions to Clerk’s integrations ecosystem through a Vue, Elysia, and Rust integration, fully open source like all of Clerk’s integrations.

##

## September

![Clerk 2023 Year Review tutorial illustration](./df028ebcab1340139c60bf84ae8aa24b3125b9f7-2400x1260.png)

### Account Portal

September saw the release of Clerk’s [Account Portal](/docs/account-portal/getting-started#getting-started-with-the-account-portal), which replaced hosted pages as the fastest way to authenticate any application, by eliminating the need for the developer to build any authentication related UI at all. The Account Portal was a fresh redesign of the entire user experience, along with better control over the look and feel.

### Custom Domain Registration

Clerk’s B2B offerings continue to get crucial upgrades, like custom domain registration for organizations, allowing users to automatically join organization based on their work email instead of requiring an invite from the admins.

### Form Pre-Fill Ability

The pre-built components for sign in and sign up also received the [ability to pre-fill](/docs/account-portal/direct-links#prefill-sign-in-and-sign-up-fields) the form, making them a lot more flexible and elevating the user experience.

## October

![Clerk 2023 Year Review tutorial illustration](./34c514b138fe70e4ac08798abd3e0f84f691e262-2400x1379.png)

### Changelog & Roadmap

This is getting to be a lot to keep track of, right? We agree, and in October we addressed this through the new changelog and roadmap.

The new [changelog page](/changelog) on [clerk.com](https://clerk.com) provides a one stop hub for all the important releases and announcements, while the [roadmap](https://feedback.clerk.com/roadmap) provides a lot of insight into the ongoing efforts of our product teams and provide any feedback for a new feature or improvements.

### Custom Pages

We also want to make sure as developers scale their products and require user management capabilities that Clerk doesn’t provide, they don’t have to opt out of the pre built components and build completely new UI. [Custom pages](/changelog/2023-10-26) in user and organization profile take their extensibility to the next level.

## November

![Clerk 2023 Year Review tutorial illustration](./913027a390d70b194c9f1bce6fde26240efb031e-2400x1260.png)

### Pricing Overhaul

We want the experience of Clerk to be available to everyone, including developers who don’t have paying users. We solidified our dedication towards the affordability of Clerk with a massive pricing overhaul, which came with 10,000 free MAUs for everyone, with the user’s first day free, and paid add-ons for the advanced authentication, administration, and B2B features. Read our [announcement post](/blog/new-pricing-plans) for further details.

### Hono Adapter

Applications built using Hono, a powerful and lightweight web server known for its compatibility with edge runtimes, also got access to Clerk’s authentication features with an [official adapter](https://github.com/honojs/middleware/tree/main/packages/clerk-auth)!

## December

### Account Lockout

[Account Lockout](/changelog/2023-12-01) is a Clerk feature that enables you to protect your users from brute-force attacks on static credentials such as passwords or backup codes. When enabled it tracks all the attempts and locks down the account after 100, the default attempts, it’s locked for an hour!

### Custom Roles & Permissions

One of our most sought after features of the year, custom roles and permissions, dropped in December, just in time for the holidays. Companies were able to simplify and improve their UX around roles, permissions and access provided to their end users.

As a part of this addition, we've added helper functions and components – `has()`, `protect()` and `<Protect />`. To learn more, read our [announcement post](/blog/introducing-authorization).

### FAPI Reference Docs

Our [Frontend API documentation](/docs/reference/frontend-api) is back and better than ever!

### Fetch User By Activity

Another drop was a new endpoint that allows insight into when a user was last active, this is great for all kinds of UX or admin dashboard implementations! Check out the [changelog entry](/changelog/2023-12-07) to learn more.

## That's A Wrap!

Let's reach new heights in 2024 with Clerk, don't stop the auth conversation! Dive into the nitty-gritty with our technical wizards on [Discord](https://clerk.com/discord). Share your ideas, get expert advice, and join the Clerk community building the future of User Management. Plus, stay in the know with [@clerk](https://x.com/clerk) on X for all the latest releases and sneak peeks. Your seamless journey starts here!

---

# Updated Pricing: 10,000 MAUs Free, and a new “Pro Plan”
URL: https://clerk.com/blog/new-pricing-plans.md
Date: 2023-11-30
Category: Company
Description: Introducing NEW pricing for Clerk – a Pro Plan with additional features, Pro Add-Ons to fit your application's use case, and 10,000 MAUs free on all plans

We’re thrilled to introduce a simplified pricing structure that’s not only easier to understand, but also significantly cheaper for most applications currently using Clerk. **Every application gets 10,000 free monthly active users (MAUs)!**

Brand new users will also now receive their **“First Day Free,”** which means their activity is not counted until 24 hours after signing up. This ensures you are not charged for users who sign up, but churn within their first day and do not return.

Since Clerk’s inception, our primary goal has been to lower the barrier to building production-grade applications. Every application deserves secure and seamless user management that takes minutes to setup, not days — freeing you to focus on what’s important: *your application*.

Existing customers will be grandfathered into their current plans, however, it may be cheaper to switch, so please
evaluate accordingly and reach out to us if you have any questions.

Our new simplified pricing structure consists of “Free”, “Pro” and “Enterprise” plans. Where each plan includes your first 10,000 MAUs free and you will never pay for an inactive user. Additional features can be added to the Pro plan via “Pro add-ons” tailor made for specific use cases.

## Free Plan – Up to 10,000 MAUs

Our free plan remains generous, with no features removed. You still get beautifully designed, performant, and customizable sign-in pages on the domain of your choosing. However, we’ve now doubled the included number of MAUs! Get started, and iterate longer — completely for free!

[Visit the pricing page to see all features in the Free plan](/pricing)

## Going Pro – A complete feature set for most use cases

The new Pro plan includes the first 10,000 MAUs, and starts at $25/mo. Additional MAUs are only $0.02 each. This is a dramatic simplification, and includes 10X more MAUs compared to the previous Hobby plan. The Pro plan also now includes the ability to set a “Custom Session Duration,” which has been a key sticking point in the past.

**Additional Pro plan features include:**

- Removing Clerk Branding
- Custom Session Duration *(previously in the Business plan)*
- Password Complexity Requirements
- User Ban/Unban
- Allowlist/Blocklist
- Multiple Domains
- SMS Authentication
- SOC2 Report on Request

[Visit the pricing page to see all features in the Pro plan](/pricing)

## Pro Add-Ons – Customize to fit your needs

While the Pro plan is designed for the majority of applications, add-ons are tailored to specific business needs and are priced according to their added value. Today we’re launching with 3 Pro add-ons that will become more feature-rich over time:

- **Enhanced Authentication** — includes MFA
- **Enhanced Organizations** — includes domain restrictions, and custom roles and permissions *(Stay tuned for this new feature* 😉)
- **Enhanced Administration** — includes user impersonation

[Visit the pricing page to see all features in each Pro add-on](/pricing)

## The Future

We know pricing changes lead to uncertainty and we’re committed to minimizing them. Our end-goal here is to continue to drop the price of authentication as we add value in adjacent areas. Building applications should be getting easier and cheaper over time, and this pricing change is just one step in the right direction. Clerk will always be committed to the developer community, and hopefully this pricing overhaul reflects that.

[We’re here if you have any questions](https://clerk.com/contact/support) — Happy building!

---

# How We Roll – Chapter 10: Roundup
URL: https://clerk.com/blog/how-we-roll-roundup.md
Date: 2023-08-11
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. This chapter provides a roundup of the topics discussed in this series.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, *roll*) authentication at Clerk.

## **Chapter 10: Roundup**

Clerk is more than just a set of authentication APIs - it’s a tool that offers a comprehensive solution for user management with **User Experience**, **Application Security**, and **Developer Experience** as the core tenets. The last nine chapters of How We Roll have been in-depth explorations into Clerk’s auth implementation. For this 10th chapter, we will round up all the topics described thus far and see how everything fits together through the lens of these three core tenets.

If this is your first time reading a post in our How We Roll series, this chapter will serve as an index to this series and the topics covered within. From here, you can choose which topics you want to learn more about and visit the chapters dedicated to those topics.

## User Experience

Let’s jump into the shoes of a user and experience Clerk from a user’s perspective.

Clerk’s embeddable components handle every authentication-related user interaction. This goes beyond sign-up, password reset, and OAuth flows – the User Profile is one of Clerk's most comprehensive authentication components. We believe that the user should have complete control over their authentication data and sessions, which is reflected in the self-service capabilities of the User Profile. Users can access and modify their profile, OAuth accounts, passwords, multi-factor steps, and active devices, all from a single `<UserProfile />` component.

[Learn how Clerk rolls the User Profile](/blog/how-we-roll-user-profile)

Every embeddable component provided by Clerk is also used on [dashboard.clerk.com](https://dashboard.clerk.com), which allows us to study and optimize every interaction in depth and offer the best possible out-of-the-box experience. However, we also acknowledge that every company and product must assert its brand identity on every user interface. This is reflected in the extensive customization options provided for Clerk’s components.

[Learn how Clerk rolls Customization](/blog/how-we-roll-customization)

Avatars are a critical component of user identity but are usually treated as an afterthought. Clerk brings a lot of care into ensuring that user avatars are high quality and are present throughout the authentication interactions to build a sense of user identity. Little details like the shimmer effect on hover and beautifully marbled default avatars elevate the user experience.

[Learn how Clerk rolls Avatars](/blog/how-we-roll-avatars)

## Application Security

While user experience is something we put a lot of effort into, the biggest priority for an auth provider like Clerk is application security.

The web has been slowly moving towards a passwordless application experience by embracing various forms of Single Sign-On. However, passwords are still the most familiar form of authentication. While over half of all signups happen through Single Sign-On providers like Google, Clerk ensures that the users and developers who prefer passwords are not under-served. Clerk’s default password requirements ensure that passwords are strong and compliant with NIST guidelines, implement countermeasures to detect breached passwords, and provide developers the flexibility to configure password rules.

![How We Roll Roundup guide illustration](./a43eae985fcc2f06e8c87db46569fa470ff85f50-2400x1260.png)

[Learn how Clerk rolls Passwords](/blog/how-we-roll-passwords)

Email verification is an essential piece of authentication. Password reset, account management, and team invitations require the presence of an email, and verification is necessary to mitigate spam or bot accounts. Clerk is configured by default to verify user emails before allowing sign-ups and employs both link-based and OTP-based verifications. For OAuth flows, if there is a question as to whether the OAuth provider verified the user’s email, Clerk requires an additional email verification step.

[Learn how Clerk rolls Email Verification](/blog/how-we-roll-email-verification)

Clerk’s implementation of Sessions enables various security capabilities. Clerk tracks the client devices used to sign in, along with the timestamps of the most recent sign-in and user activity, so users can quickly identify devices that are not supposed to be signed into their account and promptly sign out of them. Clerk also ensures that the application will never be in an invalid auth state (signed out, changed permissions, etc.) for more than 60 seconds through short-lived tokens.

![How We Roll Roundup guide illustration](./eefb254ce7a5410af709348cd9ee05250aa25cad-2400x1260.png)

[Learn how Clerk rolls Sessions](/blog/how-we-roll-sessions)

Clerk provides Multifactor Auth through SMS verification, Authenticator apps, and physical security keys for the more security-conscious folks. Users can choose to use their preferred factor for MFA within the options developers enable.

[Learn how Clerk rolls Multifactor](/blog/how-we-roll-multifactor)

## Developer Experience

Authentication should not be a hassle for developers to implement. Clerk makes it ridiculously easy for developers to get up and running with fully functioning auth.

A secure and scalable authentication infrastructure goes beyond simple client-server systems. Clerk’s robust infrastructure delivers the end-to-end authentication user experience and makes onboarding extremely easy for front-end developers. Clerk also eliminates specific configuration steps until developers are ready to release to production, ensuring absolute security across all authentication flows.

![How We Roll Roundup guide illustration](./145b05891db45736be24882aa1ac20a187db1fe6-1200x671.png)

[Learn how Clerk rolls Infrastructure](/blog/how-we-roll-infrastructure).

While Clerk’s SDKs provide easy integration with backend applications, developers might wish to leverage a Backend-as-a-Service provider like Supabase instead of building their own. Clerk also offers seamless integration with these providers with minimal effort from the developers through JWT Single Sign-On.

[Learn how Clerk rolls JWT SSO](/blog/how-we-roll-jwt-sso)

## Summary

The nine topics covered in this series are by no means an exhaustive list of how Clerk implements authentication. However, these topics demonstrate the care and effort put into making Clerk applications secure, easy to start, and a great user experience.

---

# How We Roll – Chapter 9: Infrastructure
URL: https://clerk.com/blog/how-we-roll-infrastructure.md
Date: 2023-08-04
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. This chapter covers the infrastructure that powers Clerk’s authentication capabilities.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, roll) authentication at Clerk.

## Chapter 9: Infrastructure

A system that provides secure authentication with a great user experience requires a lot of complex infrastructure. This goes beyond the typical client-server systems developers are familiar with and build.

- Sensitive data needs to be protected both in transmit and at rest
- Integrations with third party services need to be configured and orchestrated
- Work needs to be delegated to background to keep the application responsive
- Failures and timeouts need to be handled and observable to developers
- Operations need to be scalable to handle millions of users

In this chapter of How We Roll, we will explore the infrastructure that is required to build and operate an authentication system, and dive into the infrastructure that allows Clerk to offer an authentication experience that can be integrated into a project within minutes.

### Self-Hosted Auth

An application that implements auth directly or through imported libraries is self hosted, which means the developers have to manage the auth infrastructure on their own.

We can imagine an application with self-hosted auth that would look like this.

![How We Roll Infrastructure setup guide](./c8dbf4c3c324efaeef80269f07617b0418861410-1200x504.png)

The client would be a web, mobile, desktop, or console application that users interact with. The server provides the ability to sign in and check passwords, and the database is where the user data is stored securely.

The ability to send emails becomes a requirement very early, and is basically table-stakes for most consumer-facing applications. SMS is usually optional but is essential to many industries, and offering flexibility to the users to choose between email and phone is great experience.

![How We Roll Infrastructure setup guide](./5fefabb160b8972d644daa3ae2b5c793a8890727-1200x508.png)

This adds the infrastructure cost of either self-hosted servers or integrating with third-parties like [Sendgrid](https://sendgrid.com) and [Twilio](https://www.twilio.com).

Along with the development cost spent into the integration, sending email and text notifications is often high latency and failure-prone. If these operations are performed in the regular request-response cycle, it results in bad user experience since requests take too long to get a response.

![How We Roll Infrastructure setup guide](./832787cf55ca55b172cbe0da0c52ff262f687fe5-1200x554.png)

Delegating these operations to asynchronously-scheduled background jobs becomes necessary to ensure the application is snappy and responds to user interactions quickly. The jobs must ensure that all notifications are delivered, retried on failure, and offer developers observability into failures and bottlenecks. This can also be delegated to a provider like [Upstash](https://upstash.com), which again adds more cost of integration.

OAuth integration requires acquiring credentials and building adapters for each OAuth provider. The latter can usually be delegated to an open-source library like [NextAuth](https://next-auth.js.org).

This is not an exhaustive list. Some use-cases require additional infrastructure like SAML integration, blockchain adapters, compliance guarantees, or bot detection.

### Clerk-Hosted Auth

Clerk is designed to allow developers to build applications that look like this.

![How We Roll Infrastructure setup guide](./c07d33983423a94d4161a25b242ad4af255d3440-1200x339.png)

Clerk offers complete authentication capabilities directly on the client. Developers are not required to host a server to enable Clerk’s auth. This also allows Clerk to offer a great user experience and developer experience through pre-built components.

That said, many applications will have their own server and database. However, all auth-related responsibilities are delegated to Clerk, and Clerk’s backend package makes integration on the server easier and supports all non-node environments.

![How We Roll Infrastructure setup guide](./bb3661a1bd785eecaba37c2323a94239af4d73d3-1200x463.png)

But since the server is optional, developers can decide to not host one at all, and leverage a Backend-as-a-Service solution like [Firebase](https://firebase.com). Clerk also offers integrations to popular BaaS providers through [JWT Single Sign-On](/blog/how-we-roll-jwt-sso).

![How We Roll Infrastructure setup guide](./e53ed34009c8057fc66521edb69b09c7846222b3-1200x350.png)

Let’s zoom into the diagram and explore the infrastructure that powers the Client-Server integrations.

![How We Roll Infrastructure setup guide](./b9f130f6f7268d2037d6ff77cd3edf63b7dfaf8b-1200x630.png)

The [Javascript library](/docs/reference/clerkjs/installation) and [pre-built components](/docs/components/overview) interact with Clerk’s [Frontend API](/docs/reference/frontend-api), which is where the user signs in. For magic-link and OAuth based flows, the API redirects the user back to the Client after a sign in. Clerk is responsible for syncing the sign-in session with the Client.

The [Backend API](/docs/reference/backend-api) powers the [backend SDKs](/docs/references/javascript/overview) that authenticate client requests and access user data stored in Clerk’s database. The backend also delivers events to the application Server through [Webhooks](/docs/integrations/webhooks/overview) to enable asynchronous and event-based systems.

![How We Roll Infrastructure setup guide](./6e05d46aeec5d06570e9db1d684142c8cda6d648-1200x630.png)

Internally Clerk is split into the Frontend and Backend API services that run on the highly scalable and efficient runtime of [Google Cloud Run](https://cloud.google.com/run). The API services share access to the data, which is managed by [Google Cloud SQL](https://cloud.google.com/sql) and stored with security and integrity guarantees. Clerk also manages background jobs to delegate any non-critical work to. The background jobs also leverage Cloud Run and are configured with various priority levels, which are determined by the urgency of the operation being delegated (e.g. sending a message is high priority, cleanup tasks are low priority). This helps Clerk provide extremely responsive APIs, which is essential to ensure the authentication UX is fast and snappy.

![How We Roll Infrastructure setup guide](./32a8c39a636852fc2ee6866f4b28720d9b0e4ab9-1200x696.png)

Clerk also takes on the cost of integrating with other external services. SMS, Emails, and Webhooks are automatically delivered through [Sendgrid](https://sendgrid.com), [Twilio](https://www.twilio.com), and [Svix](https://www.svix.com) respectively, without any additional cost of integration. OAuth provisioning is also completely eliminated, as Clerk provides shared OAuth credentials and adapters for most popular OAuth services that can be enabled with a simple switch.

Since Clerk powers the complete end-to-end authentication flow, any additional infrastructure that might be required for special use-cases, like SAML, blockchain, or security compliance, can be added by Clerk without developers having to build any extra integrations. [A recent release](/blog/changelog-2023-07-07) of Clerk included built-in bot detection, that allows developers to build AI applications that offer free credit to new users, while Clerk ensures that malicious actors cannot spam create new accounts and abuse free credits.

![How We Roll Infrastructure setup guide](./12bacf949d2dd15dcdb8cd4acb5dcb238632efe5-1200x786.png)

Bot detection capabilities are enabled by a combination of Clerk’s in-house countermeasures and [Cloudflare](https://cloudflare.com), which are made available to Client applications out of the box.

### Bring your own Infra

Thanks to this infrastructure, getting up and running with fully functional auth in an application is a matter of minutes with Clerk. However, for various reasons, developers will prefer to leverage some of their own infrastructure. Clerk can be configured to not deliver messages if the developers wish to use their existing Email or SMS servers. Clerk sends webhook events for those messages to the application server, which can then integrate with self-provisioned Email and SMS servers to deliver the message to the user.

### Bring your own Configuration

Another contributor to Clerk’s quick onboarding experience is shared DNS and OAuth configurations.

![How We Roll Infrastructure setup guide](./9e7145464a1c7845cb548d9d7e621c4962f3d8c2-1200x630.png)

In a development instance, Clerk’s Frontend API is hosted on a subdomain of `accounts.dev`. This subdomain is a randomly generated slug unique to each application, e.g. `random-dove-45.accounts.dev`. The API is fully functional cross-origin, so the client application can be running on any domain, including `localhost` and [preview deployments](/docs/deployments/vercel), and will have full access to all of Clerk’s auth. Emails are automatically sent from an address like `no-reply@accounts.dev`, and OAuth flows use shared credentials.

While the cross-origin authentication capability contributes to the extremely quick onboarding DX of Clerk, it opens up vulnerabilities to certain cross-site scripting attacks, since sensitive authentication tokens are being shared across origins. This also means that [magic links](/blog/magic-links) and emails use a Clerk-owned `accounts.dev` URL instead of the application URL, which is a non-starter for production applications. Similarly, OAuth consents are provided to a third-party (Clerk) instead of to the application directly.

In a [production instance](/docs/deployments/overview), Clerk requires developers to add some DNS settings to their production domain, which allows Clerk to host the Frontend API at a subdomain of the application’s own origin. This eliminates cross-site vulnerabilities and guarantees absolute security of the application across all authentication flows. All links and email addresses also use the application’s domain so the risk of users mistaking

Clerk also requires production instances to have their own OAuth credentials so that all OAuth consents are provided directly to the application owner.

![How We Roll Infrastructure setup guide](./2e3b8c93ba1f2a09ed6364463caf39a8b54845f4-1200x630.png)

In addition to full security, hosting the API as a subdomain of the Client origin allows any other subdomain to access the same sign-in sessions as the primary Client. Users only need to sign-in once, and the session is automatically made available Clients on any subdomain by the Frontend API.

![How We Roll Infrastructure setup guide](./fa6fe5bacac768babc9a82ae80521aaeb3c9c1b3-1200x630.png)

## Summary

Clerk is designed not only to serve all the authentication needs of modern web applications, but to provide developers with an extremely easy onboarding experience along with the flexibility to turn off certain parts and integrate with their own systems. Clerk goes above and beyond with infrastructure to ensure that no compromises are made in terms of user and developer experience.

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# How We Roll – Chapter 8: Sessions
URL: https://clerk.com/blog/how-we-roll-sessions.md
Date: 2023-07-21
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. This chapter covers how Sessions unlock security and performance capabilities for Clerk.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, roll) authentication at Clerk.

## Chapter 8: Sessions

One of the primary objectives of Clerk is to ensure application security while providing the developer with ways to adjust it. Clerk’s auth is configured for a smooth user experience by default, but developers can enable additional layers of security like [multi-factor auth](/docs/authentication/custom-flows/multifactor) depending on the needs of the business.

Sessions is another tool in the authentication toolkit that allows fine-tuning of application security and user experience. A session is an abstract concept that refers to the period of time that a user is signed-in to an application. Like most abstract concepts, it can be modeled and designed to fit your needs.

Before describing Sessions, it’s helpful to grasp the idea of Clients.

### Clients

If a Session is the period of time a user is logged in, the Client is the device with which the user is logged in. Clients can be browsers, native applications, or any other medium that is usually the requesting part in a request/response architecture.

It’s also helpful to think of a Client as a running instance of ClerkJS. When the ClerkJS library is loaded into a browser by an application, it creates an active Client instance. A Clerk Client supports connecting to multiple sessions. This allows users to be logged in to multiple accounts at the same time and easily switch between them.

To learn more about multi-session handling [read the documentation](/docs/authentication/multi-session-applications).

Clerk stores information about the Client and the device it’s running on. This allows users to easily identify which devices they have logged in with and sign that device out of their account. This feature is crucial in case the device gets lost or stolen, or an account gets compromised.

![How We Roll Sessions guide illustration](./142db168fc61365864c470ef858a2001e034bc73-1818x1424.png)

### Creating a Session

To achieve the “Sign out of this device” functionality, Clerk stores the Session that is created when a user logs in. This Session object stores a reference to the Client that was used to create it. In the account security section, Clerk shows all the Sessions for the current user along with the Client information, and when the Session was created to further help identify what devices should and should not be signed in. Signing out from a Client is as simple as deleting the Session object.

Sessions have a maximum lifetime that can be adjusted on the Clerk Dashboard in the Sessions tab. If the application attempts to authenticate with an expired session, the authentication will fail and the user will have to log in again.

Upon sign-in, a reference to the created Session is stored in the `__client` cookie. This cookie is *Secure* and *HttpOnly*, so it’s inaccessible to [MitM](https://developer.mozilla.org/en-US/docs/Glossary/MitM) and [XSS](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss) attacks.

From here, this cookie can be used to authenticate with an application. When an HTTP request is received, the cookie can be used to fetch the Session object which has the user ID. If the Session doesn’t exist, the user has signed out of this Client, and the request would throw an Unauthorized error.

However, this process adds extra overhead to the request by introducing a database call. Usually developers attempt to mitigate this overhead by storing the Session object in memory, or in a low-latency store such as a Redis cache. This reduces the cost of fetching the Session object, but requires additional infrastructure to scale.

### Web APIs love JWTs

[JWTs](/blog/guide-JWT-authentication-JSON-Web-Tokens) were invented primarily to solve this additional overhead problem. Since JWTs can be verified to have been generated on a trusted server, the contents of the JWT can be trusted. This means Clerk can encode the Session object into a JWT, which the Client will use to authenticate with the application. When the application receives an HTTP request, the verified token can be used to access the Session object and process the request, instead of having to fetch the Session object from Clerk’s Backend API. The overhead required to verify a JWT is significantly lower than fetching data over a network. This Session token is stored in the `__session` cookie where it’s accessible to the Client. Alternatively, the Client can store this Session token in the browser’s local storage or a native equivalent, and pass it to the server through the Authorization HTTP header.

**JWKS refresher:** [JSON Web Key Set (JWKS)](https://medium.com/javarevisited/json-web-key-set-jwks-94dc26847a34) is a way for an application to advertise the public key that can be used to verify that a token was generated and signed by that application. Clerk applications expose their public keys at the `clerk.your-site.com/.well-known/jwks.json` endpoint.

When an API receives a request with a token, the JWKS is used to verify that the token was generated by Clerk. The API server fetches the JWKS at startup time and stores it in memory to save the network latency of fetching JWKS on demand. JSON Web Keys are stable and don’t need to be refreshed.

However, since the Session object is now stored on the Client that created it, only this Client is able to invalidate or terminate the session. This removes the ability to remotely sign out from a specific device. So while JWT Sessions solve the extra overhead, they disable a key security feature.

### It Always Comes Down To Cache Invalidation

Since the ability to remotely sign out of devices is a crucial feature, the source of truth has to be the Session object stored on the server. It cannot be the one stored on the Client. But fetching from the database is expensive. Whereas the JWT essentially acts as a cached version of the Session object that can never be programmatically invalidated. It’s the other extreme of the spectrum.

Fortunately, JWTs have a lifetime of their own and can be set to expire at some point in the future. When Clerk generates a Session token for a sign-in, the token is set to expire 60 seconds into the future. This Session token can be used to authenticate for the next 60 seconds. A database fetch for the Session is only required once the token has expired and a new token needs to be generated.

When a Session is deleted (user signs out of a device), new tokens cannot be generated, but the most recently generated token can still be used if it was generated less than 60 seconds ago. This guarantees that authentication states in an application will never be invalid for more than 60 seconds.

This approach allows Clerk to offer highly performant authentication without compromising on the security benefits of persistent Sessions.

### Inactivity

Generating short-lived tokens for a long-lived session comes with an additional benefit - it is a great indicator of user activity. If the user stops interacting with the application for more than 60 seconds, a new Session token is not generated. The creation time of the most recent session token can be used by Clerk to determine when the user was last *using* the application instead of just being logged in.

Clerk allows developers to enable Inactivity timeout - this is the maximum amount of time the user can be away from the application and come back to it without having to log in again. Inactivity timeout can also be set on the Clerk Dashboard in the Sessions tab.

### Dynamic Permissions

Sessions are not only created and destroyed - they can also be mutated within their lifetime. The most common use case for mutating Sessions is changing permissions or authorization policies. While a user is logged into the application, they might get added to or removed from organizations, or their roles within an organization could change.

Clerk saves this information in the Session object and ensures it’s updated so that new Session tokens can cache it and the application can process the request without making additional calls to Clerk.

Clerk also allows customization of the Session tokens. Developers can add additional claims to the Session token in the Clerk Dashboard. Shortcodes can be used to inject user data and metadata. In this example, a new claim `verified` is added to the token. Users with an unverified email address will have this claim set to `false` in the token. Once they verify their email, the next Session token that is generated will have this set to true, so any access rules depending on this claim will work as expected.

To learn more about Session token customization [read the documentation](/docs/backend-requests/making/custom-session-token).

## Summary

Sessions are a very powerful tool for enabling security capabilities like multi-sign-in, inactivity timeout, remote sign-out, and dynamic permissions. Clerk takes on the burden of implementing the complex mechanism of Session handling so that developers have access to a performant and flexible authentication system that is easy to integrate.

To learn more about the elements of session management and relevant security concerns, check out [this blog post](/blog/what-is-session-management).

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# How We Roll – Chapter 7: JWT Single Sign-On
URL: https://clerk.com/blog/how-we-roll-jwt-sso.md
Date: 2023-07-14
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. This chapter covers how Clerk integrates with BaaS providers with JWT SSO.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, roll) authentication at Clerk.

## Chapter 7: JWT Single Sign-On

Clerk offers [seamless integration](/docs/integration/overview) with Backend as a Service (BaaS) providers like [Firebase](https://firebase.google.com), [Supabase](https://supabase.com), and [Convex](https://www.convex.dev). BaaS providers enable developers to build applications quickly without worrying about the backend and infrastructure, similar to what Clerk does for auth. These providers abstract complex infrastructure logic and make it accessible to the front-end client through simple APIs and SDKs.

With that being said, any time you are accessing data from the client, there is a security concern; proper authentication and authorization is paramount, which is why these providers enable developers to configure security rules.

![How We Roll Jwt Sso guide illustration](./14852af290484142fa8bcd2f2d9db1925a8261cc-1346x344.png)

Clerk's officially supported integrations with these providers allow developers to seamlessly leverage Clerk for authentication in applications that depend on a BaaS. This is achieved through a concept called **JWT Single Sign-On**.

### Web APIs love JWTs

A JSON Web Token (JWT, pronounced *jot*) is a JSON string that is cryptographically signed and base64 encoded. Anyone can decode and read this key, but it can only be generated and verified using a secret key (or a private-public key pair). It’s like a passport that web apps create to give users access.

Claims are the credentials encoded in a JWT that are used to decide what resources the user has access to. If a JWT is a passport, claims are the contents of the passport, including your name, date of birth, passport number, and any stamps.

To learn more about JWTs, check out [this blog post](/blog/guide-JWT-authentication-JSON-Web-Tokens). Or implement JWT auth from scratch with [this blog post](/blog/adding-jwt-authentication-to-react).

### JWT Single Sign-On

JWT SSO is conceptually similar to OIDC or SAML Single Sign-On - it enables multiple services to use a single identity provider.

**SAML refresher:** Security Assertion Markup Language is an XML-based protocol that enables Single Sign-On, similar
to OIDC. SAML is used heavily in enterprise environments with a central auth system that issues XML tokens to
applications when a user needs to authenticate.

OIDC and SAML providers act as a source of identity for a user. They provide information about the user, like email address, name, avatar, and roles.

JWT SSO providers essentially share the logged-in session of the user with the services. They provide a token that represents not just the user, but the fact that the user *recently* signed in. If these tokens are short-lived, they can be regenerated, as long as the user is still logged in. JWT SSO allows the user to “be logged in” to multiple services (for example Clerk and Supabase) at the same time.

Clerk achieves this by generating a custom JWT for the signed-in user, which can then be used to make API requests to the provider. Developers can configure **JWT templates** in the Clerk Dashboard to generate these custom tokens.

```tsx
try {
  const token = await Clerk.session.getToken({ template: 'template_name' })
  // use token to call external service
} catch (e) {
  // handle error
}
```

### JWT Templates

To generate custom JWTs on the client, developers can configure a JWT template in the Clerk Dashboard. The template provides all the configurations necessary for JWT SSO to work with various providers.

These are some of the configuration options for JWT templates available on the Clerk Dashboard.

#### Token lifetime

Based on caching or security policies, developers can configure how long the token should be valid for. A long-lived token can be generated once, cached, and reused. A short-lived token has to be generated every time a request is made.

#### Custom signing key

Different providers have different ways to validate a JWT that they did not generate. For example, Firebase and Supabase require the tokens to be generated using their own secret. Developers can get this secret from the provider's dashboard, and configure the JWT template to use this secret key in the Clerk Dashboard.

When this token is generated and sent to the provider, the provider will be able to validate the token since it was generated using their key.

#### Issuer defined key

Other providers like Convex do not require using their own secrets. Instead, they can be configured with a public key or with **JSON Web Key Set** (JWKS).

A **JSON Web Key** is a public key that can be used to validate a token but cannot be used to generate one. An auth provider (issuer) like Clerk exposes its JSON Web Keys in the JWKS endpoint, which can be used by external providers to verify if a JWT was generated by Clerk. Similar to the JWT secret, the JWKS is unique to each application on Clerk.

Services like Convex allow developers to define an Issuer and a JWKS endpoint, which tells the service where to find the JWKS to validate the tokens. Both the Issuer URL and JWKS URL are available on the JWT Template configuration page on the Clerk DashboardTemplate page.

#### Custom Claims

Along with validating that the token was securely generated, data providers also need to know who the user is to check against security rules.

Most providers expect specific claims in the JWTs, optionally within a namespace. Clerk has pre-built JWT templates for popular services, and the templates are already configured with custom claims that the specific services look for. Within custom claims, handlebars-style templating can be used to include user information with shortcodes.

```json {{ title: 'JWT Template Custom Claims with Shortcodes' }}
{
  "aud": "convex",
  "name": "{{user.full_name}}",
  "nickname": "{{user.username}}",
  "picture": "{{user.image_url}}",
  "given_name": "{{user.first_name}}",
  "family_name": "{{user.last_name}}",
  "email": "{{user.primary_email_address}}",
  "phone_number": "{{user.primary_phone_number}}",
  "email_verified": "{{user.email_verified}}",
  "phone_number_verified": "{{user.phone_number_verified}}",
  "updated_at": "{{user.updated_at}}"
}
```

## Summary

Clerk strives to be a complete auth solution with an exceptional developer experience, and integrating with BaaS providers is an important part. Clerk’s JWT Single Sign-On enables this through session-sharing, or sharing the logged in state of the user with the BaaS providers, so developers can focus on building applications with minimal glue code or configuration required.

To learn more about the integrations supported by JWT SSO, check out our [Integration docs](/docs/integration/overview).

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# How We Roll – Chapter 6: User Profile
URL: https://clerk.com/blog/how-we-roll-user-profile.md
Date: 2023-06-23
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. This chapter explores how we help developers ship a fully-featured account management UI.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, roll) authentication at Clerk.

## Chapter 6: User Profile

Nothing is more frustrating than when a platform collects your information to create a profile for you, but doesn’t allow you to see what data you gave them, much less make any edits to your data without an extensive phone call or email chain. With that being said, building out an account management dashboard requires a good chunk of time to get right (think building out logic and UI components, in addition to the necessary security checks to protect users and their data), so it makes sense why some companies would push this feature off for later.

Our goal here at Clerk is to make it quick and easy for you to spin up a fully-authenticated, fully-featured application. This is why we make every effort to identify the most difficult, tedious, or time-consuming parts of standing up an authenticated app and package these features into components. One example of this is the `<UserProfile />` component, which allows you to stand up a fully featured account management UI just by dropping it into your code.

### Self service

Giving your users control over their accounts is great way to reduce support traffic and increase user satisfaction. Clerk’s `<UserProfile />` [component](/docs/component-reference/user-profile) gives your users autonomy over their data by allowing them to:

- Update their contact information, including name, email, and phone number
- Add additional login methods such as email, phone, and OAuth providers
- Change their password
- Add additional security measures like MFA or SMS OTP
- Delete their account

Implementing a self-serve feature may seem simple, but security must also be taken into consideration. For instance, a user should not be able to add an email without verifying that they own it. Additionally, the user should not be able to use the email until it has been verified. Finally, if the newly added email becomes the primary email address, the prior primary email should be alerted to the change.

We understand that this is a lot to build, which is why we baked these complexities into the `<UserProfile />` [component](/docs/component-reference/user-profile). It saves developers time and makes users happy, win/win!

### MFA / TOTP

Adding MFA to a user's account is a complex undertaking that involves the following steps:

1. Allow the user to choose which MFA option they want to use, such as application authentication
2. Enable the user to scan a QR code or manually type a code into their preferred MFA application
3. Require the user to provide a code provided by the MFA application or device
4. Provide backup codes in an easily accessible manner

When using Clerk’s `<UserProfile />` [component](/docs/component-reference/user-profile), developers can enable MFA / TOTP on with a single click in the dashboard. If you want to learn more about how Clerk handles MFA check out [chapter 3 of how we roll](/blog/how-we-roll-multifactor).

### Security

Our Clerk `<UserProfile/>` provides baked in security features such as listing all active devices, and the ability remotely log out a device if the user doesn’t recognize it.

![How We Roll User Profile guide illustration](./4258d0868dc77144b4d4cb9182f86801d31cd434-1826x1198.png)

This feature ensures that users can keep track of if there are any old or unknown devices with access to their account, but is also immensely helpful in keeping a user's data safe if one of their devices becomes lost or compromised.

### Summary

Account management is a critical and complex element of any application, and it doesn't have to be something you build from scratch. Clerk's `<UserProfile/>` component makes providing your users with a fully functional account management UI a matter of adding a few characters to your app, so you can focus on the core functionality of your application.

Check out the [Clerk Docs](/docs/component-reference/user-profile) to learn more about the `<UserProfile/>` component!

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# How We Roll – Chapter 5: Customization
URL: https://clerk.com/blog/how-we-roll-customization.md
Date: 2023-06-16
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. This chapter covers the various ways developers can customize Clerk's UI components.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, roll) authentication at Clerk.

## Chapter 5: Customization

When you build your brand, you spend hours making sure every color, font, and logo is exactly how you envisioned. When you integrate with a 3rd party product like Clerk, you want to make sure that your brand shines. Clerk empowers developers by offering:

- Highly customizable components
- Custom flows
- Themes for our hosted pages

In this chapter we are going to be talking about how to customize our drop-in components and why we wanted to treat it as a first class experience. Grab a drink and enjoy a deep dive into our `appearance` property.

### Clerk’s Appearance Property

When Clerk introduced customization, we wanted something that wasn’t an afterthought where only minimal items could be changed, like buttons or primary colors. We wanted developers to go as deep as required, and the appearance property allowed this by letting developers:

- Have access to entire themes, like dark mode, and the ability to create a custom theme
- Control the layout
- Have global control of the appearance
- Customize individual components
- Inherit from key branding like fonts, and element sizing
- Integrate with their current CSS method of choice

### Base Themes

The `baseTheme` allows a developer to create a theme for your brand, and can be reused across multiple projects. We provide a package called `@clerk/themes` that allows you to use prebuilt versions that were crafted by Clerk, like our dark theme or shades of purple:

```tsx
import { type AppType } from 'next/app'
import { ClerkProvider } from '@clerk/nextjs'

import { dark } from '@clerk/themes'

const MyApp: AppType = ({ Component, pageProps }) => {
  return (
    <ClerkProvider
      appearance={{
        baseTheme: dark,
      }}
      {...pageProps}
    >
      <Component {...pageProps} />
    </ClerkProvider>
  )
}
```

![How We Roll Customization guide illustration](./de3892f51d7ad303f57aea7dc00f088ab22cafbe-1200x630.png)

### Controlling the Layout

The default layouts for Clerk’s sign in and sign up components show the social buttons at the top, with other forms of authentication, like email, below. In similar fashion, when a user signs up for your application, by default, we show the optional fields. For some applications this makes sense, for others, not so much.

This is where our `layout` property can be used. Developers can move social providers, change the default look of the social buttons, hide optional fields, show links to help, privacy policy, and terms and conditions pages, and much more:

```jsx
<ClerkProvider
  appearance={{
    layout: {
      showOptionalFields: false,
      socialButtonsPlacement: 'bottom',
      socialButtonsVariant: 'iconButton',
      helpPageUrl: '/help',
      privacyPageUrl: '/privacy',
      termsPageUrl: '/terms',
    },
  }}
  {...pageProps}
>
  {/* ... */}
</ClerkProvider>
```

![How We Roll Customization guide illustration](./de36acd844d0309a5eb94a72f0916415763a7b51-1200x630.png)

### Global Customization

Not every application needs its own theme; it is possible to extend the Clerk `baseTheme` to fit with an application. Examples include globally setting some of the attributes, such as every `primaryButton` element having a green background and the text inside being yellow, or setting all text colors to the company’s brand color.

All of this can be done at the `ClerkProvider` level, allowing developers to speed up the customization of our components:

```jsx
<ClerkProvider
  appearance={{
    elements: {
      formButtonPrimary: {
        backgroundColor: 'green',
        color: 'yellow',
      },
    },
    variables: {
      colorText: 'red',
    },
  }}
  {...pageProps}
>
  {/* ... */}
</ClerkProvider>
```

![How We Roll Customization guide illustration](./799b8b17147919db39fba9c505f8a8b73847dec8-1200x630.png)

### Individual Components

Applications are built of components, and individual components may need to be styled in a specific way. For example the Clerk `<UserButton />` might be too small for your sidebar or navigation bar, or the `<UserProfile/>` may not need to show the sidebar.

You can apply the `appearance` property directly to individual Clerk components:

```jsx
import { UserProfile } from '@clerk/nextjs'

export default function Profile() {
  return (
    <div>
      <UserProfile
        appearance={{
          elements: {
            navbar: {
              display: 'none',
            },
          },
        }}
      />
    </div>
  )
}
```

![How We Roll Customization guide illustration](./12a76fb56aa7791c8bb545574e5ad0fdff4e9893-1200x630.png)

### Elements and Variables

We’ve talked a lot about the parts that make Clerk customization a first class experience, but the powerful parts are variables and elements. Whether you are globally styling or styling individual components, variables and elements are essential.

The `variables` property is used to adjust the general styles of the base theme, like colors, backgrounds, typography:

```tsx
import { ClerkProvider, SignIn } from '@clerk/nextjs'
import type { AppProps } from 'next/app'

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ClerkProvider
      {...pageProps}
      appearance={{
        variables: {
          colorPrimary: 'red',
          colorText: 'white',
        },
      }}
    >
      <Component {...pageProps} />
    </ClerkProvider>
  )
}

export default MyApp
```

The `elements` property is used for fine-grained theme overrides, and is useful when styling specific HTML elements. You can find the element to target by inspecting the HTML and looking for the class name.

## Summary

Clerk allows for extensive customization when integrating with third-party products, ensuring brand elements are upheld. Developers can customize individual components, control the layout, modify the global appearance, create custom themes, and adjust the style of base themes through the `appearance`, `baseTheme`, `layout`, `variables`, and `elements` properties, ensuring the brand's unique design and user experience are reflected in every interaction.

For an in-depth look at how to leverage the full power of customization with Clerk, check out the Component Customization [docs](/docs/component-customization/appearance-prop).

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# How We Roll – Chapter 4: Email Verification
URL: https://clerk.com/blog/how-we-roll-email-verification.md
Date: 2023-06-09
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. From codes to links to SSO, this chapter is about when and how we verify emails.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, *roll*) authentication at Clerk.

## Chapter 4: Email Verification

Email verification is a foundational and reusable piece of modern authentication:

- **It’s used during sign-up:** usually to prevent spam accounts, or as a requirement before enrolling a user on a mailing list.
- **It’s used during sign-in:** as a workaround to forgotten password, or as an alternative to passwords altogether.
- **It’s used during account management:** as a mechanism for adding new emails or changing the email for an account.

Although many think of verification links or one-time passcodes (OTPs) as the dominant verification mechanism, both approaches have fallen out-of-favor.

Today, the fastest and most common way to verify emails is actually single-sign on (SSO)! Social SSO (like “Sign in with Google”) and enterprise SSO (like Okta) are already responsible for over half of email verifications, and their adoption continues to grow every year.

This chapter discusses when and how to verify emails, and the tradeoffs of each option. Learn how Clerk handles email verification!

### When to verify

There are two options for when to require email verification: immediately at sign-up, or sometime after completing the signup process.

The most popular approach is to verify immediately at sign-up, which ensures that users will have a valid email, and helps mitigate spam or bot accounts.

In some cases, developers might want to allow signing up without email verification. This is usually done to give users access to some features immediately or for a limited time without jumping through an extra step, also called the foot-in-the-door strategy, while still keeping the bulk of the application behind an email verification check.

To achieve this, developers can turn off verify at sign-up through the Clerk dashboard, then check for email verification in the application to allow access to certain features. The email verification information is available on the [Clerk `User` object](/docs/reference/clerkjs/user) which can be fetched using [the `useUser` hook](/docs/reference/clerk-react/useuser) on the client, or [the `users.getUser` method](/docs/references/backend/user/get-user) on the node SDK.

### How to verify

The core verification mechanism is to send a unique token to the user’s email address. The user will then give this token to the application, verifying that the user owns that email address.

We offer both verification links and one-time passcodes. Developers can enable one of them to enforce that strategy, or enable both and let the users choose during verification.

### One time codes

The more straightforward way to implement verification is with a randomly generated 6-digit code that the user manually types into the application.

Code verification offers a benefit over links - users don’t need to be signed in to their email account to access the application on a device. Users could be using a different browser or a shared or public device that they don’t want to log into their email account on. This is also the reason why codes are seen a lot more for phone verification.

### Links

Clerk will generate a unique link with a signed JWT when using verification links. This link takes the user to the application where Clerk validates the JWT and signs in the user.

The JWT carries the signup attempt information and is only valid for 10 minutes. If the signup was successful, clicking the link again shows this message instead of signing the user in again.

![How We Roll Email Verification guide illustration](./aa54595c3d97f0d6a8c3076695da5ca706e21083-944x734.png)

Verification links offer a streamlined user experience, but it’s not without tradeoffs. Security-conscious individuals and email clients are suspicious of links in emails, as they could be malicious phishing links or download harmful files. Security policies and training often teach us to be careful with links in emails.

While we offer email verification links, for the aforementioned reasons, we recommend that developers use verification codes in most cases.

### Single Sign-on

As mentioned earlier, Single Sign-On with social providers like Google and enterprise providers like Okta is actually responsible for the majority of email verifications. We ensure that the user experience of Single Sign-On is seamless across various providers with no compromises to security.

Clerk’s SSO uses the Open ID Connect protocol (OIDC), which provides an `email_verified` claim. Identity Providers are supposed to set this to `true` if they have verified the user’s email. Almost every SSO provider verifies emails, so if a user signs up with a social provider, a second email verification step is unnecessary.

**OIDC refresher:** OpenID Connect is an identity protocol built on top of the OAuth 2.0 framework. If a request scope
of OIDC is defined when initiating an OAuth flow, identity providers present the application an ID token along with an
access token on a successful sign-in. The ID token is a JWT that contains some user information like name, email, and
avatar, in the form of “claims”.

Some providers do not verify emails immediately. So if a user signs up with SSO and their email is not verified by the provider, Clerk enforces an additional email verification step.

Some providers do not adhere to the OIDC spec. We audit all providers we support to ensure they are adhering to the spec, and if not, what their own format looks like. For example, the ID token from one of the social providers contains a `verified` claim instead of `email_verified` as defined in the spec, so we check for that. If the provider does not tell us whether the email is verified or not, we assume it’s not verified and force an additional email verification step.

Ensuring SSO providers have verified the user’s email is critical. Clerk provides account linking out of the box, which means users can sign in to the same account using multiple SSO providers as long as the email is the same. However, unless every SSO login verifies the emails, the account could be left vulnerable to takeovers. A malicious user could create a new account on a service that does not verify emails immediately, and use it to log in to an application as someone else. We are very careful with auditing every provider we work with to make sure developers don’t have to worry about the nuances across various providers.

### Summary

Email verification may seem like a straightforward task, but it is a common authentication process on the web that significantly affects both the user experience and application security. Clerk provides multiple options out of the box for developers to customize when and how they enforce email verification.

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# How We Roll – Chapter 3: Multifactor
URL: https://clerk.com/blog/how-we-roll-multifactor.md
Date: 2023-06-02
Category: Company
Description: How We Roll is a deep-dive into how Clerk implements authentication. This chapter is on something you know and something you have: multifactor authentication!

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, *roll*) authentication at Clerk.

## Chapter 3: Multifactor

Multifactor authentication, or MFA, is the process of authenticating users in multiple different ways before granting access to their account.

It goes by many names. Technologists seem to have settled on \_multi\_factor authentication because, well, why limit ourselves to two? But *two* factor authentication is still common and the phrase is used interchangeably.

Within consumer products, though, we've seen the rise of two-*step* *verification:*

![Chapter 3: Multifactor screenshot](./53eba8b30ad84278ea863251368d8d3995ca372d-1458x430.png "Google calls the feature \"2-Step Verification\" or 2SV")

Both word substitutions make the concept a little easier to digest, so Clerk has also adopted this language for `<UserProfile/>` component.

Enough about names, though... How We Roll is for technical details, and naming is the easy part! (Hah.)

Just like [passwords](/blog/how-we-roll-passwords), best practices for multifactor authentication have been learned through a history trial-and-error. Let's see how Clerk handles it all!

## Self-serve, out of the box

Clerk provides multifactor authentication out of the box, which means it requires **zero** additional work. How so? The `<UserProfile/>` component includes a self-serve flow for users to configure multifactor authentication:

Users can choose between one-time passwords sent by SMS (SMS OTP) or time-based one-time passwords (TOTP). Once configured, they also receive backup codes in the event that access to their other factor is lost.

And `<UserProfile/>` works in tandem with `<SignIn/>`, so the next time a user signs in, they will be prompted to authenticate with their second factor. No custom code required!

If preferred, Clerk also provides `useUser()` and `useSignIn()` hooks, which enable developers to build custom flows for configuring multifactor authentication.

## Something you know, something you have

For multifactor authentication, Clerk mandates that each step satisfies a different *factor*, or type of evidence, for authentication. The two factors we offer are:

1. **Something you know –** also called a knowledge factor, is proof that you know something that only you should know, like passwords, security questions, and pins. By extension, it also includes access to another account which is presumed to require a password (like an email account to receive a one-time password, or a Google account).
2. **Something you have –** also called a possession factor, is proof that you are holding a physical device. SMS and time-based one-time passwords are both examples, as are newer strategies like passkeys.

It is critical for Clerk to use one example from each bucket. A one-time password sent by email (email OTP) can be a replacement for a password, but a SMS OTP cannot.

Similarly, if a user with multifactor authentication forgets their password, the "forgot password" flow cannot be used to bypass the second factor. Instead, after the user verifies their email, they will still be asked to provide a possession factor.

If a user with multifactor authentication completely loses access to either factor, Clerk cannot directly help that user regain access to their account. Instead, an application administrator can help facilitate account recovery through the Clerk dashboard.

## Mitigate SIM swaps at the application level or user level

SMS OTP is a controversial topic among security professions, and rightfully so. SMS is susceptible to a social engineering attack called a "SIM swap," where a telephone company is persuaded to issue a SIM card to an attacker for a victim's phone number.

The general consensus is that SMS OTPs add some degree of security, but a targeted attack has a high chance of success. Jack Dorsey [infamously fell victim](https://www.wired.com/story/jack-dorsey-twitter-hacked) to a SIM swap which helped an attacker gain access to his Twitter account.

Many argue that SMS OTP should be discontinued entirely. The challenge is that SMS is ubiquitous and convenient, so users are more likely to configure multifactor when SMS can be used.

At Clerk, we don't believe the answer is not one-size-fits all, so our solution is two-fold:

1. Developers can choose to disable SMS OTP at the application level.
2. Individual users can choose to disable SMS OTP at the user level. Even if a phone number is added to an account, SMS OTP must be explicitly enabled for it to be used as a possession factor.

![Mitigate SIM swaps at the application level or user level screenshot](./3fe377763fc8afcbdee8eee118573562d27bd121-2022x954.png "Clerk's dashboard allows developers to choose if SMS OTP is enabled")

## Summary

Multifactor authentication is an essential security feature that every application should offer, but it is essential that best practices are followed for knowledge and possession factors. Clerk's components and hooks provide those best practices for multifactor authentication out of the box.

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# Announcing A New Password Experience
URL: https://clerk.com/blog/a-new-password-experience.md
Date: 2023-05-19
Category: Company
Description: The team has been focused on making a first in class experience for your end users when it comes to passwords. Let's talk about the new features we introduced. 

It's time for an exciting announcement from our end, one that's been brewing for a while. While it may seem like a small step, we believe it's a giant leap towards an unparalleled user experience, especially when it comes to password security.

Our team has been hard at work, making your end users' experience first-in-class. We're thrilled to introduce a suite of new features that will take password management to the next level. Let's dive into the details!

## Password Reset Flow

Who hasn't forgotten a password at least once? Now, with our traditional password reset flow, users can reset their password with a single click. The best part? It ships with our [sign in component](/docs/authentication/sign-in) and syncs perfectly with our [Multi-Factor Authentication (MFA) products](/docs/authentication/custom-flows/multifactor), ensuring all necessary verification steps are complete before users are automatically signed back into your application.

## **Strong Passwords Verification**

In addition to our [HaveIBeenPwned](https://haveibeenpwned.com) integration, we now enable you to set a minimum strength requirement for all new passwords in your application. This added security layer is powered by the password strength estimator [zxcvbn-ts](https://github.com/zxcvbn-ts/zxcvbn).

With its ability to detect commonly used passwords and patterns such as dates, names, and common phrases, zxcvbn-ts ensures that your end users are protected from using weak passwords in your application. Now, stronger password security is not just an option, but a standard.

## **Additional Complexity Requirements**

For our [Business plan](/pricing) users and beyond, we're introducing the ability to enforce specific password requirements like special characters, numbers, and a mix of uppercase and lowercase letters. This will help your users to not just meet, but exceed your standards.

![Clerk dashboard password complexity policy options](./b7ea47b66fa674b5ccf0537b86492ccb73b2ca36-1696x1698.png)

## **Password Completion Guidance**

To take password requirements one step further, once you set password policies in your Clerk dashboard, you can even opt for users to be alerted to your length or complexity requirements via guidance cues. When users are setting their password, friendly messaging will appear below the input box that indicates the password policies you selected to help them create passwords that are robust and compliant.

We're excited about these new additions and can't wait for you to experience them. Here's to a better user experience for all! Want to learn more about "how we roll" passwords? Check out our [post](/blog/how-we-roll-passwords) that goes in depth about passwords at Clerk.

Let us know your thoughts on X [@clerk](https://x.com/clerk) or in the [Clerk Community](https://clerk.com/discord) on Discord.

---

# How We Roll – Chapter 2: Avatars
URL: https://clerk.com/blog/how-we-roll-avatars.md
Date: 2023-05-19
Category: Company
Description: How We Roll is a deep-dive into how Clerk implements authentication. In this chapter, we discuss why avatars should never be an afterthought.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, *roll*) authentication at Clerk.

## Chapter 2: Avatars

We're focusing on avatars early in the series because too many applications treat them as an afterthought. But at the biggest companies in the world – the ones with full-time identity teams working to streamline every aspect of authentication – it's common to see avatars used to "level-up" the user experience.

![Chapter 2: Avatars screenshot](./fbba56b5223a378e591a77fe17f8fa2ee8df4062-3860x2056.png "Google uses avatars within their sign-in flow and as a button throughout their applications.")

At Clerk, we operate like an outsourced identity team, and we *love* bringing details like these to our customers. We obsess over authentication so our customers can focus on what truly differentiates their business.

Let's dive in to the full feature!

## Choosing avatars

There's an unfortunate reality when dealing with avatars: users don't like uploading their avatar directly. Forcing avatar upload during the sign-up flow would dramatically reduce conversion rates.

Thankfully, "social sign-in" providers like Google and Facebook provide access to the user's avatar through the [OpenID](https://en.wikipedia.org/wiki/OpenID) protocol. Moreover, when social sign-in is available, it's preferred by over 50% of users.

As a result, simply enabling *Sign in with Google* leads to most users automatically having their choice of avatar.

Of course, should they choose, users can always modify their avatar through Clerk's `<UserProfile/>` component, or developers can build their own flow with the `useUser()` hook.

### Beautiful default avatars

In the event that users do not provide an avatar, directly or indirectly, Clerk generates beautiful default avatars for every user.

We offers a variety of options, with initials or silhouettes in the foreground, and marbling or a solid color in the background. The marbling effect is built with the fantastic open source project [Boring Avatars](https://boringavatars.com).

## Avatars for the `<UserButton/>`

The most common place avatars are seen today is the top-right of an application. Clicking it opens a menu to manage account settings or sign out.

Clerk provides this functionality through a component called `<UserButton/>`.

![How We Roll Avatars guide illustration](./d67e359f62393528c1ab5bd353aa8f45d2dc5499-1506x880.png)

To better indicate this is a button without modifying the avatar itself, we allow developers to add a "shimmer" effect on mouseover:

## Avatars in the sign-in flow

Like Google, Clerk's `<SignIn/>` component displays the user's avatar during the sign-in flow.

This is primarily seen as a benefit to user experience, but it also has benefits in security. The idea is that users will grow accustomed to seeing their avatar during the sign-in process. If they ever see the wrong avatar, it will raise suspicion of a potential phishing attack.

![How We Roll Avatars guide illustration](./db878c278a10709c17dd3cfdd0f4ca7b0b5cf3a7-1298x1084.png)

## Summary

Though avatars may feel like a small feature, they bring a high degree of polish and professionalism to modern applications. Clerk's social sign-in integrations, beautiful default avatars, and prebuilt components all work together to make avatars easier than ever before.

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# How We Roll – Chapter 1: Passwords
URL: https://clerk.com/blog/how-we-roll-passwords.md
Date: 2023-05-19
Category: Company
Description: How We Roll is a deep-dive into how Clerk implements authentication. In this first chapter, we discuss passwords – the original form of authentication.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, *roll*) authentication at Clerk.

## Chapter 1: Passwords

Many consider passwords to be the "original" form of authentication. They have been used in software since long before the internet existed and are championed for their ease-of-use.

Despite being such a common authentication strategy, implementing passwords is a surprisingly complex task. Decades of trial-and-error have taught us best practices for:

1. Choosing passwords
2. Changing passwords
3. Storing passwords
4. Migrating passwords

Let's learn how Clerk handles it all!

## Choosing passwords

Users can choose passwords in three different scenarios: signing up, changing their password, and resetting their password.

In each scenario, Clerk presents the password field:

This field is designed to provide feedback as the user types, and offers three variants of feedback:

### 1. Password is too short

As with every authentication factor, we first referenced [NIST 800-63B](https://pages.nist.gov/800-63-3/sp800-63b.html) to determine our password requirements. The National Institute of Standards and Technology (NIST) maintains this document specifically to help authentication professionals maintain the security of digital accounts.

There, we learned that 8 characters is the optimal minimum password length, and so we apply this requirement across every Clerk application.

### 2. Password has been breached

NIST guidance also states that passwords which have been breached should not be used again. Password database leaks are unfortunately all-too-common, and many of those databases store passwords in an unsafe manner.

As a result, billions of passwords have been breached and cannot be used again. To determine whether a password can be used, Clerk leverages the well-maintained corpus from [Have I Been Pwned](https://haveibeenpwned.com).

### 3. Password is too weak

Though it's not suggested by NIST, we implement [zxcvbn](https://www.usenix.org/conference/usenixsecurity16/technical-sessions/presentation/wheeler) to measure password strength as a user types. zxcvbn is a battle-tested complexity estimation algorithm, originally developed within Dropbox.

We allow developers to enforce a minimum level of complexity (low, medium, or high), as determined by zxcvbn.

### Customizing requirements

Within Clerk's dashboard, developers can customize the requirements for users choosing a password:

![How We Roll Passwords guide illustration](./b7ea47b66fa674b5ccf0537b86492ccb73b2ca36-1696x1698.png)

Although not recommended because they frustrate users and because NIST advises against them, this screenshot shows how Clerk also allows LUDS-based password strength requirements (lowercase, uppercase, digits, symbols).

## Changing passwords

When changing or resetting passwords, we help users keep their accounts secure with two requirements:

1. The user must authenticate first, including with multifactor authentication if it is enabled on their account. The helps prevent attackers from claiming an account they discover is signed-in on a public device.
2. The user must be given the option to sign out of other devices. This stops attackers who may already have access to an account.

Here's an example in Clerk's forgot password flow:

## Storage

We use the industry-standard, one-way password hashing function called [bcrypt](https://en.wikipedia.org/wiki/Bcrypt) to hash passwords before they are saved to our database. This is essential to prevent passwords from being leaked, *even in the unlikely event that Clerk's database leaks.*

Counterintuitively, bcrypt adds security by being computationally expensive. Trying to break a bcrypt hash by brute-force is impractical, because it would cost too much money (e.g. trying every possible combination of characters).

Although bcrypt is the standard today, it's important to recognize that the standard changes with some regularity. We monitor those changes and will update our preferred strategy as needed.

## Migrations

Although bcrypt is our preferred hashing algorithm, we support importing passwords that previously used a wide variety of alternative hashing algorithms, like argon2, scrypt, and pbkdf2\_sha256.

These hashes will stay in their original format until the user signs in again with their password. At that time, we will replace the hash to use bcrypt going forward. This ensures that even if passwords are migrated from a weaker format, they are continually upgraded to a modern best-practice.

## Summary

Though simple in concept, implementing passwords for authentication is a complex task. Clerk's components and APIs offer best practices in security and user experience out-of-the-box, so developers can stop worrying about authentication and focus on what truly differentiates their business.

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- *How We Roll – Chapter 10: Coming Soon!*

---

# Clerk raises $15m Series A led by Madrona
URL: https://clerk.com/blog/series-a.md
Date: 2023-03-22
Category: Company
Description: Clerk surpasses a million managed users and taps into executive experience from Auth0 and Vercel

Today, we're excited to announce that Clerk has raised a $15 million Series A led by Madrona, just a few months after [our seed](/blog/a16z-seed). We're especially thrilled that [Karan Mehandru](https://www.madrona.com/team-profiles/mehandru-karan) has joined our board, who has tremendous experience in our industry from his time on Auth0's board.

More exciting than fundraising, though, is that Clerk is growing – **and fast:**

![Thumbnail: Clerk raises $15m Series A led by Madrona guide](./5715c0ca9de562c76bb02376aa303bbccc6aee99-1902x702.png)

Our growth accelerated in the new year as word-of-mouth and referrals became our #1 source of new customers. Here is some of the incredible enthusiasm developers are sharing on X:

![Thumbnail: Clerk raises $15m Series A led by Madrona guide](./8bb2940b16b1d504e1be0486ac3aa316d178acca-2358x924.png)

We're so grateful for our early adopters who helped hone Clerk into a streamlined solution – **and then told their friends!** Without them, we simply wouldn't be here today.

Of course, our work is far from done. Throughout the year we'll continue launching new authentication features, like SAML SSO, passkeys, and support for more frameworks.

We're also eager to begin helping developers with *authorization*. We'll expand customer profiles to include information about Roles and Permissions, as well as Billing and Entitlements. We'll solve authorization through components and hooks, same as we did authentication.

As always, if there's anything in particular you'd like to see from Clerk, or if you have feedback or suggestions, we'd love to hear from you. Join our [community discord](https://clerk.com/discord) or tweet at [@clerk](https://x.com/clerk) – we're listening and eager to improve.

### Press Release

**Clerk raises $15 million for React authentication and user management**

Clerk, the drop-in authentication and user management solution for React, today announced a $15 million Series A led by Madrona, with managing director Karan Mehandru joining its board. Mehandru brings a deep understanding of the space from his years on the board of Auth0. Other new investors include Vercel's CEO Guillermo Rauch, Mango Capital, and Auth0's former CRO Dave Wilner. Existing investors Andreessen Horowitz, S28 Capital, and Fathom Capital also participated.

*"The response from React and Next.js developers has been incredible. Monthly active users have grown 500% in the past 5 months, and we've crossed a million managed users,"* said co-founder and CEO, Colin Sidoti. *"When we met Karan, we were excited to tap into his experience in identity and in scaling go-to-market as we target the next generation of application development."*

React and frameworks like Next.js have sparked an architecture shift that will propagate across all application development, and this new architecture requires a tailored suite of developer tools. Hosting and database technologies have already evolved, but before Clerk, authentication was left to be solved in-house.

*“Clerk is the Stripe Checkout of authentication and user management, except it’s built for React,"* said Rauch, who also authors Next.js. *"The best practices built-in to their `<SignIn/>` and `<UserProfile/>` components would take months to implement in-house, yet no sacrifice is made in terms of Enterprise extensibility or customization to your brand.”*

With this new funding, Clerk will accelerate its roadmap beyond authentication. It aims to help developers with authorization, which requires knowledge of a user's role and permissions, as well as their billing plan and entitlements. Clerk will provide the glue that binds these systems together, while also empowering React developers to build authorization controls for their end-users.

*"Clerk is a foundational technology company dedicated entirely to serving the needs of the next generation of application developers as the authentication industry migrates from back-end APIs to front-end componentized modules,"* said Mehandru. *"As this market evolves, Clerk is extremely well positioned to be the "system of action" and the "system of record" for customer identities, much like what Auth0 accomplished in the last decade. I am excited to partner with Colin, his brother and cofounder Braden, and the Clerk team as they empower the developers of the future and build an enduring and exciting company over the next decade and beyond."*

Clerk is actively hiring across product and engineering roles. Please visit [clerk.com/careers](/careers) for more information.

---

# Clerk raises $6.2m led by Andreessen Horowitz
URL: https://clerk.com/blog/a16z-seed.md
Date: 2022-11-30
Category: Company
Description: Clerk is building the future of authentication for React, where the Component is the new API

Today, we're thrilled to announce $6.2m in new funding led by Martin Casado at Andreessen Horowitz, with continued participation from S28 Capital, Fathom Capital, and South Park Commons. The formal press release is available below.

This funding marks a major milestone for Clerk. For the first time, we've raised based on the traction of our customers, who are simply blowing us away with their continued success. Here's a chart showing cumulative sign ups across all customer applications since we launched:

![Cumulative customer sign-ups chart](./33e2503da8bdd68d27f8100245b0e13c0faf8a3d-1940x762.png)

Our key insight at Clerk is that the Component is the new API. In fact, over 95% of our customers use **all** of our `<SignUp/>`, `<SignIn/>`, and `<UserProfile/>` components! Of the few who implement our APIs directly, most will build a custom sign-up flow with our APIs, but still use our `<SignIn/>` and `<UserProfile/>` components for efficiency.

This outcome is obvious in hindsight. Components combine the power of backend APIs with beautiful, prebuilt frontends, so they inherently deliver more value than APIs alone. We believe component-based integrations have set a new standard for developer tools, and we're excited for the massive improvement in developer productivity they enable.

With this new funding, Clerk will reinforce and expand on our foundation, including:

- Continued, emphatic support for cutting-edge frameworks like Next.js, Remix, Redwood, and more on the way
- Additional authentication strategies like passkeys and SAML SSO
- Expanded capabilities in our `<CreateOrganization/>` and `<OrganizationProfile/>` components for B2B SaaS

We can't wait to see what you build!

### Press Release

Clerk, the drop-in authentication and user management solution for React, announced today that it has closed a $6.2m seed round led by Andreessen Horowitz. Existing investors S28 Capital, Fathom Capital, and South Park Commons also joined the round. This new funding will allow Clerk to expand its suite of authentication and user management tools, and create new tools to help B2B SaaS companies manage both user accounts and business accounts.

Clerk empowers React developers to build sign-up and sign-in flows as well as user profiles without the assistance of backend developers. The solution comes complete with security essentials like multi-factor authentication and active device management. The beautifully designed React components can be customized to match any company's branding. By providing a full stack solution, Clerk minimizes the time engineers need to spend on authentication, freeing up development resources to focus on the core of their business.

The vast majority of authentication solutions on the market today are still managed in-house and are often incomplete and fail to meet security best practices. Many of these solutions are dated, as web development technologies have evolved to component-based frontend technologies like React. With Clerk’s solution, developers are getting what they prefer—components—because they help them move faster. Components are especially appealing in authentication and user management where businesses do not get much benefit from reinventing the wheel of self-serve 2FA or avatar-uploading. Clerk includes both in their `<UserProfile/>` component.

*"Our key insight is that React Components have replaced REST APIs as the premier developer experience,"* said **Colin Sidoti, Clerk cofounder and CEO**. *"Since components have a built-in backend and presentation layer, they're an order-of-magnitude faster to implement than APIs."*

While Clerk is intended for React frontends, the authentication solution works with any framework, programming language, or API gateway on the backend. This approach has been especially helpful for legacy web applications that are transitioning to React on the frontend, but do not want to change their backend technology.

*"There's a growing preference for React among businesses of all sizes, and devtools must evolve to cater to this new architecture. Clerk is hands-down the easiest and most-complete approach to auth and user management in React today,"* said **Martin Casado, General Partner at a16z.** *"We are excited to help accelerate Clerk's growth and reach more businesses, founders and startups who are looking for easy authentication and user management."*

Clerk's vision is to build a truly complete customer management solution for developers, helping facilitate authentication, authorization, communications, billing, and more. Clerk believes these essential services can all be bundled together, and presented cohesively in simple React components.

Notable startups using Clerk include database company Grafbase, women's shapewear company Honeylove, and apartment rental company Blueground. Clerk's React-optimized solution has led the company to be included in Netlify's Jamstack Innovation Fund, and named one of the [10 most promising Jamstack startups](https://www.netlify.com/blog/jamstack-innovation-fund-launches-with-the-10-most-promising-jamstack-startups).

---

# A ‹Component/› is worth a thousand APIs
URL: https://clerk.com/blog/a-component-is-worth-a-thousand-apis.md
Date: 2022-08-07
Category: Company
Description: Clerk is the only authentication service that offers a User Profile component. It sounds incremental, but it's 10x more work – this post explains why

Clerk started with a simple vision. We loved the professionalism of Google's Sign Up, Sign In, and User Profile, so we set out to build developer tools that would help everyone match their caliber.

![A Component Is Worth A Thousand Apis guide illustration](./a0904584e9cb4e0751a833c1bc0bdd14f5060fa2-1080x494.png)

With UIs as our primary goal, we realized that Clerk wouldn't be a traditional API company. We focused on building a fantastic frontend component SDK, rather than a traditional backend REST SDK.

We chose React as our first target given its popularity, and split Google's surface area into four components: `<SignUp/>`, `<SignIn/>`, `<UserProfile/>`, and `<UserButton/>`.

![A Component Is Worth A Thousand Apis guide illustration](./e9dbf939e2b523fdf51e5f9e59a6f5e16907298b-2458x1248.png)

Components are a higher-level of abstraction than REST SDKs. They provide UIs to end-users instead of just a function to developers, so they inherently offer more value.

But what's surprising is just *how much* more value. Even at Clerk, we assumed we could just "slap a UI on top of an auth API." Despite having a team full of seasoned engineers, we failed to realize just how much work sits between traditional APIs and the end-user features they enable.

### Stripe example

As an example outside of authentication, let's compare Stripe's original 9 lines for creating a charge, to the *sidebar* for their current Checkout documentation:

![A Component Is Worth A Thousand Apis guide illustration](./cccc2d59d94fa8b00928e84940a25f5392dbbb90-1082x981.png)

Checkout has far more complexity than the original Charge API, but why? Aren't both responsible for charging a customer?

The difference is that Charge asked for the exact amount to charge, while Checkout needs to display *why* an amount is being charged. It needs all of the context of the Products being purchased, their Price, the quantity, the taxes and shipping costs, any discounts applied, and more. Stripe needed to build dozens more API resources and paramaters to produce the end-user Checkout UI.

Before Checkout, this additional context lived inside the developer's application. Now that it lives inside Stripe, charging customers has become massively easier. Stripe solves so much more complexity for developers today than it did 10 years ago.

### Clerk example

Clerk is seeing the same patterns in authentication that Stripe saw in Checkout. To build our core components, we need to manage much more complexity than legacy vendors like Auth0.

This is particularly apparent with our `<UserButton/>` and `<UserProfile/>` components. We are the only authentication provider to offer these, and there's a foundational reason why: **Clerk is the only authentication provider that manages sessions as-a-service.**

Legacy vendors focus on just the sign-up and sign-in screens. Once the user's identity is verified, the session is managed within the application – not within the authentication service. This makes it impossible for them to offer drop-in `<UserButton/>` and `<UserProfile/>` components like Clerk.

And since we're not ones to cut corners, our session management solution is fully featured. We include active device management from within `<UserProfile/>`, so users can remotely force sign outs from other devices:

![A Component Is Worth A Thousand Apis guide illustration](./3135b92fe15d7d789429299c6354d1662ea941b1-616x391.png)

Plus, we (optionally) include account switching within `<UserButton/>`, just like Google's:

![A Component Is Worth A Thousand Apis guide illustration](./c962088265770e23f04b1cf2b0b5eeba2c32d2b6-471x527.png)

Beyond session management, we also enable self-serve two-factor authentication from within `<UserProfile/>`:

![A Component Is Worth A Thousand Apis guide illustration](./dfec3db47a85e8f1f17f882a97c2b00bcbdf2ab5-615x352.png)

Once setup is completed, `<SignIn/>` will automatically request the second factor during the next sign-in:

![A Component Is Worth A Thousand Apis guide illustration](./478885fd08c6525391c6878688bea54c3d091274-526x527.png)

If Clerk didn't manage sessions and two-factor authentication, developers would be stuck implementing these features from within their application – a much more challenging prospect than simply using `<UserButton/>` and `<UserProfile/>`.

### On components vs redirects

Some readers may be aware that Clerk uses embeddable components while Stripe Checkout uses a redirect flow. That's completely correct, so we kept this blog focused on points that remain true regardless of how the UI is ultimately presented to the end-user.

That being said, there is one advantage to components that deserves a special callout:

At Clerk, every component we build is composed using React hooks, and the hooks we use are actually pacakaged alongside our components. So, if frontend developers don't like our components for any reason, they can still build their own UIs **without involving a backend developer and a backend SDK.**

This extra layer of composability has been a huge boon for companies with strict branding requirements, since we eliminated the need for backend application logic regardless of if our UIs are used.

### In summary

We've seen this pattern enough to say confidently: **a `<Component/>` is worth a thousand APIs.**

Components have become the new gold-standard in developer tools – they are the new, optimal, composable building block. They save developers far more time than traditional REST SDKs, both by providing end-user UIs, and by eliminating huge swaths of code that used to be managed in-house.

---

# The Journey to Modern Web Authentication
URL: https://clerk.com/blog/the-journey-to-modern-web-authentication.md
Date: 2022-07-21
Category: Company
Description: A blog post about the story of Clerk; our journey of bringing seamless authentication to the Modern Web, where we stand now, and what lies ahead.

Web authentication is an acronym and protocol ensemble ([SSO](https://en.wikipedia.org/wiki/Single_sign-on), [OAuth](https://oauth.net), [SAML](https://en.wikipedia.org/wiki/Security_Assertion_Markup_Language), [OpenID](https://openid.net/connect), [SCIM](https://en.wikipedia.org/wiki/System_for_Cross-domain_Identity_Management), [FIDO](https://en.wikipedia.org/wiki/FIDO_Alliance), [PKCE](https://oauth.net/2/pkce), [JWT](https://jwt.io), etc...) that traditional user management software implements one way or another. Developers know that adding authentication to an application is a mundane and tedious task and they increasingly choose to outsource it.

Authentication requires user input, usually collected via redirection to authentication pages such as the sign-up or sign-in page. UX-wise the redirection is a clean cut. Things get fascinating in the modern web world with [React](https://reactjs.org), [Vue](https://vuejs.org), [Svelte](https://svelte.dev) or [Angular](https://angular.io) applications. Users expect to land and stay on the same tab, sign in fast and securely with a Google-like experience, and maintain their sessions in a safe and unobtrusive way until they log out.

Let's mull over how to build a sign-in form with SMS [two-factor authentication](https://en.wikipedia.org/wiki/Multi-factor_authentication) (2FA) in React. Where will the state live? Will the sign-in flow be bookmarkable? How will we transform user credentials into a session? Should I use a session cookie or a JWT? How will I ensure that the UI doesn't flicker when a guest user authenticates?

User management software so far offers the necessary authentication features but integrating them into your React application is cumbersome. This is why we've built [Clerk](/); to offer a modern-web customer identity platform that just works.

From early on, our goal was to focus on developer experience. To achieve that we had to start with a framework so we picked the most popular at the time, [Next.js](/docs/quickstarts/get-started-with-nextjs). We knew that React users expect components and hooks, so we offered both of them from the start.

While building our [React components library](/docs/component-reference/overview), we had to decide how we will package it in order to make it easy to set up and upgrade. Hot-loading the latest version using semantic versioning was our preferred choice. Another requirement was the ability to customize our components to match the look'n'feel of the host application. We decided to start simple with CSS overwrites.

The hooks API design was also exciting. We debated a lot whether *`const {user} = useUser()`* should return *`null`* or an empty *`new User()`* when not authenticated. We spent countless hours trying to figure out how should we store and update the session state. The first iteration resulted in a polling mechanism with a lot of auth data piggybacking.

The initial adoption was really warm. Clerk began to mature. We started designing new components, adding more authentication features, revamping our state management, and improving our developer experience.

In the meantime, modern web frameworks were evolving rapidly. [SSR](https://nextjs.org/docs/basic-features/pages#server-side-rendering), used mostly for public, static content back in 2020 became the defacto choice for SaaS. Edge computing support for fast API endpoints was added and distributed web infra­structure improved immensely. So we followed. Clerk was ahead of the game and one of the first solutions to add native SSR support for our [hooks](/docs/reference/overview). We also moved to [session validation at the Edge](/docs/request-authentication/nextjs#using-edge-middleware) so that verifying the JWT takes less than 1 ms.

In the first half of 2022, the product went from strength to strength. We added [organizations](/docs/organizations/overview), we bridged [web3 and web2 authentication](/blog/introducing-web3-authentication), we integrated with JWT-powered databases and tools such as [Hasura](/docs/integration/hasura) and [Supabase](/docs/integration/supabase), we expanded to more React frameworks such as [Remix](/docs/quickstarts/get-started-with-remix), [Redwood.js](/docs/quickstarts/get-started-with-redwoodjs), [Gatsby](/docs/quickstarts/get-started-with-gatsby), [Expo](/docs/reference/clerk-expo), and we've just released our new, fresh, and [fully customizable ClerkJS Components](/blog/changelog-2022-07-15).

For the second half of the year, we have a lot of exciting epics in the pipeline. First and foremost, we aim to be the first authentication solution that works with [React server components](https://nextjs.org/docs/app/building-your-application/rendering/server-components). In addition, customers request [SSR for our ClerkJS Components](https://github.com/clerkinc/javascript/discussions/183). Remember how hot-loading was a great idea two years ago? With strong SSR support, it's not anymore. We have also started working on making all our [SDKs isomorphic](https://github.com/clerkinc/javascript/issues/127) to seamlessly work on Node and V8 runtimes. And of course, we will expand our customization capabilities, as we intend to allow developers to use their [Chakra](https://chakra-ui.com) or [Material UI](https://mui.com) components in their Clerk authentication forms.

On a personal level, I am looking forward to seeing how bleeding-edge web tech will evolve and shape how we build applications. If you are intrigued by any of these topics, [talk to us on Discord](https://clerk.com/contact/support) or [apply](/careers) to join our expanding team. We are an international, all-remote team and we are [currently hiring](/careers).

---

# Clerk joins the Netlify Jamstack Innovation Fund
URL: https://clerk.com/blog/clerk-joins-netlify-jamstack-innovation-fund.md
Date: 2022-07-12
Category: Company
Description: Clerk powers authentication for over 5,000 Jamstack teams – now we're working with Netlify to further our commitment to the ecosystem

Clerk provides serverless, edge-compatible authentication to over 5,000 Jamstack teams. Today, we're thrilled to join the Netlify [Jamstack Innovation Fund](https://www.netlify.com/jamstack-fund) to further our commitment to the ecosystem.

Netlify has been an inspiration for Clerk since their co-founder, Matt Biilman, [spoke about Jamstack in 2016](https://vimeo.com/163522126). The idea felt like it would change the way we build software, *and indeed it has*.

Clerk leverages Jamstack to set a new standard for developer tools. Instead of only providing a backend SDK, we also offer a frontend SDK with React components and hooks to empower frontend developers.

For example, our [`<SignUp/>` component](/components/sign-up) embeds a complete, conversion-optimized sign-up flow in our customer's application:

![Clerk Joins Netlify Jamstack Innovation Fund guide illustration](./9507286557ea85867e815093a4f0108edfa4cdf8-720x486.svg)

And for complete customization, frontend developers can instead leverage our [`useSignUp()`](/docs/reference/clerk-react/usesignup) hook to build a UI of their own.

Both `<SignUp/>` and `useSignUp()` are implemented entirely on the frontend, while Clerk's API is responsible for the backend of user and session management. This enables teams to build fully-featured authentication in minutes instead of days or weeks.

We're proud to be recognized as one of the 10 most promising Jamstack startups, and excited for the future as we begin working closely with the Netlify team. Stay tuned!

---

# Clerk is hiring a senior frontend team to expand our full-stack component library
URL: https://clerk.com/blog/hiring-frontend-full-stack-components.md
Date: 2021-11-19
Category: Company
Description: Seeking experts in CSS, browser APIs, JS bundling, React, react-native, Swift, Kotlin and Developer Experience

We're trying something new and combining the job description for several frontend roles into a single post. This is meant to highlight the diversity of expertise we're seeking, and to give applicants an idea of how the team will be structured.

**\*Public referral bonus:** Introduce us to a candidate who joins Clerk and we'll send you $5,000\*

## About us

Clerk is a developer tools company in the Authentication and User Management space. We provide developers with full-stack components like [`<SignUp/>`](/components/sign-up), [`<SignIn/>`](/components/sign-in), and [`<UserProfile/>`](/components/user-profile) to handle common but hard-to-get-right flows.

In 2022, we're expanding our scope to include Organization Management and everything that comes with it:

- Organization onboarding
- SAML SSO
- Roles and permissions
- Subscription management
- Authorization

With this new scope, Clerk will provide all the boilerplate necessary to start a B2B SaaS - except the application logic itself.

## Full-stack components?

![Hiring Frontend Full Stack Components guide illustration](./fea05669feec890cc6138436c5b3854371ebfa77-1959x664.svg)

While the last generation of developer tools was characterized by APIs targeted at backend developers, we believe the next generation will be full-stack components targeted at frontend developers.

A component is "full-stack" when it provides UI for end-users to interact with, and when it is powered by a third-party API exposed directly to the frontend. **Critically, frontend developers can implement full-stack components without needing backend developers to plumb data from a backend-only API.**

Besides our own `<SignUp/>`, `<SignIn/>`, and `<UserProfile/>` components, we look at Stripe's recently launched [Payment Element](https://stripe.com/docs/payments/payment-element) as a good example of a full-stack component.

## Who we're looking for

We're looking for developers who can help improve and grow our full-stack component library as Clerk expands into Organization Management. We're seeking expertise across many frontend disciplines:

### HTML & CSS experts

We have our fair share of debates deciding between `grid` and `flex`, but our challenges go much deeper. Customization of our full-stack components is unsolved, and it's our top customer request.

- How can we offer a customization experience that feels natural to developers using any of Tailwind, Bootstrap, CSS modules, or a global stylesheet?
- Should we deploy our components as [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components), iframes, or directly into our customer's DOM?

Help us build a design system whose key feature is blending in with others.

### Browser API experts

Full-stack components are filled with challenges where the solution depends on an intimate understanding of browser APIs.

- How can we allow multiple users to be signed in at once, with different users on different tabs, and *without* URL decoration?
- How can we provide parity between production (same-site to our API) and development (cross-site to our API) environments?
- How can we offer multi-page components if the developer's usage of the History API is abstracted behind React Router or Next.js? Or if they don't use the History API at all?

Puzzles like these are essential to delivering high-quality developer experience, and we need more experts to help us solve them.

### JS bundling experts

There is no Next.js for building full-stack components, so all the challenges of bundling, code-splitting, and lighthouse score impact fall on us. Help us improve our Webpack configuration or choose to rip out Webpack completely.

### React experts

Although our components can be used in any framework (or vanilla JS), they're developed with React behind the scenes. We need more React experts to help us build and optimize them, and we're especially interested in leveraging new tools like React Server Components.

### Mobile experts (react-native, Swift, Kotlin)

Help us port our Javascript core to native app developers. While a few developers have reverse engineered Clerk to support mobile, we’re excited to begin building first-class SDKs for mobile applications.

### Developer Experience experts

Every project at Clerk is focused on improving developer experience. Our key innovation is our frontend-facing API that powers our full-stack components. Instead of authenticating with a secret key that authorizes any action, it authenticates with the user’s session and only authorizes actions the user is permitted to take.

We've found this pattern to be incredibly empowering for frontend developers, who use Clerk to take control of User Management without ever being blocked by backend developers. As we expand into Organization Management, we need help defining the new APIs that will unlock the functionality for the frontend.

## Current stack

Clerks components are written in Typescript using React and CSS modules. They are packaged with tsdx and deployed on npm. The backend is written in Go and Postgres.

## Requirements

- 2+ years of experience in your frontend focus area
- A passion for developer tools and high quality developer experience
- Able to write clean, secure, and performant code with attention to detail
- Able to perform well in a fast-paced, remove environment

## Culture

The team at Clerk is small but our aspirations are big. To drive our mission forward, our culture emphasizes frequent deploys and high quality developer experience, both externally and internally. We recommend reading our [weekly changelog posts](/blog) to get an idea of our pace.

Clerk is a remote team, with timezone concentration in the US and the EU (especially Greece). We believe everyone should work their own hours, but also drive a lot of value from our overlapping hours each day.

## Benefits

- Competitive salary and equity
- Gear of your choice for your home office
- Health, dental, and vision insurance (US)
- Unlimited vacation policy - 25 days recommended per year plus national holidays in your country of residence. Take time when you need it.

## Apply

Even if you don't fit these descriptions exactly, we encourage you to apply if you find these challenges interesting. We look forward to hearing from you!

Please apply [through Workable](https://apply.workable.com/clerk-dev/j/C5BBBAB4AA) or email our cofounder directly at [colin@clerk.dev](mailto:colin@clerk.dev)

---

# Clerk raises $4 million to build the next-gen authentication and user management platform for developers
URL: https://clerk.com/blog/clerk-raises-for-next-gen-auth.md
Date: 2021-07-01
Category: Company
Description: Clerk raised a $4 million seed round led by S28 capital with Andrew Miklas (cofounder, PagerDuty) joining our board.

## Announcing our seed round

Today, we're thrilled to announce that Clerk has raised a $4 million seed round, led by S28 Capital with Andrew Miklas (cofounder, PagerDuty) joining our board. Additional participants include Fathom Capital, Calvin French-Owen and Ilya Volodarksy (cofounders, Segment), Darragh Buckley (first employee, Stripe), Max Stoiber (creator, styled-components).

## The business case

Developers love Clerk because it's fast to set up and maximizes conversion through their Sign Up and Sign In forms. Development speed and conversion rates have become bigger challenges in recent years, as users are demanding more variety in the ways they authenticate.

While privacy-focused users are demanding to create accounts by email, convenience-focused users are demanding "single sign-on" through vendors like Google and Facebook. Subsets of both groups are also security-focused, and are demanding multi-factor authentication.

Creating a cohesive user experience that caters to every group is challenging, and developers often make mistakes that leave their users frustrated or unable to access their accounts. A few examples we commonly see are:

- Disallowing single sign-on for users who originally created their accounts by email
- Allowing single sign-on for users who originally created their accounts by email, but accidentally creating them a second account
- Accidentally reducing multi-factor authentication to single-factor authentication during a "forgot password" flow

Clerk's prebuilt Sign Up and Sign In forms eliminate these mistakes and more. They are built by a team of designers and engineers who focus on maximizing conversion without sacrificing security. If custom designs are preferred, Clerk's API can be used instead to speed up development while still mitigating these errors.

## The path ahead

With this funding, we will focus our efforts on five product areas that are crucial to our customers' success:

1. **Security -** Applications built on Clerk automatically receive critical security features like multi-factor authentication, password breach protection, and device management. We will continue to invest in new protections **for our customers' applications**, as well as begin applying for formal security certifications like SOC-2.
2. **Optimization -** Most businesses simply do not have the resources to focus on optimizing the conversion of their Sign Up and Sign In forms. This is our core competency, and we will continue to analyze our prebuilt components to ensure they are as streamlined as possible.
3. **Customization -** In the months since launch, we've heard a chorus of requests for more customization capabilities in our prebuilt components. More flexibility is on the way, as well as better documentation for creating completely custom user experiences with Clerk's API.
4. **Extensibility -** Another frequent request is the ability to connect user data with other applications. We already deployed webhooks so developers can build connections in-house, and now we will focus on prebuilt connections for common vendors, [starting with Hasura](/docs/integrations/databases/hasura).
5. **Developer Experience -** We launched Clerk with SDKs for React, Next.js, and Express - but beneath those SDKs are language-agnostic REST APIs. The coming months will bring official support for more languages and frameworks.

These five focus areas are driven by the requests of our early adopters, whose enthusiasm and steadfast support made this funding possible. We're excited to continue servicing their needs, and also to welcome the next wave of developers in need of better [tools for authentication and user management](/nextjs-authentication).

## Growing our team

Our team has grown from 2 to 15 in the past eight months, and we're excited to continue that growth across engineering, design, developer advocacy, and more. If you're interested in complex design and engineering problems, or the challenge of creating a ubiquitous developer tool, [come join us](/careers).

With immense gratitude,

Colin and Braden Sidoti\
Cofounders, Clerk

---

# Introducing Clerk: All of user management, not just authentication
URL: https://clerk.com/blog/all-of-user-management-not-just-authentication.md
Date: 2021-01-07
Category: Company
Description: The task came with a sense of helplessness. We knew what "great" looked like, but it was impractical to build all of that functionality.

![--- screenshot](./3537bb21b9137a72b75315872c03b0a97678aef2-2000x914.webp "Google user management")

We thought authentication-as-a-service vendors might ease our pain, but over and over again, we were disappointed by how much extra work was necessary. We never understood why until one friend quipped, *"auth-as-a-service really just solves half of 2-factor auth."*

Then it clicked. **We needed all of user management, not *just* authentication.** And we realized if we could solve this problem, countless others could benefit, too.

Today, we're absolutely thrilled to launch Clerk: **user management as-a-service**. We're solving the whole problem, from frontend to backend, with beautiful UIs and elegant APIs. Our architecture pulls user management out of the way, so developers can focus on what makes their software truly special.

![Today, we're absolutely thrilled to launch Clerk: user management as-a-service. We're solving the whole problem, from frontend to backend, with beautiful UIs and elegant APIs. Our architecture pulls user management out of the way, so developers can focus on what makes their software truly special. screenshot](./a37e8ca7365432384210b4b61e7c11d631148c14-2000x1016.webp "Clerk user management")

Our launch today includes UI components for Sign Up, Sign In, User Profile, and what we're calling the "User Button." They can be mounted directly in your application, or you can redirect users to a Clerk-hosted page on accounts.yourdomain.com.

Best of all, the components will automatically update as our team optimizes their design, develops new features, and adds support for the latest in account security.

While today marks an exciting milestone for Clerk, this is truly just a "minimum viable product." The roadmap ahead will bring many features to better support developers and their end users:

- SDKs for additional languages and frameworks
- Additional OpenID Connect and OAuth providers
- Additional 2-step verification factors like TOTP and WebAuthN
- Session management and revocation in the User Profile
- Team management and enterprise authentication like SAML

Need help with something you don't see listed? [Make a request](https://clerk.com/contact/support).