Authentication and Authorization

Authentication vs Authorization

ReadingPreview

আপনি একটি free preview lesson দেখছেন।

So far, we have deliberately postponed security implementation.

Our application workflows already assume concepts such as:

authenticated CustomerId

and:

ADMIN-only operation

For example, Create Order assumes:

CustomerId
→ came from authenticated identity

Product administration assumes:

only an authorized administrator
may perform this operation

Now we need to make those assumptions explicit.

Before introducing Spring Security configuration, filters, tokens, or authorities, we need to understand two different questions:

Authentication
→ Who is making this request?
Authorization
→ What is that authenticated identity allowed to do?

These concepts are related.

They are not interchangeable.

The goal of this lesson is:

Establish a clear security model where authentication determines trusted identity, authorization determines allowed operations, and resource ownership remains an application-level rule when it depends on business data.


Authentication

Authentication answers:

Who is making the request?

Conceptually:

Incoming HTTP Request
    ↓
authentication mechanism
    ↓
authenticated identity

For example, the external identity capability may establish a stable subject such as:

user-abc-123

Our backend does not need to know how that user originally proved their identity through:

password

social login

magic link

MFA

unless our backend actually owns those responsibilities.

It does not.

Our Order Management Backend receives the result of authentication.


Authorization

Authorization answers:

Is this authenticated identity allowed to perform this operation?

Examples:

Customer
→ create Order
Customer
→ cancel own Order
Admin
→ modify Product
Customer
→ modify Inventory
→ not allowed

Authentication gives us identity.

Authorization evaluates permissions using that identity and the operation being attempted.


Authentication Must Happen Before Authorization

We cannot meaningfully ask:

Is this caller an Admin?

until we know:

Who is this caller?

Conceptually:

Request
    ↓
authenticate
    ↓
AuthenticatedUser
    ↓
authorize
    ↓
Handler / UseCase

Authentication therefore establishes the trusted security context from which authorization decisions can be made.


Our Security Requirements

The requirements we have accumulated throughout the course can now be grouped.

Customer

An authenticated Customer can:

create an Order
view their own Orders
view their own Order details
cancel their own eligible Order
pay their own eligible Order

A Customer cannot operate on another Customer's Order.

Admin

An authorized Admin can:

create Products
update Products
deactivate Products
manage Inventory
view administrative Order data

Our current requirements do not need a large permission system.

These two categories are enough for now.


Do Not Confuse Identity With Role

These are different facts:

CustomerId
→ who owns this Order?

and:

ADMIN authority
→ what may the current caller do?

Suppose the same person later receives Admin access.

Their historical Order ownership does not change.

Therefore we do not model:

public record CustomerId(
        String value,
        Role role
) {
}

Identity and authorization have different responsibilities and potentially different lifecycles.


Order Stores Identity, Not Role

Our Order contains:

private final CustomerId customerId;

That records durable ownership.

It does not contain:

private Role role;

or:

private boolean admin;

because runtime authorization state is not part of historical Order ownership.

An Admin viewing an Order does not become its owner.


Authentication Comes From an External Identity Capability

Our backend does not currently own:

user registration

password storage

password reset

identity lifecycle

Those responsibilities belong outside this application.

Our security architecture is therefore conceptually:

External Identity System
        ↓
authentication result
        ↓
Spring Security
        ↓
application security context
        ↓
CustomerId / authorities

We do not create a local Customer subsystem merely because authenticated Customers exist.


No Local Customer Table Is Required

Authentication does not imply:

customers table

Our application currently needs:

stable CustomerId

to associate Orders with authenticated Customers.

That does not require:

CustomerEntity

CustomerRepository

CustomerService

CustomerController

The identity provider owns account lifecycle.

Our application owns Order state.


Authentication Result vs Application Identity

A security framework may eventually expose something like:

AuthenticatedUser

containing:

subject

authorities

The application may then derive:

CustomerId

from the trusted subject.

Conceptually:

AuthenticatedUser
    ↓
stable subject
    ↓
CustomerId

The exact external token claim or provider-specific field is not defined in this lesson.

We do not invent:

JWT claim names

issuer

OAuth flow

token URL

before integrating the real authentication contract.


CustomerId Remains an Application Concept

We already use:

public record CustomerId(
        String value
) {

    public CustomerId {
        if (
                value == null ||
                value.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Customer ID is required"
            );
        }
    }
}

This represents:

stable business identity reference

It does not represent:

access token

session ID

email

display name

unless the external identity contract explicitly defines one of those values as the stable subject.


Credentials Are Not Identity

An access token may prove authentication.

But the token itself is usually:

temporary

rotatable

expiring

It should not become:

orders.customer_id

Likewise:

session ID

is not a durable Customer identity.

The backend stores:

stable identity reference

not authentication credentials.


Never Trust customerId From the Request Body

Consider:

{
  "customerId": "customer-999",
  "items": [
    {
      "productId": "9ed4c9a9-8761-4e4f-9f50-aef83f14c13d",
      "quantity": 1
    }
  ]
}

If Order ownership is determined by authentication, this API is unsafe.

A caller could attempt:

create an Order
owned by someone else

Our correct Create Order request remains:

{
  "items": [
    {
      "productId": "9ed4c9a9-8761-4e4f-9f50-aef83f14c13d",
      "quantity": 1
    }
  ]
}

Ownership comes from:

authenticated identity
    ↓
CustomerId

not client input.


Client Controls Intent, Server Controls Authority

This principle now becomes especially important.

The client can say:

I want to order Product X
quantity 2

The client cannot authoritatively say:

I am Customer Y

I am an Admin

I own Order Z

Those facts come from trusted server-side context and persisted application state.


Do Not Accept isAdmin

This would be obviously wrong:

{
  "name": "Mechanical Keyboard",
  "priceCents": 8990,
  "isAdmin": true
}

or:

new CreateProductCommand(
        name,
        priceCents,
        true
);

where:

true
→ caller is Admin

A client cannot grant itself authority.

Admin access must come from authenticated security context.


Security Context Should Not Leak Into Domain

This would be wrong inside Order:

public void cancel() {
    Authentication authentication =
            SecurityContextHolder
                    .getContext()
                    .getAuthentication();

    // determine current user...
}

Now the Domain depends on:

Spring Security

thread-local security state

framework infrastructure

Our Domain should still be testable using:

plain Java

without authentication infrastructure.


UseCases Should Not Depend on Spring Authentication

Avoid:

createOrderUseCase.execute(
        authentication,
        command
);

Now application code knows Spring Security directly.

Instead, the security/HTTP boundary extracts the application concept:

CustomerId customerId =
        currentUser.customerId();

Then:

createOrderUseCase.execute(
        new CreateOrderCommand(
                customerId,
                items
        )
);

The UseCase receives only what it needs.


Security Integration Is an Adapter

A useful mental model is:

Spring Security Authentication
        ↓
security adapter
        ↓
application-friendly identity

The adapter understands framework-specific security representation.

The application does not.

Conceptually, Module 8 may introduce something like:

AuthenticatedUser

CurrentUser

or equivalent.

The exact implementation comes later.


Authorization Has Two Levels

For our application, authorization happens broadly at two levels:

Endpoint-level authorization

and:

Resource-level authorization

They solve different problems.


Endpoint-Level Authorization

Some operations have simple role requirements.

For example:

POST /api/v1/products
→ Admin only
PATCH /api/v1/products/{productId}
→ Admin only
POST /api/v1/products/{productId}/deactivate
→ Admin only
PUT /api/v1/inventory/{productId}
→ Admin only

The authorization decision does not require loading a Product or Inventory row to discover who owns it.

This is a good fit for security-layer authorization.

Conceptually:

authenticated caller
    ↓
has ADMIN authority?
    ├── no → reject
    └── yes → Handler

Resource-Level Authorization

Now consider:

POST /api/v1/orders/{orderId}/cancel

Knowing that the caller is:

CUSTOMER

is not enough.

We also need to know:

Does this Order belong
to this Customer?

That depends on application data.

So authorization requires:

authenticated CustomerId

+

Order ownership

This is resource-level authorization.


Authentication Does Not Prove Ownership

Suppose:

Customer A

is fully authenticated.

They request:

Order belonging to Customer B

Authentication succeeds.

But authorization must still fail.

Therefore:

authenticated

does not mean:

authorized for every resource

This distinction is fundamental.


Ownership-Aware Repository Queries

Our canonical Order operations already support ownership-aware persistence.

For example:

Optional<Order> findOwnedForUpdate(
        OrderId orderId,
        CustomerId customerId
);

For ordinary detail reads we can similarly use an ownership-scoped query.

Conceptually:

WHERE id = :orderId
  AND customer_id = :customerId

Then:

missing Order

and:

Order owned by someone else

both produce:

ORDER_NOT_FOUND

for Customer-facing operations.


Why Return 404 Instead of 403 for Another Customer's Order?

Suppose Customer A requests Customer B's Order.

Returning:

403 ACCESS_DENIED

can reveal:

that Order exists

Our accepted policy is to hide that distinction for Customer-owned Orders.

So:

does not exist

and:

not owned by caller

both become:

404 ORDER_NOT_FOUND

This is an application resource-access policy.

It does not mean:

403 is never useful

For a Customer calling an Admin Inventory endpoint, explicit:

ACCESS_DENIED
→ 403

is perfectly appropriate.


Authentication Error

If a protected endpoint receives no valid authenticated identity:

AUTHENTICATION_REQUIRED

maps to:

401 Unauthorized

The request never reaches business processing as an authenticated Customer or Admin.

The Domain must not throw this error.

It belongs to the security boundary.


Authorization Error

If a valid authenticated Customer tries to perform an Admin-only operation:

ACCESS_DENIED

maps to:

403 Forbidden

For example:

PUT /api/v1/inventory/{productId}

from a regular Customer.

The caller's identity is known.

They simply lack authority for the operation.


401 vs 403 vs 404

These meanings should remain clear:

401
→ caller has not been successfully authenticated
403
→ caller is authenticated
   but lacks permission for this operation
404
→ requested resource is not visible/found
   under this application's access policy

For Customer-owned Orders, our application may deliberately use:

404

instead of:

403

for ownership failures.


Customer and Admin Are Authorization Concepts

Our requirements currently use:

Customer

Admin

as useful application language.

We should not assume that the external identity provider literally sends:

ROLE_CUSTOMER

ROLE_ADMIN

claims.

The future security adapter may need to map provider-specific representation into application authorities.

Conceptually:

external security representation
        ↓
security integration
        ↓
application authorities

This lesson defines the application rule.

It does not invent the provider contract.


Admin Does Not Become Order Owner

Suppose an Admin can:

view all Orders

That is authorization.

It does not change:

Order.customerId

The Order remains owned by the Customer who created it.

Authorization to inspect or administer a resource is not ownership.


Create Order Security Flow

Conceptually:

POST /api/v1/orders
    ↓
authentication
    ↓
AuthenticatedUser
    ↓
CustomerId
    ↓
OrderHandler
    ↓
CreateOrderUseCase
    ↓
Order.customerId =
authenticated CustomerId

The request body contains:

items

not:

customerId

This creates trustworthy Order ownership.


Order History Security Flow

Requirement:

Customer can view only their own Orders.

Flow:

authenticated CustomerId
        ↓
GET /api/v1/orders
        ↓
GetOrderHistoryUseCase
        ↓
OrderRepository
        ↓
WHERE customer_id =
authenticated CustomerId

The Customer does not send:

GET /api/v1/orders?customerId=someone-else

Customer identity is server-controlled context.


Order Detail Security Flow

For:

GET /api/v1/orders/{orderId}

the application should use:

authenticated CustomerId

+

OrderId

to find the visible resource.

Conceptually:

findOwned(
    orderId,
    customerId
)

This makes ownership harder to accidentally forget.


Cancellation Security Flow

We already designed:

authenticated CustomerId
        ↓
CancelOrderUseCase
        ↓
findOwnedForUpdate(
    orderId,
    customerId
)
        ↓
order.cancel()
        ↓
restore Inventory

There are two separate decisions:

Does this Customer own the Order?
→ authorization/application
Can this Order be cancelled?
→ Domain lifecycle

They should remain separate.


Payment Security Flow

The same principle applies later:

authenticated CustomerId
        ↓
PayOrderUseCase
        ↓
load owned Order
        ↓
verify payment eligibility
        ↓
PaymentService

The external Payment Provider does not decide:

who owns this Order

Our application does.


Product Administration Flow

Conceptually:

PATCH /api/v1/products/{productId}
    ↓
authentication
    ↓
authorization
    ↓
ADMIN authority required
    ↓
ProductHandler
    ↓
UpdateProductUseCase

The Product Domain does not ask:

is caller Admin?

That is an operation-access rule, not a Product invariant.


Inventory Administration Flow

Likewise:

PUT /api/v1/inventory/{productId}
    ↓
authenticated caller
    ↓
ADMIN authority?
    ├── no
    │   → ACCESS_DENIED
    │
    └── yes
        ↓
    SetInventoryUseCase

Inventory should not contain:

if (!currentUser.isAdmin()) {
    ...
}

Security remains outside the Domain.


Public Product Browsing

Our existing requirements define Customer Product browsing behavior:

active Product

AND

positive Inventory

But they have not yet required us to decide whether:

GET /api/v1/products

must be:

public

or:

authenticated-only

We do not invent that access policy in this lesson.

When configuring endpoint security, we will make the decision from the actual product requirement.


Authentication Is Not Business Validation

Suppose an authenticated Customer creates an Order.

Security establishes:

this request belongs to Customer A

The application must still validate:

Product exists

Product active

Inventory available

Authentication does not replace business validation.


Authorization Is Not Domain Lifecycle Validation

Likewise:

Customer owns Order

does not mean:

Order can be cancelled

The complete cancellation decision is:

authorization
→ Customer owns Order

then:

Domain
→ status is UNPAID

Both conditions are required.


Domain Rules Should Not Depend on Roles

Consider:

PAID Order cannot be cancelled in v1

That Domain rule should remain true independently of:

Customer

Admin

If a future Admin refund workflow is introduced, that would be a different business operation.

We should not weaken:

order.cancel();

into:

order.cancel(
        boolean isAdmin
);

to anticipate requirements that do not exist.


Do Not Put Roles Into Commands Without Need

Avoid:

new CancelOrderCommand(
        customerId,
        orderId,
        Role.CUSTOMER
);

if the UseCase only needs:

trusted CustomerId

for ownership.

Likewise, Admin Product UseCases should not receive:

isAdmin = true

after the security boundary has already authorized the operation.

Pass business/application inputs.

Do not repeatedly propagate security implementation details.


Current User Abstraction

A useful future abstraction may look conceptually like:

CurrentUser

which can answer application-relevant questions such as:

authenticated identity

authorities

The Handler/security adapter can obtain:

CustomerId

or enforce:

ADMIN authority

without exposing Spring Security throughout the application.

We will implement the concrete shape in the next lesson rather than guessing prematurely.


No Direct Identity Provider Call Is Required Yet

If Spring Security can validate authentication and expose a stable subject, our application may already have everything needed for:

Order ownership

Admin authorization

There is no reason to call an external identity API for every request.

Do not automatically introduce:

IdentityService

simply because authentication is external.

Introduce an external Service only if a workflow genuinely needs to call that system.


Security Architecture

The intended architecture is:

Incoming Request
      ↓
Spring Security
      ↓
Authenticated Identity
      ↓
Security Integration
      ↓
Handler
      ↓
application-friendly context
      ↓
UseCase
      ↓
Domain / Repository

Spring Security stays near the outer boundary.

Domain logic stays framework-light.


Responsibility Map

External Identity Capability

Owns:

authentication

credential management

identity lifecycle

Security Integration

Owns:

interpret authenticated principal

map trusted identity
to application context

map external authorities
to application authorization

Handler / API Boundary

Owns:

receiving authenticated context

mapping HTTP input

passing trusted application identity
into UseCases

UseCase

Owns:

resource-level ownership

operation-specific authorization
when application data is required

Domain

Owns:

business invariants

lifecycle rules

and does not authenticate users.

Repository

Owns:

ownership-scoped queries
when useful for safe access

but does not read Spring Security context.


Why Controller Annotations Alone Are Not Enough

An annotation can express something like:

ADMIN authority required

or:

authenticated user required

That works well for coarse endpoint authorization.

But it cannot necessarily answer:

Does Customer A own Order X?

without loading application data.

So:

Framework authorization and application ownership rules complement each other.

We need both.


Do Not Put Everything in Spring Security

It can be tempting to encode every rule into complex security expressions.

For example:

Can this Customer cancel
this specific Order
in its current state?

But that question combines:

identity

ownership

Order state

business lifecycle

Our existing architecture already has natural homes for those concerns.

Keep:

authentication + coarse authority
→ security boundary
ownership
→ application workflow / Repository
cancellability
→ Order Domain

This separation remains easier to reason about and test.


Testing Strategy

Security will require several levels of tests.

Domain Tests

Should not need Spring Security.

For example:

UNPAID Order can be cancelled

PAID Order cannot be cancelled

No authenticated user is required to test lifecycle behavior.

UseCase Tests

Can use explicit application identity:

CustomerId customerId =
        new CustomerId(
                "customer-1"
        );

Then verify:

Customer can access own Order

Customer cannot access another Customer's Order

No token parsing is required.

Security Integration Tests

Later, when Spring Security is wired, verify:

protected endpoint rejects unauthenticated request

Customer cannot access Admin endpoint

Admin can access Admin endpoint

authenticated identity becomes correct CustomerId

This keeps each test focused on the layer it actually verifies.


Error Semantics

Our previous error model already established:

no valid authentication
→ AUTHENTICATION_REQUIRED
→ 401
authenticated but forbidden
→ ACCESS_DENIED
→ 403
Customer cannot see requested Order
→ ORDER_NOT_FOUND
→ 404

Security implementation must preserve these semantics.

It should not introduce inconsistent ad-hoc responses.


Common Mistakes

Avoid:

customerId from request body

for authenticated Customer workflows.

Avoid:

isAdmin from request

or from a command supplied by the client.

Avoid storing:

access token

as Order ownership identity.

Avoid using:

email

name

username

as stable identity without an explicit identity-provider contract.

Avoid:

SecurityContextHolder

inside Domain objects.

Avoid passing:

Authentication

through every UseCase.

Avoid storing:

role

on Order.

Avoid building:

CustomerEntity

CustomerRepository

customers table

without local Customer state.

Avoid assuming:

authenticated
→ authorized for every resource

Avoid using only endpoint-level role checks for resource ownership.

Avoid exposing another Customer's resource existence unnecessarily.


Security Review Checklist

For every protected operation, ask:

Does this endpoint require authentication?

Where does trusted identity come from?

Is identity coming from the request body?

Does the operation require a role/authority?

Does authorization depend on application data?

Is resource ownership involved?

Should non-owned resources appear as 404?

Does the Domain accidentally know Spring Security?

Are credentials being confused with durable identity?

Are roles being confused with ownership?

Can this rule be tested without the authentication framework?

Our Current Security Model

The model is now:

Authentication
→ establish trusted caller identity
Authorization
→ determine permitted operations
Ownership
→ determine whether a specific resource
belongs to the authenticated Customer
Domain lifecycle
→ determine whether the requested
business transition itself is valid

These are separate layers of reasoning.


Example — Cancel Order

A cancellation request must pass all of these questions:

1. Is there an authenticated caller?
2. Is the caller acting as a Customer
for this Customer endpoint?
3. Does this Order belong
to that Customer?
4. Is the Order currently UNPAID?

Then and only then:

cancel Order

restore Inventory

Notice how each question belongs to a different responsibility.


Example — Update Product

Product update is simpler:

1. Is the caller authenticated?
2. Does the caller have Admin authority?
3. Does the Product exist?
4. Is the requested Product change valid?

There is no Customer ownership check.

Different operations need different authorization models.


What We Deliberately Do Not Add

We do not yet introduce:

JWT-specific claims

OAuth flows

identity-provider URLs

token issuer details

token refresh logic

password management

Customer table

custom permission database

ACL framework

resource-policy engine

Spring Security annotations everywhere

Those details should follow actual requirements and the concrete authentication integration.

This lesson defines the security model first.


Engineering Principle

The core principle is:

Authentication determines who the caller is. Authorization determines what that caller is allowed to do. Resource ownership is a form of authorization that may require application data and therefore cannot always be solved by endpoint-level role checks alone.

Another:

Trusted identity comes from the security boundary, never from client-controlled business input. The client expresses intent; the backend determines identity, authority, ownership, and business validity.

And:

Keep security infrastructure near the application boundary. UseCases should receive application-friendly identity such as CustomerId, while Domain objects remain unaware of Spring Security, tokens, roles, and request context.


Summary

In this lesson, we established that:

  • Authentication asks who is making the request.
  • Authorization asks what the authenticated identity may do.
  • Authentication happens before authorization.
  • Customer and Admin capabilities have different authorization requirements.
  • Authentication is provided by an external identity capability rather than a local Customer subsystem.
  • The backend does not need a Customer table merely to authenticate Customers.
  • Stable Customer ownership is represented using CustomerId.
  • Credentials such as tokens and sessions are not durable Customer identity.
  • CustomerId must not come from authenticated Customer request bodies.
  • Admin authority must never come from client-controlled isAdmin input.
  • Identity and role are different concepts.
  • Order stores Customer ownership but does not store runtime roles.
  • Spring Security details must not leak into Domain objects.
  • UseCases should not depend directly on Spring Security Authentication.
  • Security integration maps framework/external identity into application-friendly context.
  • Endpoint-level authorization works well for coarse Admin/Customer restrictions.
  • Resource-level authorization is required for Customer-owned Orders.
  • Authentication alone does not prove resource ownership.
  • Ownership-aware Repository queries help enforce Customer access safely.
  • Missing and non-owned Customer Orders may both return ORDER_NOT_FOUND.
  • 401 represents missing/failed authentication.
  • 403 represents an authenticated caller lacking operation-level permission.
  • 404 may intentionally hide another Customer's resource.
  • Admin access does not change Order ownership.
  • Create Order derives ownership from authenticated CustomerId.
  • Customer Order history is automatically scoped to authenticated CustomerId.
  • Cancellation separates authentication, ownership, and Domain lifecycle checks.
  • Payment will use the same ownership principle.
  • Product and Inventory administration use Admin authorization.
  • We do not yet invent whether Product browsing must be public or authenticated.
  • Security does not replace Product, Inventory, or Order business validation.
  • Domain lifecycle rules do not depend on role.
  • A CurrentUser or AuthenticatedUser abstraction may isolate framework security details.
  • We do not need a direct IdentityService call if authenticated context already provides everything required.
  • Domain tests remain independent from Spring Security.
  • UseCase ownership tests can use explicit CustomerId values.
  • Security integration tests will verify real endpoint authentication and authorization behavior.

Next lesson:

Integrating Authenticated User Identity

There we will connect the conceptual model to the application boundary:

Bearer authentication
    ↓
Spring Security
    ↓
AuthenticatedUser / CurrentUser
    ↓
stable subject
    ↓
CustomerId

while keeping Spring Security types out of UseCases and Domain objects, preserving the external identity system as the authority, and avoiding a fake local Customer subsystem.