Identity And Access Management

Identity And Access Management

We will now explore Identity and Access Management (IAM), a fundamental and indispensable protective component for any system.

Identity

When a user seeks to access a system, they must first authenticate by providing valid credentials, such as passwords or secret keys. This verification process establishes the user’s identity.

Temporary Credentials

While it is technically feasible to store and reuse a user’s permanent credentials locally on the frontend layer to avoid repetitive input, this approach introduces significant security vulnerabilities. If a user’s device is compromised or stolen, these stored credentials could be directly exploited by an attacker, granting unauthorized access.

To mitigate this risk, a more secure practice is to issue and utilize short-lived credentials instead of persistently storing the user’s primary secrets. These temporary credentials are valid only for a limited duration. Consequently, even if such a credential were intercepted or stolen, its potential for misuse would be strictly confined to its brief lifespan, significantly limiting the window of opportunity for an attacker.

User Session

One of the most straightforward approaches to implementing temporary credentials is through the use of User Sessions.

Following successful user authentication (sign-in with username and password), the Identity Service generates a temporary session code and returns it to the client.

ClientIdentity ServiceSign in with <username, password>Respond with a session code

This session code is subsequently included in requests from the client to other parts of the system. By presenting this code, the user can interact with various services without needing to repeatedly re-authenticate with their primary credentials. Any service receiving a request accompanied by a session code must then validate its authenticity and validity by querying the central Identity Service.

ClientBusiness ServiceIdentity ServiceRequest with the codeVerify the code

This design reveals a big drawback: the Identity Service effectively acts as a Single Point of Failure . Its availability and performance are critical, as all other services depend on it for session validation.

User Sessions are often well-suited for stateful services, particularly where session management is tightly coupled with business logic and handled locally within the service instance.

A typical application of this can be found in certain gaming systems. A specific session code is typically valid only within the instance that issued or currently manages it, rather than being universally valid across all instances.

Game ServiceGame Instance 1Game Instance 2Session StoreSession Store

JSON Web Token (JWT)

JSON Web Token (JWT) is a widely used method for building identity systems.

At its core, a JWT is an immutable credential that represents secure information about a user. A typical JWT looks like this eyJhbGc.eyJzdWIiO.cThIIoDv, where the dots separate three encoded sections: header, payload, and signature.

Removing the dots, we get three distinct encoded parts:

{
  "header": "eyJhbGc",
  "payload": "eyJzdWIiO",
  "signature": "cThIIoDv"
}

Each section is encoded using Base64, starting from its original JSON representation:

{
  // eyJhbGc
  "header": {
    "alg": "HS256",
    "typ": "JWT"
  },
  // eyJzdWIiO
  "payload": {
    "userId": "user1",
    "name": "John Doe",
    "role": "admin"
  },
  // cThIIoDv
  "signature": "cThIIoDv"
}
  • Header specifies the cryptographic algorithm used to sign the token.
  • Payload contains the customizable data, such as role, username, or other user-related information.
  • Signature is generated from the payload and a secret key, securing the token against tampering.

Token Validation

Based on Signature Validation, JWTs rely on a secret key (secured server-side) for generating the signature. Combined with the algorithm defined in the header, the signature is computed as: signature = alg(payload, secret key)

Any modification to the payload results in a completely different signature due to cryptographic properties.

For example, when the system generates signature = HS256(payload, secret):

  • Every time the token is validated, its signature is recalculated and compared with the one present in the token.
{
  "header": {
    "alg": "HS256",
    "typ": "JWT"
  },
  "payload": {
    "id": "user1",
    "role": "user"
  },
  "signature": "M5MDIy"
}

If an attacker modifies the role from user to admin, a valid signature cannot be generated without the secret key.

  • Using the original signature with the altered payload leads to a verification failure, as they no longer match.
{
  "header": {
    "alg": "HS256",
    "typ": "JWT"
  },
  "payload": {
    "id": "user1",
    "role": "admin" // changed
  },
  "signature": "M5MDIy" // INVALID: does not match modified payload, should be "UD511yc"
}

As long as the secret key remains protected and undisclosed, external entities cannot forge or tamper with tokens.

This approach addresses the availability aspect of User Session:

  • By sharing the secret key with other services, tokens can be validated locally. There is no need to query a central authenticator.
  • For instance, the Identity Service can distribute the key to the Business Service.
ClientIdentity ServiceBusiness ServiceDistributes the secret key1. Authenticate2. Respond a token3. Request with the token4. Use the distributed key to validate

Asymmetric Signature

Distributing the secret key to consumer services is risky because it allows them to independently generate tokens. To maintain security, other services should not be permitted to create new tokens.

By leveraging Asymmetric Encryption, this risk is addressed by separating the key into two distinct parts:

  • Signing (private) key: Used solely for generating tokens.
  • Authentication (public) key: Used for verifying tokens.

Integrating a Key store further enhances control:

  • The signing key is accessible only to the Identity Service.
  • The authentication key can be distributed freely for token verification.
ClientIdentity ServiceBusiness ServiceKey Management ServiceDistribute the authentication keyDistribute the signing key1. Authenticate2. Use the signing key to generate a new token3. Respond with the token4. Request with the token5. Use the authentication key to validate

Token Expiry

Since we do not store JWTs, there is no mechanism to invalidate issued tokens directly. To limit potential damage if a token is compromised, access tokens are designed to be short-lived, typically lasting only 5 to 10 minutes.

Token lifespan is managed through special fields in the token payload, and authentication processes must check these timestamps during verification:

{
  "payload": {
    // Issued At: Time the token was created
    "iat": "2025-01-01 13:00",
    // Expiration: Time the token becomes invalid
    "exp": "2025-01-01 13:05"
  }
}

Short token lifespans, however, can create a poor user experience by requiring frequent logins. To address this, the Refresh Token mechanism is used.

ℹ️
Some critical systems (such as banking) opt to require users to re-authenticate each time, forgoing refresh tokens entirely to maximize security.

Refresh Token

Following a successful user sign-in, the system generates a long-lived refresh token. This token, potentially valid for an extended duration such as a month, is securely stored by the identity system. Concurrently, the user’s client application (e.g., web browser or mobile app) also store this refresh token locally.

ClientIdentity ServiceRefresh Token StoreSign inGenerate and save refresh tokenRespond access token + refresh tokenSave refresh token locally

The primary purpose of the refresh token is to enable users to acquire new access tokens without requiring them to re-authenticate each time their current access token expires.

ClientIdentity ServiceRefresh Token StoreRe-authenticate with the refresh tokenCheck the refresh tokenRespond new access token

The advantages of Refresh Tokens include:

  • Eliminates the need for users to repeatedly enter their password, improving usability.
  • Allows for revocation: removing a refresh token from the store immediately invalidates it.

Federated identity

Identity is a fundamental requirement that often functions similarly across various systems. Instead of developing an identity mechanism from scratch, systems can utilize a trusted identity service. This approach, known as the Federated Identity Pattern, involves relying on an independent service to handle user identity.

Identity Provider

Consider a scenario where a system intends to use an Identity Provider (IdP), like Google, as its login method.

A naive implementation might involve the system acting as an intermediary, forwarding user credentials (email, password) directly to the identity provider.

SystemGoogleClientSend email and passwordAuthenticate

This workflow is inherently insecure because the application handling the credentials could potentially misuse them. Consequently, identity providers typically do not permit dependent applications to authenticate directly on behalf of users in this manner.

SystemGoogleClientSend email and passwordSteal the credentialAuthenticate

ID Token

Basically, identity providers require users to interact directly with them for authentication, rather than through an intermediary application.

Let’s examine this process in detail.

  • When a user chooses to sign in to our system using a third-party platform, such as Google.

  • The system redirects the user to the identity provider to sign in.

  • Upon successful authentication by the provider, an ID Token, in the JSON Web Token (JWT) format, is issued and sent back to the user’s browser or client application

ClientSystemGoogle1. Initiate Sign in with Google2. Redirect3. Sign in4. Issue ID Token

The user then transmits this ID Token to the system. The system must then verify the token’s authenticity to confirm it was issued by a legitimate Google account. Additionally, the system can inspect the token’s payload to extract user information, such as email, account id, and other details.

ClientSystemGoogle1. Initiate Sign in with Google2. Redirect3. Sign in4. Issue ID Token5. Send ID Token6. Verify token and extract user information

How does the system verify the token? This verification relies on Asymmetric Encryption principles:

  • The identity provider securely holds a private signing key used to generate new tokens.
  • The provider also distributes a corresponding public authentication key to the system, enabling it to verify the authenticity of tokens autonomously.
Identity ProviderSystemSigning Key (Private)Authentication Key (Public)Key Distribution

Authorization

Once a user’s identity is confirmed, the next critical step is to determine what actions they are permitted to perform and which resources they can access within the system. This process is known as Authorization.

This section will explore fundamental concepts and methods for implementing Authorization.

Access Policy

A core component in managing permissions is the Access Policy. An access policy is a set of rules assigned to a user that explicitly defines what actions it is permitted or denied to perform on specific resources.

For example, we can create distinct policies:

  • One policy allowing user John to write to Technical documents,
  • Another policy allowing John to read Business documents.
JohnPolicy 1Policy 2target: Technical documentsallow: writetarget: Technical documentsallow: writetarget: Business documentsallow: readtarget: Business documentsallow: readAssigned toAssigned to

Least Privilege Principle

The Principle of Least Privilege is a fundamental and widely adopted cybersecurity concept. In essence, this principle dictates that users should only be granted the minimum level of access to perform their designated tasks, and no more.

A key tenet of this principle is that access is denied by default: A user’s action is only permitted if explicitly approved by at least one applicable policy.

Role

Managing permissions on an individual user basis can become exceedingly complex. To simplify this, Roles are introduced. A role groups users who share similar responsibilities, allowing them to inherit a common set of permissions.

For example, all users assigned the Developer role might be granted permission to read Technical documents.

Developer RolePermission 1JohnDoetarget: Technical documentsaction: readtarget: Technical documentsaction: readAssigned to

A crucial aspect related to the Principle of Least Privilege. If multiple policies apply, an explicit denial policy overrides any allow policies. This ensures that specific restrictions can be enforced even if a user belongs to a role that generally grants broader access.

For instance, while the Developer role might be granted permission to read Technical documents, an explicit reject policy (p2) assigned specifically to user John for those same documents will prevent him from performing that action.

Developer RolePermission 1: AllowPermission 2: DenyJohnDoetarget: Technical documentsaction: readtarget: Technical documentsaction: readtarget: Technical documentsaction: rejecttarget: Technical documentsaction: rejectAssigned to RoleAssigned to John (Overrides Role Permission)

Role Explosion

While roles simplify permission management, relying solely on them can lead to a common issue known as role explosion.

Consider a scenario with Technical documents shared among Developers:

  • Lead Developers require full access to them.
  • Regular Developers can read and write them.
  • Intern Developers should only be able to read them.

If a single, overly permissive Developer role is created, it could lead to permission escalation. The alternative is to create distinct roles like Developer-Lead, Developer-Regular, and Developer-Intern, and assign developers to these new roles accordingly.

As the organization grows and more variations are needed based on departments, projects, or teams, the number of roles can multiply rapidly. This proliferation results in significant management overhead.

Attribute

To achieve finer-grained access control, instead of relying solely on static role assignments, we can tag users and resources with attributes that are then used in authorization decisions.

In our developer scenario, users within the Developer role can be tagged with attributes such as their position. Thus, access policies become more dynamic by checking user attributes to make the final authorization decision.

Developer RolePolicy 1: Full AccessPolicy 2: Read AccessPolicy 3: Write AccessJohn (Lead)Doe (Intern)target: Technical documentsrequire:  position: Leadaction: fulltarget: Technical documentsrequire:  position: Leadaction: fulltarget: Technical documentsrequire:  position: Internaction: readtarget: Technical documentsrequire:  position: Internaction: readtarget: Technical documentsrequire:  position: Internaction: writetarget: Technical documentsrequire:  position: Internaction: writename: Johnposition: Leadname: Johnposition: Leadname: Doeposition: Internname: Doeposition: InternAssigned to RoleAssigned to RoleAssigned to Role

Resourced-based Authorization

Thus far, our discussion has primarily focused on an administrative perspective where permissions are managed by creating policies and assigning them to identities (users or roles). This model is known as Identity-Based Authorization.

However, there are scenarios where regular users might need to control access to resources they own or manage, such as sharing their documents with specific colleagues.

Another authorization approach is Resource-Based Authorization. In this model, permissions are attached directly to the resources themselves. The policies on the resource then specify which identities (users or roles) are granted or denied access.

For example, a specific Technical document could have a policy allowing the Developer role to read it, and another policy granting user Admin01 full control.

Technical Document (Resource)Permission 1Permission 2target:  type: role  id: Developeraction: readtarget:  type: role  id: Developeraction: readtarget:  type: user  id: Admin01action: fulltarget:  type: user  id: Admin01action: fullAttached to ResourceAttached to Resource

In practice, many robust authorization systems combine both Identity-Based and Resource-Based policies to offer comprehensive and flexible access control.

Cross-system Resource Sharing

Consider a scenario where a system needs to share its resources with other applications.

For instance, imagine an application being developed that allows a user to upload files directly from their Google Drive. In this situation, Google Drive must authorize the application to perform actions on the user’s behalf.

UserApplicationGoogleDriveUpload a file from DriveAccess the file

Similar to the principles discussed in the Identity Provider section, it’s crucial that the resource service (in this case, Google Drive) does not permit the application to access the user’s credentials, such as passwords or comprehensive access tokens. These credentials fully represent the user and could be misused to perform any action as that user.

Instead, a more secure approach involves creating distinct credentials specifically for the application. These credentials are limited to the permissions explicitly granted by the user.

OAuth2.0

OAuth2.0 is an authorization framework that allows third-party applications to access user resources hosted on a service.

Basic Flow

The process typically begins when the application redirects the user to the resource service, such as Google Drive. Here, the user signs in and grants specific permissions to the application. Following this, the resource service issues an Access Token back to the user, which is limited to the granted permissions.

UserApplicationGoogleDriveRequestRedirect to GoogleDriveSign in and consent permissionsRespond a limited access token

The user then sends this token to the application. With this access token, the application can now access the resources shared by the user on the resource service.

UserApplicationGoogleDriveRequestRedirect to GoogleDriveSign in and consent permissionsRespond an access tokenSend the tokenAccess resources with the token

This outlines the fundamental flow of OAuth 2.0. However, a potential security concern arises in this simplified model. If the access token is sent directly to the frontend (e.g., the user’s browser or device), but the backend system is the actual entity that needs to access the resource, the token becomes vulnerable.

HackerClient (Frontend)Application (Backend)GoogleDriveRespond an access tokenSteal the token hereSend the token

If a user’s device is compromised and the comprehensive token is stolen, it might seem minor that the limited access token is abused. However, this is fundamentally an issue of responsibility. Our system issued the token, and therefore, we are accountable for any actions performed using it. Thus, its protection is paramount.

Authorization Code Grant

To mitigate the aforementioned security risk, the OAuth 2.0 framework introduces the concept of authorization code grant. Instead of directly sending an access token to the user’s browser, the resource service sends back a temporary authorization code.

UserApplicationGoogleDriveRequestRedirect to GoogleDriveSign in and consent permissionsRespond an authorization code

The user then transmits this authorization code to the application (its backend system). To gain access to the resources, the application’s backend must first use this code to exchange it for an actual Access Token directly with the resource service.

UserApplicationGoogleDriveRequestRedirect to GoogleDriveSign in and consent permissionsRespond an authorization codeSend the authorization codeExchange an access token with the code

With this flow, attackers who might intercept the communication between the user and the application would only obtain the short-lived authorization code, not the actual access token.

However, this alone does not entirely solve the problem. If attackers obtains the authorization code, they could potentially still use it to exchange an access token themselves. Therefore, this exchange mechanism is most effective when the entity exchanging the code is a trusted target.

Trusted Target

To ensure that only the legitimate application can exchange the authorization code for an access token, the application must first register itself with the resource service. This registration typically involves:

  • Assigning a unique Client ID to the application.
  • Providing the application with a Client Secret, which acts as a password for the application itself.
GoogleDriveApplication 1Application 2app1: id: 123 secret: 111222app2: id: 234 secret: 222333app1: id: 123 secret: 111222app2: id: 234 secret: 222333id: 123secret: 111222id: 123secret: 111222id: 234secret: 222333id: 234secret: 222333RegisterRegister

When redirecting a user to the resource service for authorization, the application must include its Client ID in the redirection request (e.g., /auth?client_id=123). This ensures that the resource service issues an authorization code specifically for that application.

UserApplication 1GoogleDriveRequestRedirect to /auth?client_id=123

During the access token exchange, the application authenticates itself to the resource service using its Client ID and Client Secret, along with the authorization code. The resource service verifies all these components before issuing the access token.

UserApplication 1GoogleDriveRequestRedirect to /auth?client_id=123Sign in and consent permissionsRespond an exchange codeSend the authorization codeExchange an access token with (code + Client ID + Client Secret)Respond an access token

This enhanced process ensures that only the registered application, possessing the correct Client ID and Client Secret, can successfully exchange the authorization code for an access token. Consequently, even if an authorization code is intercepted, it cannot be exploited by an unauthorized party.

Last updated on