Design IAM systems that balance security with usability. Covers authentication protocols, authorization models, identity federation, session management, API key patterns, machine-to-machine auth, and the IAM architecture decisions that protect without paralyzing.
Identity and Access Management Architecture defines the structure, protocols, data flows, and operational practices that govern how users, services, and systems authenticate and authorize access across a distributed environment. It matters when a system must securely and consistently manage identities across multiple applications, cloud environments, and hybrid infrastructures—especially when users, devices, and APIs must dynamically gain or lose access based on policies, roles, and context.
User identity begins with provisioning: creating a user record with attributes such as email, given_name, family_name, preferred_username, locale, and timezone. This record is stored in an identity store—typically a relational database or directory service like LDAP. The identity is uniquely identified by a subject_id (e.g., sub in JWT tokens), and each identity may have one or more identities (e.g., email, phone, saml:entity_id).
A user signs up via a web form or API endpoint, triggering a POST /api/v1/users with:
{
"email": "[email protected]",
"given_name": "Alice",
"family_name": "Smith",
"preferred_username": "alice-smith",
"locale": "en-US",
"timezone": "America/New_York"
}
The system validates the email, assigns a unique subject_id (e.g., sub=usr_9a2b3c), and persists the record. The identity is now *provisioned*. Upon login, the user receives a session cookie with Set-Cookie: auth_session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; HttpOnly; Secure; SameSite=Lax.
After sign-up, the system sends a verification email with a link containing a token parameter. The token is a JWT with iss="verify-email", aud="identity-service", and exp set to 1 hour. The endpoint GET /api/v1/verify-email?token=... validates the token and updates the user’s email_verified flag to true.
Multi-factor authentication (MFA) is implemented using the TOTP (Time-based One-Time Password) standard. The user enrolls by scanning a QR code generated via GET /api/v1/mfa/qrcode?user_id=usr_9a2b3c. The QR code contains a otpauth://totp/Alice%20Smith:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=ExampleCorp&algorithm=SHA1&digits=6&period=30.
The client app (e.g., Google Authenticator) reads the secret and generates 6-digit codes every 30 seconds. On login, the user enters the current code. The service validates it via POST /api/v1/mfa/verify:
{
"user_id": "usr_9a2b3c",
"code": "723194",
"timestamp": "2024-04-10T12:00:00Z"
}
The response includes status: "success" and mfa_verified: true, setting user.mfa_enabled = true. If the code is invalid, the service returns status: "failed", error: "invalid_code", and retry_after: 30.
Sessions are maintained via JWTs issued at login. The JWT header specifies alg: "HS256" and typ: "Bearer". The payload includes standard claims:
{
"sub": "usr_9a2b3c",
"name": "Alice Smith",
"email": "[email protected]",
"roles": ["user", "editor"],
"permissions": ["posts:read", "posts:write"],
"exp": 1712793600,
"iat": 1712787600,
"auth_time": 1712787600,
"session_id": "sess_abc123"
}
The exp (expiration) claim determines session lifetime. The service validates the token on each request by verifying the signature using a shared secret and checking exp and iat. A request to GET /api/v1/posts with Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... returns 401 Unauthorized if the token is expired or invalid.
Access to resources is governed by policies that define who can do what. Policies are expressed using a declarative language—typically JSON or YAML—and are evaluated at runtime.
Users are assigned roles, and roles grant permissions. A role is defined in a policy file:
name: editor
description: "Can read and write posts"
permissions:
- "posts:read"
- "posts:write"
- "posts:delete"
- "users:read"
- "users:write"
The system loads policies at startup from ./policies/*.yaml. During authorization, it collects all roles assigned to a user and combines their permissions into a flat set.
When a service checks access, it calls POST /api/v1/authorize with:
{
"user_id": "usr_9a2b3c",
"resource": "posts:123",
"action": "read"
}
The service evaluates the policy set and returns:
{
"allowed": true,
"reason": "User has 'editor' role with 'posts:read' permission",
"policy": "editor"
}
If allowed: false, the response includes reason: "missing_permission" and missing_permission: "posts:read".
For more complex access needs, ABAC uses attributes of the user, resource, and environment. A policy evaluates Boolean expressions over these attributes.
Example: A policy allows editing a post only if the user is in the same department as the post author and the post is not published.
{
"name": "edit_unpublished_post",
"description": "Allow editing a post if user's department matches post.author.department and post.status != 'published'",
"conditions": [
{
"match": "user.department",
"equals": "post.author.department"
},
{
"match": "post.status",
"not_equals": "published"
}
]
}
During authorization, the service evaluates the conditions using a set of available attributes:
{
"user": {
"id": "usr_9a2b3c",
"name": "Alice Smith",
"department": "engineering",
"role": "editor"
},
"resource": {
"type": "post",
"id": "123",
"title": "Building Identity Systems",
"author": {
"id": "usr_7d8e9f",
"name": "Bob Johnson",
"department": "engineering"
},
"status": "draft"
},
"context": {
"time": "2024-04-10T12:30:00Z",
"ip": "192.168.1.100",
"device_type": "mobile"
}
}
The evaluation returns allowed: true, reason: "user.department matches post.author.department and post.status is not published".
Federated identity enables users to sign in using external identity providers (IdPs), such as Google, GitHub, or Azure AD. The system supports the OpenID Connect (OIDC) protocol.
A web application initiates login by redirecting to the IdP with:
GET /auth/authorize?
response_type=code
&client_id=app_xyz
&redirect_uri=https://app.example.com/callback
&scope=openid profile email
&state=abc123
&code_challenge=xyz789
&code_challenge_method=S256
The IdP returns a 302 Found redirect to https://app.example.com/callback?code=auth_code_123&state=abc123.
The client exchanges the code for tokens via POST /auth/token:
{
"grant_type": "authorization_code",
"code": "auth_code_123",
"redirect_uri": "https://app.example.com/callback",
"code_verifier": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2",
"client_id": "app_xyz"
}
The response includes:
{
"access_token": "at_abc123",
"id_token": "idt_123abc",
"refresh_token": "rt_456def",
"expires_in": 3600,
"scope": "openid profile email"
}
The id_token is a JWT with iss, aud, exp, sub, and email claims. The system validates the JWT, extracts the user’s sub, and creates or updates the local user record.
For legacy enterprise systems, SAML 2.0 is used. The IdP generates a SAML Response as XML:
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_abc123"
Version="2.0"
IssueInstant="2024-04-10T12:00:00Z">
<saml:Issuer>https://idp.example.com</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion ID="_xyz789"
IssueInstant="2024-04-10T12:00:00Z"
Version="2.0">
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">[email protected]</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2024-04-10T12:15:00Z"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2024-04-10T12:00:00Z" NotOnOrAfter="2024-04-10T12:15:00Z">
<saml:AudienceRestriction>
<saml:Audience>https://app.example.com</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AttributeStatement>
<saml:Attribute Name="email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">
<saml:AttributeValue>[email protected]</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="roles" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">
<saml:AttributeValue>editor</saml:AttributeValue>
<saml:AttributeValue>admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
The service parses the SAML response, validates the signature, checks Audience, NotOnOrAfter, and NameID, and maps attributes to local user fields.
User lifecycle events trigger actions across systems. Events are published to a message broker (e.g., Kafka, RabbitMQ) as JSON messages.
On user creation, a user.created event is published:
{
"event": "user.created",
"timestamp": "2024-04-10T12:00:00Z",
"data": {
"user_id": "usr_9a2b3c",
"email": "[email protected]",
"given_name": "Alice",
"family_name": "Smith",
"preferred_username": "alice-smith",
"created_at": "2024-04-10T12:00:00Z",
"roles": ["user"],
"attributes": {
"department": "engineering",
"location": "San Francisco"
}
}
}
Other services consume this event to provision accounts in downstream systems:
When a user is deactivated (e.g., via PATCH /api/v1/users/usr_9a2b3c with status: "inactive"), the system emits user.deactivated:
{
"event": "user.deactivated",
"timestamp": "2024-04-10T13:00:00Z",
"data": {
"user_id": "usr_9a2b3c",
"reason": "left_company",
"deactivated_at": "2024-04-10T13:00:00Z"
}
}
The system invalidates all active sessions for the user by updating the sessions table:
UPDATE sessions SET active = false, invalidated_at = NOW()
WHERE user_id = 'usr_9a2b3c' AND active = true;
Additionally, it broadcasts a user.logout event to invalidate SSO sessions across all IdPs:
{
"event": "user.logout",
"timestamp": "2024-04-10T13:00:00Z",
"data": {
"user_id": "usr_9a2b3c",
"logout_url": "https://idp.example.com/logout?user_id=usr_9a2b3c"
}
}
The IdP handles the logout by clearing the user’s session and redirecting to a logout page.
exp claim is missing, the system assumes the token is valid for 1 hour from iat, but the actual exp is not checked.JWT_SECRET environment variable to be misconfigured; the service accepts tokens from one environment but rejects them from another.UTC clock is off by 3 minutes; TOTP codes generated on a device with perfect time are rejected. The solution is to validate codes within ±1 period (e.g., 25–35 seconds) around the expected time.Audience: the Audience restriction in the SAML response is not validated; users can log in to any relying party, even if they are not registered.user.department but the user object does not include department, the evaluation returns false even if department is present in attributes or context.subject_id, email, name, roles, attributes, email_verified, mfa_enabled, last_login.session_id, user_id, token, created_at, expires_at, active, last_activity.name and description as top-level keys.samlp:Response, saml:Assertion, and saml:AttributeStatement elements.The architecture is not about a tool or a platform—it is about the consistent flow of identity, the rules of access, and the protocols that bind systems together across time and trust boundaries.
This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy