We use cookies to enhance your experience on the site
CodeWorlds
Back to collections
Guide14 min read

Kinde, authentication plus the things you would add anyway

Kinde combines authentication, organisations, and feature flags in one service. A free tier of 10,500 users, Next.js integration, and a Clerk comparison.

Kinde, authentication plus the things you would add anyway

Login in an application rarely ends at login. A month later organisations are needed, since a customer wants to invite their team. Two months later roles and permissions. Three months later feature flags, to release something to selected customers only.

Kinde puts those things in one service rather than leaving them to assemble from separate tools. That is the main reason to consider it alongside better known options, and incidentally the answer to why its free tier is higher than the competition's.

What comes included

The base covers standard login methods: password, login through external providers, a link sent by email, passkeys, and a second factor. Nothing surprising and that is how it should be.

It gets more interesting at organisations. A user can belong to several, hold different roles in each, and the application receives in the token which organisation's context it operates in. That is exactly the mechanism which, implemented yourself, takes two weeks and always contains a bug in context switching.

Feature flags are the second distinguishing element. A flag's value arrives with the token, so no separate call to another service is needed on every render. A flag can be set globally, per organisation, or per individual user.

The third element is machine permissions, meaning access for services rather than people. With a back end made of several services that is needed earlier than most teams assume.

Integrating into an application

Code
Bash
pnpm add @kinde-oss/kinde-auth-nextjs
Code
TypeScript
import { getKindeServerSession } from '@kinde-oss/kinde-auth-nextjs/server'

export default async function Dashboard() {
  const { getUser, getPermission, getOrganization } = getKindeServerSession()

  const user = await getUser()
  if (!user) redirect('/api/auth/login')

  const organisation = await getOrganization()
  const canManage = await getPermission('manage:team')

  return <View user={user} organisation={organisation} admin={canManage.isGranted} />
}

Checking the permission server side matters here and deserves emphasis. Hiding a button in the interface is not a safeguard, since the route handling the action must check the same thing.

Code
TypeScript
export async function POST(request: Request) {
  const { getPermission, getOrganization } = getKindeServerSession()

  const canManage = await getPermission('team:manage')
  if (!canManage?.isGranted) {
    return new Response('Forbidden', { status: 403 })
  }

  const organization = await getOrganization()
  await removeMember(await request.json(), organization.orgCode)

  return new Response(null, { status: 204 })
}

Without that step permissions are a matter of appearance rather than access control. Reading the organisation code in the same function matters just as much: without it the query removing a member has nothing to identify which organisation is meant, and a user from one company can touch another company's data.

Integration with Next.js covers both server components and routes handling redirects. Beyond that, libraries exist for other environments plus a plain authorisation flow when working somewhere without a ready library.

Organisations and multi tenancy

This is where decisions made at the start are hardest to change later, so think them through.

The first question is whether a user can belong to several organisations. If so, the application must know everywhere which context it operates in, and context switching is a flow of its own. If not, the model is simpler, but extending it later means migrating data.

The second concerns where roles live. A role assigned to a user globally means something different from a role assigned within an organisation. Mixing the two leads to an administrator of one organisation holding permissions in another.

The third is invitations. An invitation flow covers sending a message, handling the link, the case of somebody who already has an account, and the case of an expired invitation. The service provides this, and it is worth checking whether its default behaviour matches what you expect.

Mind the appearance too. A login screen carrying the service's default branding is fine for an internal tool, and for a product sold to customers it usually needs changing, which means a paid plan.

Feature flags in practice

Putting flags in the same service as authentication carries an advantage and a limitation worth knowing.

The advantage is no additional call. A flag's value arrives in the token alongside user and organisation information, so checking costs nothing and works during server side rendering too.

The limitation is when it refreshes. A flag changed in the panel takes effect on the next token refresh rather than immediately.

Code
TypeScript
const { getFlag } = getKindeServerSession()

const newCart = await getFlag('new-cart', false, 'b')
const itemLimit = await getFlag('item-limit', 50, 'i')

if (newCart.value) return <NewCart limit={itemLimit.value} />
return <OldCart />

The second argument is the fallback value and is worth supplying every time. A flag deleted in the panel or unavailable through a network error then yields default behaviour rather than an exception, and when choosing that fallback it is safer to name the old variant than the new one.

For gradual releases the refresh interval is immaterial; for switching something off in an emergency it matters, and knowing how long it lasts is worthwhile.

Three uses recur. Releasing a feature to selected customers ahead of general availability. Distinguishing subscription plans, where a flag matches what the customer paid for. Switching off a troublesome feature without deploying a new application version.

For split traffic comparison tests with outcome measurement this solution is too simple, so those needs call for a separate tool.

Tokens and sessions

It helps to understand exactly what the application receives after login, since that determines where to check permissions and how long a session lasts.

After login the service issues two tokens. The first describes identity: who this is, their email address, their picture. The second serves calls to your own programming interface, and it holds permissions, roles, and organisation.

The distinction matters practically. A back end checking identity from the first token makes a mistake, since that token is not meant for authorising resource access. The second is the right one, and verifying it means checking the signature against the service's public key.

Code
TypeScript
import { createRemoteJWKSet, jwtVerify } from 'jose'

const keys = createRemoteJWKSet(
  new URL(`${process.env.KINDE_ISSUER_URL}/.well-known/jwks`)
)

export async function verifyToken(token: string) {
  const { payload } = await jwtVerify(token, keys, {
    issuer: process.env.KINDE_ISSUER_URL,
    audience: process.env.KINDE_AUDIENCE
  })

  return payload
}

Checking the issuer and the audience is two lines whose omission voids the whole mechanism. A correctly signed token issued for a different application passes signature verification and gets accepted, unless you check who it was meant for.

The second matter is lifetime. An access token expires after a dozen or so minutes and refreshing happens in the background. That means a permission change in the panel takes effect on refresh rather than instantly, and the same applies to feature flags.

The third is size. Every permission and flag lands in the token, so with dozens of entries the token grows and travels with every request. With an elaborate permission model, check whether keeping roles in the token and resolving detailed permissions back end side is simpler.

Pricing

PlanCostWhat it covers
Free0 USD10,500 monthly active users, 5 active organisations, 10 flags, 2 roles, 1 webhook
Pro25 USD monthly0.0175 USD per user above the tier, no service branding, unlimited roles and flags, API keys
Plus75 USD monthly0.0163 USD per user, permission scopes, unlimited enterprise login, 5 non production environments
Scale250 USD monthly0.0151 USD per user, per organisation settings, 10 non production environments
Enterprisequoted individuallyDedicated infrastructure, availability guarantees, support, custom contracts

First thing to remember: the tier of 10,500 monthly active users applies on every plan, not only the free one. A paid plan does not buy a larger allowance; it lifts feature limits and lowers the overage rate.

Second: the per user rate above the tier differs on every plan and runs to 0.0175 USD on Pro, 0.0163 USD on Plus, and 0.0151 USD on Scale. At twenty thousand active users on Pro the overage covers nine and a half thousand people, about one hundred and sixty six dollars, so roughly one hundred and ninety dollars a month with the subscription included.

Crossing the tier blocks nothing. The overage is added automatically at the published rate, so the bill grows smoothly rather than halting logins. Active organisations are billed separately: five on the free plan, fifty on the paid ones, and each further one costs between 0.43 and 0.50 USD depending on plan. An organisation with a single active user is not counted.

The higher free tier is the main argument against the competition, and it helps to understand what sits behind it. The service does not shut security mechanisms behind price tiers, so passwordless login, a second factor in an authenticator app, and enterprise connections all work on the free plan. You pay for scale and for removing the branding, not for unlocking security.

Counts are what is limited, and they decide when you must move up. The free plan gives two roles with ten permissions, ten feature flags, one webhook, one enterprise connection, and five active organisations. API keys and permission scopes arrive only on the paid plans.

When estimating cost, count active users rather than registered accounts. An application with fifty thousand accounts of which five thousand log in monthly fits within the free tier.

Kinde against the alternatives

OptionStrengthWeaknessPick it when
KindeOrganisations and flags included, high free tierSmaller ecosystem, less materialProduct for companies with teams and subscription plans
ClerkReady interface components, polished experienceCost at scaleApplication where time to ship matters
Auth0Maturity, compliance, extensive capabilityComplexity and priceLarge organisation with compliance requirements
SupabaseAuthentication alongside the databaseLess elaborate roles and organisationsProject already on that platform

Choosing between the first two rows depends on whether you need organisations. For an application serving individual users, the competitor's ready components save more time. For a product sold to companies, where a customer invites a team and holds roles, the bundle in one service wins.

Consider the last row when the project already rests on that platform. Authentication alongside the database simplifies architecture, though with elaborate roles and many organisations you will reach your own permission layer sooner or later.

It is only fair to add that rolling your own authentication is feasible and, with simple requirements, inexpensive. Password sign in with decent hashing and a session cookie is a day of work. The cost appears in what comes after: password reset, a second factor, sign in through external providers, passkeys, lockout after failed attempts, and handling account takeovers. An external service buys exactly those things rather than sign in itself.

The line runs where compliance requirements or corporate customers begin. Then a home grown implementation means an audit and documentation nobody planned for, while a ready service supplies them with the product.

What to settle before rolling out

Authentication is among the things costly to replace a year in, so a few decisions deserve making deliberately at the start.

The first concerns the user identifier. In your database a user has their own row, and the service issues them its own identifier. Linking the two through the service's identifier is convenient and ties you to the vendor. Keeping your own identifier, with the service's as merely a reference, costs one column and preserves an exit.

The second is the scope of data held on the service side. Email address and name must be there, since login fails without them. Everything beyond that, preferences, history, and billing data included, is better kept in your database, where you control it and can query it.

The third is event handling. The service can notify your back end about registration, a data change, or account deletion. Without that your database drifts from the service on every change made in the panel, and the drift is usually noticed at the first report.

The fourth is environments. One environment for production and testing means test data mixes with real data and every configuration experiment touches customers. Separating them needs no paid plan here: a free account gets one production and one non production environment, and the Pro plan allows exactly the same. Only the third environment costs extra, five dollars a month on Pro, while the higher plans include five and ten non production environments respectively. So if you plan a separate environment for testing, for previews, and for training, count them up front, because those rather than user numbers push the bill upwards.

Common mistakes

The first is checking permissions in the interface alone. A hidden button does not protect the route performing the action, so the check must be server side.

The second is mixing global roles with organisation roles. That leads to an administrator of one organisation reaching another's data, and the bug surfaces after deployment.

The third is assuming flags change instantly. The value refreshes with the token, so switching a feature off in an emergency takes effect with a delay and that belongs in your incident plan.

The fourth is estimating cost by account count. Billing runs per monthly active user, which on applications with a long tail of rarely used accounts gives an entirely different figure.

The fifth is migrating users without a plan. Moving from another service requires carrying passwords across, and they are hashed, so check format compatibility before setting a date.

The sixth is relying on this service for split traffic comparison tests. Flags suit releasing features rather than measuring impact on metrics, and the latter needs a different tool.

FAQ

What does Kinde cost?

The free plan covers up to ten and a half thousand monthly active users with every core feature. The same tier applies on the paid plans, which cost twenty five, seventy five, and two hundred and fifty dollars a month. Above the tier a per user rate applies, falling as the plan rises: 0.0175 USD on Pro, 0.0163 USD on Plus, and 0.0151 USD on Scale.

How does it differ from Clerk?

Clerk supplies ready interface components and a polished login experience, so rollout takes less time. Kinde includes organisations, roles, and feature flags, which on a product sold to companies saves adding separate tools.

Do the feature flags replace a dedicated tool?

For releasing features to selected customers and distinguishing subscription plans, yes. For split traffic comparison tests with metric measurement, no, since this solution is simpler and carries no analytics layer.

Does it support enterprise login?

Yes, and without moving to a paid plan: one connection to a customer's identity provider is included on the free plan, as is a custom SAML connection. Only the second such customer needs Plus or Scale, where the number of connections is unlimited at no extra cost. That is usually a requirement when selling to larger organisations, and the configuration details deserve checking before promising a customer a date.

Can users be migrated from another service?

Yes, and carrying passwords across is the key point. They are hashed, so import works only with a compatible format; otherwise users must set a password again. Check that before planning a migration, since it shapes the message to customers.

Documentation sits on the project site, and current price tiers on the pricing page.