--forcepushed--fp
  • Home
  • Articles
  • Resources
  • Projects

Build smarter, ship faster, and stand out from the crowd.

Subscribe or follow on X for updates when new posts go live.

Follow on X

Authentication vs Authorization in SaaS

Authentication and authorization are two terms that get used constantly in modern web development, often interchangeably and often imprecisely. I spent many years writing authentication code while giving comparatively little thought to authorization. That imbalance worked fine for internal applications deployed behind corporate firewalls, backed by standard operating procedures, and supported by service desks that could manually resolve access issues when something went wrong. Opening a public-facing SaaS product to arbitrary users changes the problem entirely.

When you expose your system to the internet, you are no longer solving a convenience problem. You are solving a security, compliance, observability, and operational problem simultaneously. That distinction becomes painfully obvious the moment you try to scale beyond a handful of users or meet real enterprise expectations.

I recently completed the migration of a SaaS platform from an internally managed authentication and authorization solution to Ory Network. The difference in capability, operational maturity, and long-term maintainability was substantial. This article breaks down why that gap exists, how authentication and authorization differ at a systems level, and why building and maintaining your own auth stack is rarely the right decision for enterprise-grade software.

Authentication: Proving Who You Are

Authentication answers a single question: are you who you claim to be? Everything else in the system depends on this being correct.

In its most basic form, authentication consists of collecting credentials, validating them against stored data, and establishing some form of session. Many developers, myself included, have written versions of this dozens of times. A typical early-stage implementation looks something like a username and password table, a password hash check, and a session cookie.

Here is a simplified example using a custom Next.js API route and a PostgreSQL-backed user table:

import bcrypt from "bcrypt";
import { serialize } from "cookie";
import { db } from "@/db";

export async function POST(req: Request) {
  const { email, password } = await req.json();

  const user = await db.user.findUnique({ where: { email } });
  if (!user) {
    return new Response("Invalid credentials", { status: 401 });
  }

  const valid = await bcrypt.compare(password, user.passwordHash);
  if (!valid) {
    return new Response("Invalid credentials", { status: 401 });
  }

  const sessionToken = crypto.randomUUID();
  await db.session.create({
    data: {
      token: sessionToken,
      userId: user.id,
      expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24),
    },
  });

  return new Response("OK", {
    headers: {
      "Set-Cookie": serialize("session", sessionToken, {
        httpOnly: true,
        secure: true,
        path: "/",
      }),
    },
  });
}

This works, and for a long time it feels sufficient. The problems emerge gradually. Password resets require additional flows and token management. Account recovery requires careful consideration of email verification and rate limiting. Multi-factor authentication introduces state machines and fallback logic. Session invalidation across devices requires bookkeeping. Audit logging becomes necessary once customers ask who logged in, when, and from where.

Most homegrown authentication systems stop at credential validation and session issuance. They lack identity lifecycle management. There is no concept of required profile completion, no built-in support for multiple authentication factors, and no consistent way to reason about identity state beyond “logged in” or “not logged in”.

This is where systems like Ory Kratos differ fundamentally. Kratos is not a login library. It is an identity system. It models identities as stateful entities with verifiable traits, recovery flows, credential rotation, session introspection, and audit-ready metadata. It supports passwordless flows, WebAuthn, social identity providers, and step-up authentication without forcing you to reinvent the same fragile mechanisms repeatedly.

For example, validating an authenticated request with Kratos does not require you to query your database directly. You ask the identity system whether a session is valid and what identity it represents:

const res = await fetch(`${ORY_KRATOS_URL}/sessions/whoami`, {
  headers: {
    cookie: req.headers.get("cookie") ?? "",
  },
});

if (!res.ok) {
  return new Response("Unauthorized", { status: 401 });
}

const session = await res.json();
const identityId = session.identity.id;

That identity is now a first-class object, not just a row in a users table. This distinction matters as soon as compliance, enterprise SSO, or customer security reviews enter the picture.

Authorization: Deciding What You Are Allowed to Do

Authorization answers a different and arguably harder question: what is this identity allowed to do right now?

In many internal systems, authorization is implemented as a role column on a user record and a handful of conditional checks scattered throughout the codebase. For example:

if (user.role !== "admin") {
  throw new Error("Forbidden");
}

This approach collapses quickly under real-world pressure. Roles become overloaded. Permissions become ambiguous. Changes require code deployments. Auditing who had access to what at a given point in time becomes nearly impossible.

Authorization is fundamentally about policy. Policies evolve independently of application logic. They need to be inspectable, testable, and enforceable consistently across services.

My experience prior to adopting a dedicated authorization system was limited to brittle role-permission mappings stored in the application database with no management UI, no policy versioning, and no reliable audit trail. Error messaging was inconsistent, and debugging access issues involved tracing conditionals through multiple services.

Ory Keto approaches authorization as a policy decision problem rather than a code branching problem. It implements a Zanzibar-style relationship-based access control model. Instead of checking roles, you ask whether a subject has a specific relation to a resource.

For example, you can express that a user owns a project, that members belong to an organization, or that editors can modify a document. These relationships live in the authorization system, not in your application code.

An authorization check using Keto looks like this:

const res = await fetch(`${ORY_KETO_URL}/relation-tuples/check`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    namespace: "projects",
    object: projectId,
    relation: "owner",
    subject_id: identityId,
  }),
});

const { allowed } = await res.json();
if (!allowed) {
  return new Response("Forbidden", { status: 403 });
}

This decoupling is critical. It allows authorization rules to evolve without redeploying application code. It enables centralized auditing and policy introspection. It supports fine-grained access models that go far beyond static roles.

Why Writing Your Own Auth System Breaks Down at Scale

Writing your own authentication and authorization system is not inherently wrong. It is often the fastest way to get started. The problem is not initial implementation. The problem is long-term ownership.

An enterprise SaaS must support regulatory requirements, customer security questionnaires, incident response, and evolving threat models. Authentication systems must handle credential compromise, session hijacking, brute force mitigation, and MFA enforcement. Authorization systems must answer questions about historical access, policy changes, and least privilege enforcement.

Each of these concerns introduces complexity that compounds over time. Every edge case becomes your responsibility. Every vulnerability becomes your incident. Every compliance gap becomes a blocker in enterprise sales cycles.

Specialized systems like Ory exist because these problems are orthogonal to your product domain. They require deep, sustained focus. Offloading them to a dedicated platform reduces risk and allows your engineering team to focus on business logic rather than security plumbing.

Alternatives and Ecosystem Considerations

Ory is not the only option in this space, but it is one of the few that provides both authentication and authorization as first-class, decoupled services that integrate cleanly with modern frameworks like Next.js.

Other authentication-focused tools such as BetterAuth offer streamlined identity flows and developer-friendly APIs, though authorization capabilities may be limited or delegated to application logic. Providers like Auth0 and Clerk offer managed identity with varying degrees of policy control, often trading flexibility for convenience. Open-source options like Keycloak provide comprehensive features but require significant operational investment.

The key distinction is whether authentication and authorization are treated as infrastructure or as libraries. Infrastructure-level systems expose APIs, policies, and audit surfaces. Libraries embed assumptions into your codebase. For enterprise SaaS, the former scales better organizationally and technically.

Putting It Together in a Real Application

In a mature architecture, authentication establishes identity via a system like Kratos. Authorization decisions are delegated to a policy engine like Keto. Your application becomes a consumer of identity and policy rather than the owner of either.

A typical request flow looks like this. A request arrives with session cookies. The application validates the session via the identity provider. The resulting identity is used to query the authorization system for specific access rights. Only then does business logic execute.

This separation enforces clarity. Authentication answers who you are. Authorization answers what you can do. Neither leaks into the other.

Closing Thoughts

The transition from internal tooling to public SaaS exposes assumptions that are easy to ignore early on. Authentication and authorization are not solved problems, but they are well-understood problem domains with mature solutions. Writing your own implementations can be educational, but operating them at enterprise scale is expensive, risky, and rarely differentiating.

Systems like Ory exist not to save you a few lines of code, but to absorb decades of hard-won lessons about identity, access control, and security operations. Leveraging them is less about convenience and more about acknowledging that some problems are better solved once and reused everywhere.

If the goal is to build a durable SaaS product rather than a clever prototype, treating authentication and authorization as infrastructure rather than application code is one of the most important architectural decisions you can make.