← Back to Articles         
Service-to-Service Authentication

Service-to-Service Authentication

Published on 29 Sep 2026     18 min read Microservices
Authentication & Security

How Does Service-to-Service Authentication Work in Microservices?

Service-to-service authentication (S2S authentication) is the mechanism by which one microservice proves its own identity when calling another microservice.

For example:

Order Service
     |
     | "I am Order Service"
     v
Payment Service

Payment Service should not trust the request merely because it came from the internal network. It should establish:

Who is calling me?
        ↓
Is this really Order Service?
        ↓
Is Order Service allowed to call me?
        ↓
Is it allowed to perform THIS operation?

A very common standards-based approach is OAuth 2.0 Client Credentials, where the calling service authenticates to an authorization server and obtains an access token representing the application itself, not an end user. OAuth 2.0 defines Client Credentials specifically for confidential clients acting on their own behalf; Microsoft likewise documents it as a standard server-to-server scenario. RFC Editor


1. What Is Service-to-Service Authentication?

Consider these microservices:

Order Service
Payment Service
Inventory Service
Notification Service

When Order Service creates an order, it may need to call Payment Service:

Client
   |
   v
Order Service
   |
   | POST /api/payments
   v
Payment Service

The question is:

How does Payment Service know the request actually came from an authorized Order Service?

That's the purpose of service-to-service authentication.

Without authentication:

Order Service --------+
                      |
Inventory Service ----+----> Payment Service
                      |
Attacker -------------+

Payment Service cannot safely assume:

Internal network = trusted

Instead:

Caller
   |
   | Credential / Identity
   v
Payment Service
   |
Authenticate caller
   |
Authorize caller
   v
Execute operation

2. Why Do We Need It?

Imagine Payment Service exposes:

POST /api/payments

Order Service legitimately calls it:

Order Service
      |
      v
Payment Service

But if there is no authentication, another compromised workload might also call it:

Compromised Service
       |
       | POST /api/payments
       v
Payment Service

The receiving service needs a cryptographically verifiable identity.

Service-to-service authentication helps prevent:

  • unauthorized internal calls
  • impersonation
  • lateral movement
  • gateway bypass
  • unrestricted access between workloads
  • anonymous access to internal APIs

3. Authentication vs Authorization

This distinction remains important for service identities.

Authentication

Payment Service asks:

Who is calling me?

Result:

Caller = Order Service

Authorization

Payment Service then asks:

What is Order Service allowed to do?

For example:

Order Service

Allowed:
    payments.create

Not Allowed:
    payments.refund
    payments.admin

Therefore:

Service Token
     |
     v
Authentication
     |
     v
Caller = Order Service
     |
     v
Authorization
     |
     | Has payments.create?
     v
Payment Controller

4. Common S2S Authentication Methods

There are several approaches:

Method Typical use
OAuth 2.0 Client Credentials General service-to-service authentication
Managed Identity Azure-hosted services
Workload Identity Kubernetes/cloud-native environments
mTLS Strong certificate-based mutual authentication
API Key Simpler integrations; less expressive
Service Mesh identity Kubernetes/service-mesh environments

For ASP.NET Core enterprise microservices, a very common model is:

OAuth 2.0
+
Client Credentials
+
JWT Access Token

Microsoft documents Client Credentials as an application-identity flow used for server-to-server interactions without user interaction. Microsoft Learn


5. Client Credentials Flow

Suppose:

Order Service
      |
      v
Payment Service

Order Service does not simply send:

Username = OrderService
Password = abc123

to Payment Service.

Instead, an authorization server is introduced:

                 Authorization Server
                 / Identity Provider
                        ^
                        |
                Client Credentials
                        |
                  Order Service
                        |
                    Access Token
                        |
                        v
                 Payment Service

Examples of authorization servers/identity providers include:

  • Microsoft Entra ID
  • Keycloak
  • Auth0
  • Duende IdentityServer

6. Complete Flow

The complete flow is:

Order Service
      |
      | 1. Authenticate itself
      |    Client ID + credential
      v
Authorization Server
      |
      | 2. Validate service identity
      |
      | 3. Issue access token
      v
Order Service
      |
      | 4. Authorization:
      |    Bearer <access_token>
      v
Payment Service
      |
      | 5. Validate access token
      |
      | 6. Authenticate caller
      |
      | 7. Check permission
      v
PaymentController
      |
      v
Process Payment

RFC 6749 defines essentially this flow: the confidential client authenticates to the authorization server, requests an access token using grant_type=client_credentials, and receives an access token if authentication and authorization succeed. RFC Editor


7. Step 1 — Register the Services

Conceptually, register Order Service as a client:

Client ID:

order-service

and give it a credential.

For example:

Client ID:
order-service

Client Secret:
**************

Payment API might be registered as:

payment-api

with an application permission such as:

payments.process

8. Client ID vs Client Secret

Think of:

Client ID
    =
Service username / public identifier

Client Secret
    =
Service password / confidential credential

For example:

ClientId:
order-service

ClientSecret:
7fK9**********

However, long-lived client secrets are not the strongest option.

Where supported, prefer:

Managed Identity
Certificate
Workload Identity

over manually managed shared secrets. Microsoft notes that confidential clients can authenticate using secrets or certificates, and managed identities avoid exposing underlying credentials to application administrators/code. Microsoft Learn


9. Step 2 — Order Service Requests a Token

Conceptually:

POST /connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
client_id=order-service
client_secret=********
scope=payments.process

The authorization server validates:

Does order-service exist?
       ↓
Is credential valid?
       ↓
Can order-service request
payments.process?
       ↓
YES
       ↓
Generate access token

The OAuth standard requires the token endpoint to authenticate the confidential client, and credentials must be protected with TLS. RFC Editor


10. Token Response

Conceptually:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600
}

The access token represents:

Order Service

not:

Syed / End User

That's the critical difference.


11. Example Service Token

Conceptually, a JWT access token could contain claims like:

{
  "sub": "order-service",
  "aud": "payment-api",
  "scope": "payments.process",
  "iss": "https://identity.example.com",
  "exp": 1790690000
}

Meaning:

Subject
    = Order Service

Audience
    = Payment API

Permission
    = payments.process

Issuer
    = Trusted Authorization Server

Expiration
    = Token expiry

12. Step 3 — Call Payment Service

Order Service sends:

POST /api/payments
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Content-Type: application/json
{
  "orderId": 1001,
  "amount": 75000
}

Flow:

Order Service
     |
     | Bearer Service Token
     v
Payment Service

13. Step 4 — Payment Service Validates the Token

Payment Service validates:

Signature
    ↓
Issuer
    ↓
Audience
    ↓
Expiration
    ↓
Required permission

For example:

aud = payment-api

scope = payments.process

If everything is correct:

Authenticated caller
        =
Order Service

ASP.NET Core protected APIs normally validate incoming access tokens using authentication middleware/handlers, then authorization policies restrict access to the appropriate caller permissions. Microsoft Learn


14. ASP.NET Core Example Architecture

Let's create:

MicroservicesDemo
│
├── OrderService
│
└── PaymentService

The real authorization server is external:

                  Identity Provider
                         ^
                         |
                   Get Token
                         |
                   OrderService
                         |
                    Bearer Token
                         |
                         v
                  PaymentService

15. Required NuGet Packages

Order Service

If using Microsoft Entra ID/MSAL for acquiring application tokens:

dotnet add package Microsoft.Identity.Client

Payment Service

For JWT bearer validation:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

The exact acquisition library depends on your identity provider.


16. Order Service — appsettings.json

For demonstration:

{
  "Identity": {
    "TenantId": "YOUR-TENANT-ID",
    "ClientId": "ORDER-SERVICE-CLIENT-ID",
    "ClientSecret": "ORDER-SERVICE-CLIENT-SECRET",
    "PaymentApiScope": "api://PAYMENT-API-CLIENT-ID/.default"
  },

  "Services": {
    "PaymentService": "https://localhost:7201"
  }
}

Important

Do not store the real production secret in committed appsettings.json.

Use:

Local development
    -> .NET User Secrets

Azure
    -> Managed Identity / Key Vault

Kubernetes
    -> Workload Identity / secured secret management

17. TokenService

Create:

Services/TokenService.cs
using Microsoft.Identity.Client;

namespace OrderService.Services;

public class TokenService
{
    private readonly IConfiguration _configuration;

    private readonly IConfidentialClientApplication _app;

    public TokenService(IConfiguration configuration)
    {
        _configuration = configuration;

        var tenantId =
            configuration["Identity:TenantId"];

        var clientId =
            configuration["Identity:ClientId"];

        var clientSecret =
            configuration["Identity:ClientSecret"];

        _app = ConfidentialClientApplicationBuilder
            .Create(clientId)
            .WithClientSecret(clientSecret)
            .WithAuthority(
                $"https://login.microsoftonline.com/{tenantId}")
            .Build();
    }


    public async Task<string> GetPaymentTokenAsync()
    {
        var scope =
            _configuration["Identity:PaymentApiScope"]!;

        var result =
            await _app
                .AcquireTokenForClient(
                    new[] { scope })
                .ExecuteAsync();

        return result.AccessToken;
    }
}

The important line is:

AcquireTokenForClient(...)

It means:

Acquire an access token for the application itself, not for an interactive user.

MSAL supports this client-credential acquisition for confidential applications. Microsoft Learn


18. PaymentClient

Create:

Services/PaymentClient.cs
using System.Net.Http.Headers;
using System.Net.Http.Json;

namespace OrderService.Services;

public class PaymentClient
{
    private readonly HttpClient _httpClient;

    private readonly TokenService _tokenService;

    public PaymentClient(
        HttpClient httpClient,
        TokenService tokenService)
    {
        _httpClient = httpClient;
        _tokenService = tokenService;
    }


    public async Task<HttpResponseMessage> ProcessPaymentAsync(
        int orderId,
        decimal amount)
    {
        var accessToken =
            await _tokenService.GetPaymentTokenAsync();

        _httpClient.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue(
                "Bearer",
                accessToken);

        var request = new
        {
            OrderId = orderId,
            Amount = amount
        };

        return await _httpClient.PostAsJsonAsync(
            "/api/payments",
            request);
    }
}

The important part is:

Authorization: Bearer <service-token>

19. Order Service — Program.cs

using OrderService.Services;

var builder =
    WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddSingleton<TokenService>();

builder.Services.AddHttpClient<PaymentClient>(
    client =>
    {
        client.BaseAddress =
            new Uri(
                builder.Configuration[
                    "Services:PaymentService"]!);
    });

var app = builder.Build();

app.UseHttpsRedirection();

app.MapControllers();

app.Run();

20. OrdersController

using Microsoft.AspNetCore.Mvc;
using OrderService.Services;

namespace OrderService.Controllers;

[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly PaymentClient _paymentClient;

    public OrdersController(
        PaymentClient paymentClient)
    {
        _paymentClient = paymentClient;
    }


    [HttpPost]
    public async Task<IActionResult> CreateOrder()
    {
        var orderId = 1001;

        var amount = 75000m;

        var response =
            await _paymentClient
                .ProcessPaymentAsync(
                    orderId,
                    amount);

        if (!response.IsSuccessStatusCode)
        {
            return StatusCode(
                500,
                "Payment failed");
        }

        return Ok(new
        {
            OrderId = orderId,

            Message =
                "Order and payment completed"
        });
    }
}

21. Payment Service — appsettings.json

{
  "Authentication": {
    "Authority": "https://login.microsoftonline.com/YOUR-TENANT-ID/v2.0",
    "Audience": "PAYMENT-API-CLIENT-ID"
  }
}

Your actual audience value must match how the Payment API is registered/configured with the identity provider.


22. Payment Service — Program.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder =
    WebApplication.CreateBuilder(args);

builder.Services.AddControllers();


builder.Services
    .AddAuthentication(
        JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority =
            builder.Configuration[
                "Authentication:Authority"];

        options.Audience =
            builder.Configuration[
                "Authentication:Audience"];

        options.RequireHttpsMetadata = true;
    });


builder.Services.AddAuthorization(options =>
{
    options.AddPolicy(
        "ProcessPayment",
        policy =>
        {
            policy.RequireAuthenticatedUser();

            policy.RequireAssertion(context =>
            {
                var scopes =
                    context.User.FindFirst("scp")?.Value
                    ?? context.User.FindFirst("scope")?.Value;

                var roles =
                    context.User.FindAll("roles")
                        .Select(x => x.Value);

                return
                    roles.Contains("payments.process")
                    ||
                    (scopes?.Split(' ')
                        .Contains("payments.process")
                        ?? false);
            });
        });
});


var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

app.Run();

For Microsoft Entra application-only tokens, application permissions/app roles are normally the relevant authorization mechanism; delegated scp scopes apply to user-delegated scenarios. Microsoft documents protected APIs as supporting both delegated permissions and application permissions. Microsoft Learn


23. PaymentRequest

namespace PaymentService.Models;

public class PaymentRequest
{
    public int OrderId { get; set; }

    public decimal Amount { get; set; }
}

24. PaymentsController

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using PaymentService.Models;

namespace PaymentService.Controllers;

[ApiController]
[Route("api/payments")]
[Authorize]
public class PaymentsController : ControllerBase
{
    [HttpPost]
    [Authorize(Policy = "ProcessPayment")]
    public IActionResult ProcessPayment(
        PaymentRequest request)
    {
        return Ok(new
        {
            request.OrderId,
            request.Amount,

            Message =
                "Payment processed successfully"
        });
    }
}

Now Payment Service does not simply trust Order Service.

It requires:

Valid Access Token
        +
Correct Issuer
        +
Correct Audience
        +
Valid Lifetime
        +
Required Application Permission

25. Complete Execution Flow

When the client creates an order:

Client
  |
  | POST /api/orders
  v
Order Service

Order Service needs Payment Service.

It first obtains a service token:

Order Service
     |
     | Client ID
     | Client Credential
     v
Microsoft Entra ID
     |
     | Authenticate Order Service
     |
     v
Access Token

Then:

Order Service
     |
     | POST /api/payments
     |
     | Authorization:
     | Bearer <access-token>
     v
Payment Service

Payment Service:

JWT received
    |
    v
Validate Signature
    |
    v
Validate Issuer
    |
    v
Validate Audience
    |
    v
Validate Expiration
    |
    v
Authenticate Caller
    |
    v
Caller = Order Service
    |
    v
Check payments.process
    |
    +------ No ------> 403 Forbidden
    |
   Yes
    |
    v
PaymentsController
    |
    v
Process Payment

26. What Happens If No Token Is Sent?

Order Service
      |
      | POST /api/payments
      | No Authorization header
      v
Payment Service

Result:

401 Unauthorized

Because Payment Service cannot authenticate the caller.


27. What Happens If Token Is Valid but Permission Is Missing?

Suppose:

Caller = Inventory Service

Permission:
inventory.read

but Payment Service requires:

payments.process

Result:

403 Forbidden

Remember:

401
=
Authentication problem


403
=
Authorization problem

28. Client Credentials vs User Token

This is one of the most important distinctions.

User authentication

User
 |
 | Login
 v
Identity Provider
 |
 | User Access Token
 v
Order Service

The token represents:

User

Service authentication

Order Service
 |
 | Client Credentials
 v
Identity Provider
 |
 | Application Access Token
 v
Payment Service

The token represents:

Order Service

Microsoft specifically describes Client Credentials as allowing a web service to use its own identity instead of impersonating a user. Microsoft Learn


29. What If Payment Service Needs the User Identity?

Now consider:

User
 |
 | User Token
 v
Order Service
 |
 | ???
 v
Payment Service

Payment Service might need to know:

Which user initiated this operation?

This is different from pure application authentication.

One standards-based option in Microsoft Entra environments is the On-Behalf-Of (OBO) flow:

User
 |
 | User Token for Order API
 v
Order Service
 |
 | Exchange user assertion
 v
Identity Provider
 |
 | Downstream token
 | for Payment API
 v
Order Service
 |
 | Downstream token
 v
Payment Service

Microsoft distinguishes these two scenarios:

  • Client Credentials → application acts as itself.
  • On-Behalf-Of → API calls another API on behalf of the authenticated user. Microsoft Learn

30. Don't Blindly Forward the Original JWT

Avoid assuming this is always correct:

User JWT
   |
   v
Order Service
   |
   | SAME JWT
   v
Payment Service

Why?

The original token might have:

{
  "aud": "order-api"
}

That means it was intended for:

Order API

not necessarily:

Payment API

Payment Service should reject a token whose audience is not appropriate for Payment Service.

Instead use the identity architecture appropriate to the scenario:

Service acting itself
       ↓
Client Credentials


Service acting for user
       ↓
On-Behalf-Of / delegation

31. Client Secret vs Certificate vs Managed Identity

There are multiple ways for Order Service to prove its identity.

Option 1 — Client Secret

Order Service
    |
Client ID
Client Secret
    |
    v
Identity Provider

Simple, but you must securely manage and rotate the secret.


Option 2 — Certificate

Order Service
    |
Private Key / Certificate
    |
    v
Identity Provider

This avoids a simple shared password and can provide stronger assurance when managed correctly. Microsoft explicitly supports certificates for confidential client authentication. Microsoft Learn


Option 3 — Managed Identity

In Azure:

Azure-hosted
Order Service
     |
     | Managed Identity
     v
Microsoft Entra ID
     |
     | Access Token
     v
Payment API

No application-managed client secret is required.

Microsoft notes that with managed identities, Azure manages the underlying credentials so applications don't need to expose them. Microsoft Learn

For Azure-hosted services, this is often preferable when the target resource/API and architecture support it.


32. mTLS Alternative

Service-to-service authentication does not have to use JWT.

Another approach is mutual TLS:

Order Service
     |
     | Client Certificate
     |
     |       mTLS
     v
Payment Service
     |
     | Server Certificate

Both sides authenticate each other:

Order verifies Payment
+
Payment verifies Order

This is especially common with:

Kubernetes
Service Mesh
Istio
Linkerd
Internal high-security systems

33. JWT vs mTLS

Feature JWT/OAuth mTLS
Service identity Yes Yes
Permissions/scopes Strong support Usually separate policy
Certificate required Not necessarily Yes
Works well with APIs Yes Yes
Token expiration Yes N/A in same sense
Certificate lifecycle Optional Required
User delegation Supported by OAuth flows Not inherently

They can also be combined:

mTLS
+
OAuth Access Token

where mTLS authenticates the connection/workload and the token carries application authorization information.


34. API Key Alternative

Simpler systems sometimes use:

X-API-Key: abc123

Flow:

Order Service
     |
     | API Key
     v
Payment Service

This can work for simpler controlled integrations, but compared with OAuth it usually provides weaker identity/authorization semantics and more manual:

Key distribution
Rotation
Revocation
Permission management
Auditing

For larger microservices systems, workload identity or OAuth-based S2S authentication is generally more scalable.


35. Should Every Microservice Have Different Credentials?

Generally, yes.

Avoid:

All Microservices

ClientId = common-service
Secret   = same-secret

Because if one service is compromised, the attacker may impersonate all services.

Prefer:

Order Service
    identity = order-service

Inventory Service
    identity = inventory-service

Notification Service
    identity = notification-service

Then authorization can be precise:

Order Service
    -> payments.process

Inventory Service
    -> inventory.update

Notification Service
    -> notifications.send

This follows least privilege.


36. Token Caching

Do not unnecessarily request a new token for every API call.

Bad:

Request #1 -> Get token
Request #2 -> Get token
Request #3 -> Get token
Request #4 -> Get token
Request #5 -> Get token

Prefer token acquisition libraries that cache/reuse valid access tokens:

Get Token
   |
   v
Cache
   |
   +--> API Call #1
   +--> API Call #2
   +--> API Call #3

When approaching expiration:

Acquire new token

MSAL provides token caching behavior around its token acquisition APIs, so applications should use the library rather than implementing an ad-hoc token request for every outgoing request. Microsoft Learn


37. Advantages

Strong service identity

The receiving API knows which application is calling.

No user required

Background services can communicate independently.

Fine-grained authorization

You can define:

orders.read
payments.process
inventory.update

Short-lived credentials

Access tokens expire automatically.

Centralized identity

An authorization server manages application identities and permissions.

Scalable

Works across many services and instances.

Zero-Trust friendly

Internal network location is not treated as sufficient authentication.


38. Disadvantages

Additional infrastructure

You need an authorization/identity system.

Credential management

Client secrets/certificates require secure handling unless workload/managed identities eliminate this burden.

Token acquisition complexity

Services must obtain and cache tokens.

Configuration complexity

You must correctly configure:

Client ID
Credential
Audience
Permissions
Issuer
Token validation

Identity provider dependency

Token acquisition can depend on the authorization server, although already-issued locally verifiable tokens can often continue to be validated until they expire.


39. Best Practices

For production microservices:

  1. Give each microservice its own identity.
  2. Prefer OAuth Client Credentials for application-only calls where appropriate.
  3. Prefer Managed/Workload Identity when the platform supports it.
  4. Prefer certificates over long-lived shared secrets where appropriate.
  5. Never commit client secrets to source control.
  6. Store unavoidable secrets in Key Vault or another secret manager.
  7. Use HTTPS/TLS.
  8. Use short-lived access tokens.
  9. Validate issuer.
  10. Validate audience.
  11. Validate signature.
  12. Validate token lifetime.
  13. Authorize using application permissions/roles.
  14. Apply least privilege.
  15. Cache access tokens appropriately.
  16. Don't blindly forward user access tokens.
  17. Use OBO/delegation when downstream processing genuinely needs user context.
  18. Restrict network access in addition to authentication.
  19. Never log access tokens or client secrets.
  20. Rotate credentials/certificates according to policy.

40. Key Points

The most important flow to remember is:

Service A
   |
   | Client Credential /
   | Workload Identity
   v
Authorization Server
   |
   | Access Token
   v
Service A
   |
   | Bearer Access Token
   v
Service B
   |
   +--> Validate Token
   |
   +--> Identify Service A
   |
   +--> Check Permission
   |
   v
Execute Request

And remember the distinction:

USER AUTHENTICATION

User
  ↓
Identity Provider
  ↓
User Token
  ↓
Microservice


SERVICE AUTHENTICATION

Microservice A
  ↓
Authorization Server
  ↓
Application Token
  ↓
Microservice B

41. Interview Questions and Answers

Q1. How does service-to-service authentication work?

Answer:

One common approach is OAuth 2.0 Client Credentials. The calling microservice authenticates itself to an authorization server using its application identity, receives a short-lived access token, and sends that token to the target microservice. The target service validates the token and then authorizes the caller using application permissions or roles.


Q2. Which OAuth flow is used for service-to-service communication?

For an application acting as itself:

Client Credentials flow.

OAuth defines it for confidential clients acting on their own behalf. RFC Editor


Q3. Does Client Credentials authenticate a user?

No.

It authenticates:

Application / Microservice

not:

End User

Q4. How does Payment Service know Order Service called it?

Order Service presents an access token representing its application identity. Payment Service validates the token and checks the caller's application permission, for example:

payments.process

Q5. Should all Microservices share the same client secret?

No.

Each service should generally have its own identity and only the permissions it requires.


Q6. Where should client secrets be stored?

Production secrets should be stored in a secure secret-management system such as Azure Key Vault rather than committed into appsettings.json.

When possible, eliminate manually managed secrets through Managed Identity or Workload Identity.


Q7. Client Credentials vs On-Behalf-Of?

Client Credentials
    =
Service acts as itself


On-Behalf-Of
    =
Service calls downstream API
for an authenticated user

Microsoft supports both scenarios and distinguishes application-only Client Credentials from delegated OBO calls. Microsoft Learn


Q8. Can we use mTLS instead of OAuth?

Yes.

mTLS authenticates services through certificates. OAuth tokens are particularly useful when you also need application-level permissions, audiences and centralized authorization.

They can also be used together.


Q9. Should we get a new access token for every request?

Normally, no. Use a token acquisition library/cache and reuse a valid access token until it needs renewal. Microsoft Learn


Q10. What happens if Service A has a valid token but lacks permission?

Authentication succeeds but authorization fails:

403 Forbidden

Interview Summary

A strong interview answer is:

For service-to-service authentication, each microservice should have its own workload/application identity. A common implementation uses OAuth 2.0 Client Credentials: Service A authenticates to the authorization server using its application credential or workload identity, obtains a short-lived access token targeted at Service B, and sends it in the Bearer header. Service B validates the token's signature, issuer, audience and lifetime, identifies Service A, and checks its application permissions before processing the request. In Azure, I prefer Managed Identity or workload identity where possible so that long-lived client secrets don't need to be stored in the application. If the downstream call must preserve user context, I would consider a delegated flow such as On-Behalf-Of rather than Client Credentials.

The easiest way to remember it is:

Order Service
     |
     | "Give me permission
     |  to call Payment API"
     v
Identity Provider
     |
     | Service Access Token
     v
Order Service
     |
     | Bearer Token
     v
Payment Service
     |
     | Who are you?
     | -> Order Service
     |
     | What can you do?
     | -> payments.process
     v
Process Payment

Service-to-service authentication proves the identity of the calling microservice; authorization determines what that microservice is permitted to do.

← Back to Articles