Featured resource
2025 Tech Upskilling Playbook
Tech Upskilling Playbook

Build future-ready tech teams and hit key business milestones with seven proven plays from industry leaders.

Check it out
  • Lab
    • Libraries: If you want this lab, consider one of these libraries.
    • Core Tech
Labs

Guided: Securing an ASP.NET Core 10 API with OAuth2 Client Credentials Flow

In this Code Lab, you'll secure an ASP.NET Core 10 Web API using the OAuth2 Client Credentials Flow. You'll configure JWT bearer authentication, protect endpoints with authorization policies, obtain access tokens from an identity provider, and validate your API’s security behavior. By the end, you'll have a production-ready API with OAuth2 machine-to-machine authentication.

Lab platform
Lab Info
Level
Intermediate
Last updated
Mar 03, 2026
Duration
45m

Contact sales

By clicking submit, you agree to our Privacy Policy and Terms of Use, and consent to receive marketing emails from Pluralsight.
Table of Contents
  1. Challenge

    Introduction

    Welcome to the Securing an ASP.NET Core 10 API with OAuth2 Client Credentials Flow Code Lab. In this hands-on lab, you'll learn how to protect a Web API using industry-standard OAuth2 authentication. You will configure JWT bearer authentication, protect endpoints with authorization policies, obtain access tokens using the Client Credentials Flow, and validate that your API correctly accepts authorized requests and rejects unauthorized ones. By the end of this lab, you will have a production-ready API with OAuth2 machine-to-machine authentication.

    Why OAuth2 Client Credentials Flow?

    Service-to-service communication is a cornerstone of modern architectures. When one microservice calls another, it needs to prove its identity—not as a user, but as a trusted application. The OAuth2 Client Credentials Flow is the industry-standard solution for this machine-to-machine authentication pattern:

    • The client application (a microservice, background job, or automated tool) authenticates with an identity provider using its own credentials—a Client ID and Client Secret—rather than a user's password.
    • The identity provider validates those credentials and issues a signed JSON Web Token (JWT) containing claims about the client, such as which scopes it is authorized to access.
    • The API receives the JWT in the Authorization header, validates the token's signature and claims, and either grants or denies access based on the result.

    This flow eliminates direct credential sharing between services, centralizes credential management, and provides cryptographic proof of identity through signed tokens. Understanding this pattern is essential for any engineer building secure APIs in production environments.

    What You Will Learn

    This lab covers four core areas:

    1. JWT Bearer Authentication Configuration - You will configure an ASP.NET Core 10 Web API to validate access tokens issued by an OAuth2 identity provider, including setting the token authority and audience.
    2. Endpoint Protection - You will protect API endpoints so that only requests presenting a valid OAuth2 access token can access protected resources.
    3. Token Acquisition - You will obtain an access token using the OAuth2 Client Credentials Flow and send it in the Authorization header of API requests.
    4. Security Validation - You will confirm that authorized requests succeed with HTTP 200 and unauthorized requests return HTTP 401, proving your security configuration works correctly.

    The Scenario

    You are a backend engineer at Globomantics Analytics building an internal metrics API consumed by other microservices. The security team mandates OAuth2 authentication for all service-to-service communication. Your task is to implement JWT bearer authentication using Client Credentials Flow to secure API endpoints while allowing authorized services to access protected resources.

    The Application (Click to expand) #### This lab uses an ASP.NET Core 10 Web API application that serves analytics metrics. The application includes:
    • The MetricsController exposing endpoints for retrieving analytics data, with both public health-check and protected metrics endpoints
    • A local identity provider (TokenController) that simulates an OAuth2 authorization server, issuing signed JWTs using the Client Credentials Flow
    • JWT authentication middleware that validates incoming tokens before allowing access to protected resources
    • Authorization policies that enforce scope-based access control on protected endpoints

    Your Mission

    Your job is to:

    1. Configure JWT bearer authentication in Program.cs to trust tokens from the identity provider
    2. Add authorization policies that require specific scopes in access tokens
    3. Protect the metrics endpoint with the [Authorize] attribute so only authenticated requests succeed
    4. Implement a token request using the Client Credentials Flow in a test script
    5. Send the obtained access token in the Authorization header to access the protected API
    6. Validate that authorized requests return HTTP 200 and unauthorized requests return HTTP 401
    7. Implement scope validation to ensure tokens carry the correct permissions
    8. Run comprehensive security tests confirming the complete OAuth2 flow works end-to-end
    Familiarizing with the Program Structure (Click to expand)
    The lab environment includes the following key files:
    1. `src/Program.cs` - Application entry point where you configure authentication and authorization (you will complete this file) 1. `src/Controllers/MetricsController.cs` - API controller with public and protected endpoints (you will complete this file) 1. `src/Auth/TokenController.cs` - Local identity provider that issues JWTs via the Client Credentials Flow (pre-built) 1. `src/Auth/TokenService.cs` - JWT token generation service (pre-built) 1. `src/Models/TokenRequest.cs` - Model for OAuth2 token requests (pre-built) 1. `src/Models/TokenResponse.cs` - Model for OAuth2 token responses (pre-built) 1. `src/Configuration/AuthSettings.cs` - Authentication configuration constants (you will complete this file) 1. `tests/validate-security.sh` - Security validation script that tests authorized and unauthorized access (you will complete this file)
    The environment uses C# 14 with .NET 10, the `Microsoft.AspNetCore.Authentication.JwtBearer` package for token validation, and Bash scripts with `curl` for testing. All dependencies are pre-installed.

    To build the solution, run:

    dotnet build
    

    To run the API, use:

    dotnet run --project src/GlobomanticsMetrics.csproj
    

    Note: Complete tasks in order. Each task builds on the previous one. Build frequently using dotnet build to catch errors early.

  2. Challenge

    Configuring JWT Authentication

    Understanding JWT Authentication in ASP.NET Core

    Before you configure authentication, you need to understand how ASP.NET Core validates JWT tokens. When a request arrives with an Authorization: Bearer <token> header, the authentication middleware performs several checks:

    Token validation parameters define the rules for accepting or rejecting a token. The middleware verifies:

    • The token's digital signature (proving it was issued by a trusted authority and has not been tampered with)
    • The issuer claim (iss) to confirm who created the token
    • The audience claim (aud) to confirm which API the token is intended for
    • The token’s expiration time

    If any check fails, the middleware rejects the request before it reaches your controller.

    Issuer and audience are two critical claims inside every JWT. The issuer (iss) identifies the authorization server that created the token. The audience (aud) identifies the API that the token is meant for. Your API should only accept tokens where both values match its configuration. This prevents tokens issued for a different API or by an untrusted server from being accepted.

    In this lab, you use a local identity provider built into the application. In production, you would point to an external OAuth2 server such as Azure AD, Auth0, or IdentityServer. The concepts are identical—only the issuer URL changes. ### How ASP.NET Core Authentication Works

    ASP.NET Core uses a middleware pipeline to process requests. Authentication middleware runs early in the request pipeline and inspects incoming requests for credentials. When you call AddAuthentication with a default scheme, you tell the framework which authentication handler to use when no specific scheme is requested. The JwtBearer scheme tells the framework to look for a Bearer token in the Authorization header.

    The AddJwtBearer method configures how tokens are validated. You provide TokenValidationParameters that specify the issuer, audience, and signing key. When a request arrives, the middleware extracts the token, decodes it, checks the signature against your key, and validates the claims. If everything passes, the request is authenticated and the claims from the token become available to your controllers.

    Why configure this manually?

    In many tutorials, the authority URL handles validation automatically by downloading signing keys from the identity provider's discovery endpoint. In this lab, you use a local provider with a symmetric key, so you configure the validation parameters explicitly. This teaches you exactly what the middleware checks, which is valuable knowledge even when using automatic discovery in production.

  3. Challenge

    Protecting API Endpoints

    Understanding Authorization vs Authentication

    Authentication answers the question"Who are you?"—it verifies the caller's identity through the JWT token. Authorization answers the follow-up question "What are you allowed to do?"—it checks whether the authenticated caller has permission to access a specific resource.

    Scope-based authorization is the standard mechanism for enforcing permissions in OAuth2 APIs. When a client requests a token using the Client Credentials Flow, it can request specific scopes that represent permissions. The identity provider includes these scopes as claims in the token. Your API then checks for the required scope before granting access.

    In this lab, the identity provider issues tokens with a scope claim set to "metrics.read". You will create an authorization policy that requires this scope. This means even a valid, properly signed token will be rejected if it does not carry the metrics.read scope—an important defense-in-depth measure. ### Understanding the Authorize Attribute

    The [Authorize] attribute is how you apply security to individual controllers or actions in ASP.NET Core. When placed on a controller or method, it tells the framework to require authentication (and optionally authorization) before executing that code. Without this attribute, an endpoint is accessible to everyone regardless of whether they present a token.

    You can apply [Authorize] at two levels of specificity:

    • [Authorize] alone requires only that the request is authenticated—any valid token passes.
    • [Authorize(Policy = "PolicyName")] requires both authentication and that the caller satisfies the named policy. In your case, this means the token must contain the metrics.read scope.

    The [AllowAnonymous] attribute does the opposite—it explicitly permits unauthenticated access to an endpoint even when the controller has [Authorize] applied. This is useful for health-check endpoints that monitoring tools need to access without tokens.

  4. Challenge

    Testing the OAuth2 Client Credentials Flow

    Why Test Unauthorized Access First?

    Before proving that authorized access works, you should prove that unauthorized access fails. This is a fundamental security testing principle: if your API returns data without requiring a token, your entire security configuration is broken. By testing the negative case first, you establish a baseline—the API rejects unauthenticated requests with HTTP 401 Unauthorized.

    HTTP 401 Unauthorized is the expected response when a request lacks a valid Bearer token. The authentication middleware intercepts the request before it reaches your controller, finds no token (or an invalid one), and short-circuits the pipeline with a 401 response. This happens automatically once you configure JWT authentication and apply the [Authorize] attribute to the endpoint.

    curl is the tool you use to test your API. The -s flag silences progress output, -o /dev/null discards the response body, and -w "%{http_code}" prints only the HTTP status code. This gives you a clean pass/fail signal. ### Understanding the Client Credentials Flow

    The Client Credentials Flow is the OAuth2 grant type designed for machine-to-machine communication. Unlike the Authorization Code Flow (used for user login), there is no browser redirect or user interaction. The client sends its credentials directly to the token endpoint and receives an access token in return.

    The flow works in three steps:

    1. The client sends a POST request to the token endpoint with grant_type=client_credentials, a client_id, a client_secret, and optionally a scope.
    2. The identity provider validates the client credentials and checks that the requested scopes are allowed for this client.
    3. The identity provider returns a JSON response containing an access_token (the signed JWT), token_type (always "Bearer"), and expires_in (the token's lifetime in seconds).

    In this lab, the token endpoint is built into your application at /api/token. In production, this would be a separate identity provider service. The flow itself is identical—the client does not care whether the token endpoint is local or remote.

  5. Challenge

    Validating End-to-End Security

    Sending the Token in the Authorization Header

    Once you have an access token, you include it in the Authorization header of your API request using the Bearer scheme. The format is Authorization: Bearer <token>. When ASP.NET Core's JWT middleware receives this header, it extracts the token, validates the signature, verifies the issuer, audience, and expiration, and reads the claims. If everything passes, the request proceeds to the controller with the authenticated identity available.

    The critical test here is end-to-end: you obtained a token using Client Credentials Flow and now you send it to a protected endpoint. If the endpoint returns HTTP 200 and the expected data, you have proven the complete OAuth2 flow works—from credential exchange to token validation to authorized data access. This is the full circle of service-to-service authentication. ### End-to-End Security Testing

    A complete security validation suite should cover:

    • Public endpoints return 200 without any token (health check)
    • Protected endpoints return 401 without a token (unauthorized access)
    • Protected endpoints return 200 with a valid token (authorized access)

    This three-point verification proves your security configuration is correct: public routes are accessible, protected routes reject strangers, and protected routes welcome authenticated callers with the right permissions.

  6. Challenge

    Conclusion

    Congratulations on completing the Securing an ASP.NET Core 10 API with OAuth2 Client Credentials Flow lab! You have successfully implemented industry-standard OAuth2 machine-to-machine authentication from end to end.

    What You Have Accomplished

    Throughout this lab, you have:

    1. Defined Authentication Constants - Centralized the issuer, audience, and signing key that both the identity provider and the API use for token operations.
    2. Configured JWT Bearer Authentication - Set up ASP.NET Core's authentication middleware to validate tokens by checking the issuer, audience, and digital signature.
    3. Added Authorization Policies - Created a scope-based policy requiring the metrics.read claim, enforcing fine-grained access control beyond basic authentication.
    4. Protected API Endpoints - Applied [Authorize] and [AllowAnonymous] attributes to create layered security with public, authenticated, and scope-restricted endpoints.
    5. Validated Unauthorized Access - Proved that the API correctly returns HTTP 401 when no token is provided, establishing the security baseline.
    6. Obtained an Access Token - Used the OAuth2 Client Credentials Flow to authenticate as a service and receive a signed JWT with the required scopes.
    7. Accessed the Protected Endpoint - Sent the Bearer token in the Authorization header and confirmed the API returns HTTP 200 with the expected data.
    8. Ran the Complete Security Suite - Executed end-to-end tests covering public access, unauthorized rejection, token acquisition, and authorized access.

    Key Takeaways

    • JWT Bearer authentication validates tokens cryptographically - The middleware checks the token's digital signature, issuer, audience, and expiration before trusting any claims.
    • Authorization policies enforce fine-grained access - Even a valid token can be rejected if it lacks the required scope, providing defense-in-depth beyond simple authentication.
    • Client Credentials Flow is for services, not users - This OAuth2 grant type uses client credentials instead of user passwords, making it ideal for machine-to-machine communication.
    • Test both positive and negative cases - Proving that unauthorized requests fail is as important as proving that authorized requests succeed.
    • Centralize security configuration - Sharing constants between the identity provider and the API prevents mismatches that silently break authentication.

    Experiment Before You Go

    You still have time in the lab environment. Try these explorations:

    • Request a token without the metrics.read scope and observe the API's response
    • Change the SecretKey in AuthSettings and observe how token validation fails
    • Add a second protected endpoint that requires a different scope
    • Modify the token expiration to 5 seconds and observe how expired tokens are rejected
    • Add a second client with different permissions in the TokenService

    Take this opportunity to experiment and deepen your understanding!

About the author

Angel Sayani is a Certified Artificial Intelligence Expert®, CEO of IntellChromatics, author of two books in cybersecurity and IT certifications, world record holder, and a well-known cybersecurity and digital forensics expert.

Real skill practice before real-world application

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Learn by doing

Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.

Follow your guide

All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.

Turn time into mastery

On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.

Get started with Pluralsight