Identity management is one of the architectural concerns that appears in almost every serious application.
It does not belong to one screen, one controller, one microservice, or one database table. It cuts across the system. Every protected feature needs to know who is calling it and what that caller is allowed to do.
That is why identity management should be designed as a shared capability, not rebuilt separately inside every application.
The Problem
In small applications, it is tempting to implement login, password checks, user lookup, and permission rules directly in the application code.
That may work at the beginning, but it creates problems as soon as the system grows.
Weak approach:
Application A -> custom users table
Application B -> custom login logic
Application C -> separate permission rules
Microservice D -> another token check
This creates duplicated logic, inconsistent permissions, and more places where security can fail.
In a microservices architecture, the problem becomes even more visible. Each component may expose its own functionality. Each component may need to identify the caller and enforce access rules. If every service solves this differently, the system becomes hard to secure and hard to operate.
A better approach is to define a company-wide identity strategy and make applications integrate with it.
Better approach:
Client
|
v
IAM platform
|
+--> Application A
+--> Application B
+--> Microservice C
+--> Microservice D
The goal is not only safer login. The goal is reusable identity infrastructure.
Authentication and Authorization
Identity management starts with two different concepts: authentication and authorization.
Authentication answers this question:
Who are you?
Authorization answers this question:
What are you allowed to do?
They are connected, but they are not the same.
A user can authenticate successfully and still be blocked from a feature. For example, a normal customer may sign in correctly but still not be allowed to access an administration endpoint.
Request
|
v
Authentication: identify the caller
|
v
Authorization: check the requested action
|
v
Allow or deny
Both concepts apply to two common scenarios.
The first scenario is interactive access. A human user signs in through a browser, mobile application, internal portal, or another user-facing channel.
The second scenario is machine-to-machine access. One application, batch job, worker, or backend component calls another service without direct human interaction.
Both cases need identity checks. The credentials and flow may differ, but the architectural need is the same.
Authentication Factors
Authentication is based on something the caller can present as proof.
There are three common groups of authentication factors.
| Factor group | Examples |
|---|---|
| Something the user knows | Password, PIN, unlock sequence |
| Something the user has | Badge, hardware token, certificate, software token |
| Something the user is | Fingerprint, face identification, signature |
A username is usually not an authentication factor by itself. It is commonly public or easy to guess. The username identifies the account, while the authentication factor proves control over that account.
For stronger security, combine more than one factor. This is multi-factor authentication.
Single factor:
username + password
Multi-factor:
username + password + token
Strong authentication:
factor from one group + factor from another group
In sensitive systems, such as banking or payment systems, strong authentication may be required by policy or regulation.
Authentication factors should also have policies. Passwords may require length and complexity rules. Tokens may expire. Sessions may have limited lifetimes. These policies reduce risk when a credential is guessed, stolen, copied, or reused.
User Stores
Authentication needs a place to store user information.
A common enterprise option is LDAP. LDAP is a protocol used to store and query user information such as usernames, emails, phone numbers, and passwords. Authentication against an LDAP server is commonly called Bind. Microsoft Active Directory is a well-known enterprise implementation in this space.
A relational database can also store user information, but database schemas for identity are usually custom. That means another application cannot automatically understand the table layout unless it has been designed for that exact schema.
Files can also be used for small or testing scenarios. A classic example is .htpasswd, originally associated with Apache HTTP Server authentication. This approach is simple, but it is not a scalable identity strategy.
The user store should be hidden behind the identity layer whenever possible.
Application
|
v
IAM
|
+--> LDAP
+--> Database
+--> External identity provider
Applications should not need to know where the password lives. They should depend on the identity service, not on the storage technology.
Password Storage
Passwords must never be stored as cleartext.
If an attacker gains access to the user store, cleartext passwords immediately become useful. That is dangerous not only for the current application but also for other systems where users may reuse credentials.
One option is symmetric encryption, such as encrypting the password with a secret key. This is better than cleartext, but it creates another problem: the key must be stored safely. If an attacker gets both the encrypted password and the key, the original password can be recovered.
A better model is password hashing.
Registration:
password -> hash function -> stored hash
Login:
provided password -> same hash function -> compare with stored hash
With a proper hashing approach, the original password is not meant to be recovered. The system validates the password by applying the same hashing process and comparing the result.
Even hashed passwords are not automatically safe forever. Attackers can try brute force attacks or use precomputed hash lookup techniques such as rainbow tables. Stronger passwords and salting make these attacks harder.
The practical rule is simple:
Never store cleartext passwords.
Prefer one-way password hashing.
Use password policies and salting.
Protect the user store anyway.
Authorization with RBAC
After authentication, the system must decide what the caller can do.
The simplest authorization model is allowing every authenticated user to do everything. That is rarely acceptable in real applications.
A better approach is to define permissions and group them into roles.
Role: CUSTOMER
Permissions:
- View own profile
- View own payment history
- Create payment
Role: ADMIN
Permissions:
- View customer profile
- Disable user
- Review disputed payment
This is Role-Based Access Control, or RBAC.
A role links users to permissions. Roles often map to job functions, departments, customer types, or operational responsibilities.
Some RBAC implementations allow one user to have multiple roles. The common behavior is additive: the user receives the permissions from all assigned roles. Some systems may behave differently when permissions conflict, so this rule should be documented.
Role inheritance is also common.
SUPPORT_AGENT
|
v
SENIOR_SUPPORT_AGENT
|
v
SUPPORT_MANAGER
A child role can inherit permissions from a parent role and add more specialized capabilities.
In the Java Enterprise world, JAAS, the Java Authentication and Authorization Service, is a standard related to authentication and authorization and can be considered a reference point for RBAC-based security.
Authorization with PBAC
RBAC is not the only model.
Policy-Based Access Control, or PBAC, calculates permission from attributes and Boolean logic.
A policy can use facts about the user, the request, the resource, or the environment.
Allow payment approval when:
user belongs to group Finance
AND request amount is below approval limit
AND request comes from trusted network
AND current time is within business hours
PBAC is useful when permissions depend on more than a static role.
Examples of policy attributes include:
User group
Source IP
Time of day
Geographical location
Resource owner
Requested operation
RBAC is easier to understand and operate. PBAC is more flexible. Many real systems use a combination of both.
IAM as the Shared Security Layer
Identity and Access Management, or IAM, provides authentication, authorization, and related identity services in a unified way.
An IAM system can provide several important capabilities.
It can decouple the user store from the application. The user data may live in LDAP, a database, or several systems, but the application integrates with the IAM layer.
It can federate other identity systems. This is useful when different organizations or identity providers need to participate in the same access flow.
It can provide Single Sign-On. With SSO, a user signs in once and can access multiple configured applications without repeating the login process for every application.
A typical web application flow can look like this:
1. User requests protected page.
2. Application checks whether the request is authenticated.
3. If not authenticated, the user is redirected to the IAM login flow.
4. IAM validates credentials.
5. IAM returns an identifier or token.
6. Application uses the token to recognize the user.
7. Application checks permissions before serving the resource.
In Java web applications, a common implementation point is a servlet filter that intercepts requests before they reach protected application logic. Other approaches include agents and reverse proxies.
A conceptual Java-style flow can look like this:
final class SecurityGate {
Response handle(Request request) {
if (!request.isProtectedResource()) {
return continueRequest(request);
}
Identity identity = identityProvider.resolve(request);
if (identity == null) {
return redirectToLogin();
}
if (!authorizationService.canAccess(identity, request.resource(), request.action())) {
return denyAccess();
}
return continueRequest(request);
}
}
The application does not own the full identity system. It only integrates with it and enforces the decision at the right boundary.
Token-Based Identity
Once a user authenticates, the system needs a way to recognize that user across later requests.
A common approach is a session token. The token acts like an identity card for the session. It can be stored by the client, often in a cookie, and usually has a limited lifespan.
Login success
|
v
Create token
|
v
Client stores token
|
v
Client sends token with later requests
|
v
Application validates token
OAuth is a standard way to support this kind of scenario.
The important point of design is that the token must be treated as sensitive. If a token is stolen, an attacker may be able to reuse it until it expires or is revoked.
Common Mistakes
The first mistake is building a separate identity system for every application. This creates inconsistent behavior and increases maintenance costs.
The second mistake is confusing authentication with authorization. A valid login does not mean unlimited access.
The third mistake is storing passwords in cleartext or using reversible encryption without strong key protection.
The fourth mistake is designing roles that are too broad. Roles should express real permission boundaries, not just convenience groups.
The fifth mistake is ignoring machine-to-machine access. Backend services also need identity and access rules.
The sixth mistake is letting applications depend directly on identity storage details. The IAM layer should hide those details.
Checklist
- Authentication and authorization are modeled separately.
- Interactive and machine-to-machine access are both considered.
- Authentication factors are classified and protected.
- MFA is used where stronger assurance is needed.
- Passwords are not stored in cleartext.
- Password hashing and salting are used where passwords are stored.
- Applications do not directly depend on user store implementation details.
- RBAC roles map to real permission groups.
- PBAC policies are used where contextual decisions are needed.
- IAM integration is standardized across applications.
- SSO behavior is documented.
- Tokens have limited lifetimes and are treated as sensitive.
Conclusion
Identity management is not a side feature. It is a foundational architectural capability.
For Java systems, especially enterprise and microservices systems, the safest direction is to centralize identity through an IAM strategy, separate authentication from authorization, protect credentials properly, and make every application enforce access decisions consistently.
A good identity design reduces duplicated code, improves security, and gives teams a reusable foundation for future applications.