All posts

Understanding Unauthenticated Traffic: How Applications and APIs Represent The Unauthenticated User

Authentication is one of the most fundamental concepts in application security. Most developers spend considerable time designing how authenticated users are represented, authorized, and managed. Surprisingly, much less attention is given to the opposite state of users who have not authenticated at all.

From a security and architecture perspective, an application must still make decisions about unauthenticated traffic. The system needs to answer questions such as:

  • Is this user anonymous?
  • Is this a new visitor?
  • Have we seen this client before?
  • Should rate limits apply?
  • Can this request access public resources?

The answer often depends on how the application represents an unauthenticated user.

Contrary to what many developers assume, unauthenticated users are not always represented by the complete absence of credentials.

The Simplest Representation: Nothing

The most straightforward representation of an unauthenticated request is the absence of authentication information entirely.

For example:

GET /api/products HTTP/1.1Host: example.com

No session cookie.

No bearer token.

No API key.

No authentication headers.

The application simply treats the request as anonymous.

Many public APIs and websites use this model for publicly accessible resources.

Advantages:

  • Simple implementation
  • Minimal overhead
  • Easy to reason about

Challenges:

  • No way to distinguish one anonymous visitor from another
  • Difficult to apply user-specific rate limits
  • Limited personalization

In this model, “unauthenticated” literally means “nothing was provided.”

Over the years, this has become my preferred approach in the absence of additional requirements. I usually don’t get much push back on this, but a while back I was at a client site where the IAM team (which didn’t really do anything other than manage identity infrastructure) insisted that XYZ is large, mature organization that must deploy enterprise patterns in everything they do. One of my favorite sayings comes to mind, “Reaching for the stars while tripping over the pebble in front of you.” That really sums up any observations that I could make about that situation. The Identity Architect in that situation insisted that a generic identity that is understood to represent an unauthenticated session must be used. We’ll talk more about that below.

Anonymous Session Cookies

Many web applications create a session before authentication occurs.

Consider an e-commerce site:

  1. Visitor arrives.
  2. Application creates a session.
  3. Session ID is stored in a cookie.
  4. Visitor adds items to a shopping cart.
  5. Visitor eventually logs in.

The session exists even though the user has not authenticated.

Example:

Cookie: SESSIONID=abc123

The associated session record may contain:

{  "authenticated": false,  "cartItems": 3}

In this design, the user is unauthenticated but still has server-side state.

Advantages:

  • Shopping carts work before login
  • Personalization is possible
  • Easier analytics and tracking

Challenges:

  • Session storage requirements
  • Session fixation concerns
  • Additional complexity

This is extremely common in traditional web applications. One may even see it in APIs that are using cookies to track sessions in an API (don’t do this).

Anonymous JWTs or Bearer Tokens

Some systems issue tokens to users who have not authenticated.

Example:

Authorization: Bearer eyJ...

The token might contain claims such as:

{  "sub": "anonymous-12345",  "authenticated": false,  "role": "guest"}

At first glance this may seem strange. Why issue a token to someone who is not authenticated?

There are several reasons:

  • Device tracking
  • Rate limiting
  • Shopping carts
  • Guest user experiences
  • Progressive registration flows

In this design, the application has positively identified a client instance without verifying the user’s identity.

The user is known, but not authenticated. Still, a user session exists, but it is not an authenticated session.

The bearer token could describe a generic user identity that exists in the user repository (and is understood to be the “Unauthenticated” identity) or it could describe some transient unauthenticated user concept.

Guest Accounts

Some applications explicitly create guest users. This would be the transient, unauthenticated user concept I mentioned.

Example:

{  "userId": "guest-98765",  "role": "guest",  "authenticated": false}

From the application’s perspective, every visitor is represented by a user object.

Authenticated users have verified identities.

Guest users do not.

This model simplifies authorization logic because the system always operates on a user record.

Common examples include:

  • Gaming platforms
  • Mobile applications
  • SaaS trials
  • AI chat applications

Device Identifiers

Mobile applications frequently use device identifiers before authentication occurs.

Examples include:

{  "deviceId": "a8d4f7e2",  "authenticated": false}

This allows systems to:

  • Apply rate limits
  • Detect fraud
  • Store preferences
  • Maintain application state

The device may be known even though the person using it is not.

API Keys with Anonymous Access

Some APIs issue public API keys.

Example:

X-API-Key: public-demo-key

The key identifies the application or consumer but not an authenticated user.

In this scenario:

  • The client is identified.
  • The user is not authenticated.

This distinction is important because identification and authentication are not the same thing.

Security Context Objects

Many frameworks internally represent every request with a security context.

Authenticated request:

{  "principal": "alice",  "authenticated": true}

Unauthenticated request:

{  "principal": "anonymous",  "authenticated": false}

Frameworks such as Spring Security often create an anonymous principal automatically.

This allows authorization code to operate consistently regardless of authentication state.

Why Applications Often Represent Anonymous Users Explicitly

Developers frequently ask if the user isn’t authenticated, why not simply treat them as nothing?

The answer is that applications often need to maintain state.

Examples include:

  • Shopping carts
  • Language preferences
  • Rate limiting
  • Fraud detection
  • A/B testing
  • User behavior analytics
  • Multi-step workflow

Representing anonymous users explicitly allows these capabilities without requiring authentication.

Security Considerations

One of the most common security mistakes is confusing anonymous identities with authenticated identities.

For example:

{  "userId": "guest-123",  "authenticated": false}

should never be treated the same as:

{  "userId": "alice",  "authenticated": true}

Applications should always distinguish:

  • Authentication state
  • Identity state
  • Authorization state

These are separate concepts.

An anonymous user may have an identifier.

An identified device may have a token.

A session may exist.

None of those facts necessarily mean the user has authenticated.

There needs to be a well-understood transition from an authenticated session (its representation, its state, etc) to an authenticated session.

Make authentication, user session tracking, device tracking as sophisticated as it needs to be to satisfy the application and non-functional security requirements, but no more so.

This functionality may be part of a larger enterprise approach to identity that is owned by an IAM team or application framework team that is working with the IAM team. Requirements for the non-functional security requirements will be driven by that larger strategy in such cases.

Use standardized libraries and protocols to implement authentication and session tracking. Follow the advice given in my Authentication Series and Authorization Series.

Summary

Unauthenticated traffic can be represented in many different ways. Some systems represent anonymous users as nothing at all, while others create sessions, issue tokens, assign guest accounts, or maintain device identities.

The key architectural distinction is that identification and state management do not necessarily imply authentication. An application may know quite a bit about a visitor while still having no verified knowledge of who that visitor actually is.

Understanding these patterns helps developers build authentication systems that are both secure and flexible while avoiding the common mistake of treating anonymous state as authenticated identity.

Notes

  • AI / GenAI / ChatGPT / etc were not used to generate the text of this article.
  • ChatGPT was used to generate the images.
  • I used em dashes in my writing before the current GenAI wave was a thing. Not planning on changing now.
  • Names have been changed to protect the guilty.
  • None of the hostnames or users used in examples actually exist.
  • Feel free to post any comments or suggestions below.

Originally published on Medium.