Integrate Keycloak with ASP.NET Core Using OAuth 2.0
A beginner-friendly guide to securing an ASP.NET Core API and web app with Keycloak using OAuth 2.0 and OpenID Connect, with diagrams, tables, and copy-paste code.
A security guard at the office gate
Imagine a big office building in your city. At the front gate there is a security guard. When you arrive, you do not argue with every department about who you are. You go to the guard once, show your ID, and the guard gives you a visitor pass. After that, you simply show the pass at each floor. The people on each floor trust the pass because they trust the guard who issued it.
Keycloak is that security guard for your apps. It checks who the user is, then hands out a token (the visitor pass). Your ASP.NET Core API does not check passwords. It just looks at the token and trusts it, because it trusts Keycloak.
This is a very nice way to build software. You do not have to store passwords, build a login page, or handle "forgot password" emails. Keycloak does all of that. Your job is to trust its tokens. Let us learn how.
What is Keycloak?
Keycloak is a free, open-source identity and access management server. It is run by the CNCF community and used by companies all over the world. It gives you:
- A ready-made login and sign-up page.
- User accounts, roles, and groups.
- Social login (Google, GitHub, and more).
- Standard protocols: OAuth 2.0 and OpenID Connect (OIDC).
Because Keycloak speaks these standard protocols, ASP.NET Core can talk to it using the built-in authentication packages. You do not need a special "Keycloak SDK" for the basics.
Two words you will hear a lot
Before code, let us make two words simple.
| Term | Plain meaning | Real-life version |
|---|---|---|
| Authentication | Proving who you are | Showing your ID at the gate |
| Authorization | Deciding what you can do | The pass only opens certain floors |
| OAuth 2.0 | A standard for giving access (authorization) | The rules for issuing passes |
| OpenID Connect | A layer on OAuth 2.0 that adds login (authentication) | The rule that the pass also proves your name |
| Realm | A separate "world" of users in Keycloak | One building with its own guard and staff |
| Client | Your app, as registered inside Keycloak | A tenant company in the building |
Keep this table in mind. When you see "realm" later, just think "one building."
The big picture
Here is how the pieces fit together. The user logs in at Keycloak, gets a token, and then sends that token to your API.
The token is a JWT — a JSON Web Token. It is a small, signed string that says "this is user Ravi, and these are his roles." Because Keycloak signs it, your API can check the signature and know it was not faked.
Running Keycloak locally
The fastest way to try Keycloak is Docker. This one command starts a Keycloak server on port 8080 with an admin user.
docker run -p 8080:8080 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:26.0 start-devNow open http://localhost:8080 and log in with admin / admin. Inside the admin console you will:
- Create a realm (for example,
myrealm). This is your building. - Create a client (for example,
myapp). This is your app's identity. - Create a user with a password, so you have someone to log in as.
For a web app that logs people in, set the client's access type to confidential and add a client secret. For a pure API that only checks tokens, you mostly care about the realm name and the audience.
Setting up the Keycloak client
When you create the client, a few settings matter. Here is a quick guide.
| Setting | What to put | Why it matters |
|---|---|---|
| Client ID | myapp | Your app's name inside the realm |
| Client authentication | On (confidential) | Requires a secret, safer for web apps |
| Valid redirect URIs | https://localhost:5001/signin-oidc | Where Keycloak sends the user back |
| Web origins | https://localhost:5001 | Allows the browser to talk to Keycloak |
| Valid post logout URIs | https://localhost:5001/signout-callback-oidc | Where to land after logout |
The redirect URI is important. After login, Keycloak must send the user back to your app. If this URL does not match exactly, Keycloak refuses, and you see an error. Think of it like the guard only releasing your pass to the correct office address.
The login flow (Authorization Code with PKCE)
For a web app where a real person logs in, ASP.NET Core uses the Authorization Code flow with PKCE. PKCE (say it "pixie") is a safety check that stops attackers from stealing the login code in the middle.
Authorization Code flow with PKCE
Steps
Redirect
App sends user to Keycloak
Login
User enters username and password
Code back
Keycloak returns a short-lived code
Swap for tokens
App exchanges code + secret for tokens
Signed in
App stores tokens in a cookie
Let us see the same flow as a sequence, so the order is crystal clear.
The good news: you do not write this flow by hand. The ASP.NET Core OpenID Connect handler does it for you. You only configure it.
Wiring up a web app with OpenID Connect
This is for an app that logs users in (like an MVC or Razor Pages app). Add these packages first.
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnectNow configure authentication in Program.cs. The app keeps the user signed in with a cookie, and uses Keycloak only at login time.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
// The realm URL is the "Authority" — Keycloak's address for this building.
options.Authority = "http://localhost:8080/realms/myrealm";
options.ClientId = "myapp";
options.ClientSecret = builder.Configuration["Keycloak:ClientSecret"];
options.ResponseType = "code"; // Authorization Code flow
options.UsePkce = true; // PKCE is on by default, shown here for clarity
options.SaveTokens = true; // Keep tokens so we can call APIs later
options.Scope.Add("openid");
options.Scope.Add("profile");
// In dev only: Keycloak on http needs this. Never do this in production.
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = true;
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/profile", (ClaimsPrincipal user) =>
$"Hello, {user.Identity?.Name}!")
.RequireAuthorization();
app.Run();A few things to notice:
- The
Authoritypoints to the realm, not the bare server. The handler readsAuthority + /.well-known/openid-configurationto learn everything else. UsePkce = trueis the default, so you are safe even if you forget it.RequireHttpsMetadata = falseis only for local development with plainhttp. Turn it on in production.
When a user hits /profile, ASP.NET Core sees they are not logged in, sends them to Keycloak, and brings them back signed in. Lovely.
Securing an API that only checks tokens
Many real systems split things up. A front-end (maybe Angular or React) does the login. Then it calls a back-end API with the access token in the Authorization header. The API's only job is to validate the token — it never shows a login page.
For this, you use JWT Bearer authentication instead of OpenID Connect.
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearerHere is the API setup. Notice how short it is.
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "http://localhost:8080/realms/myrealm";
options.RequireHttpsMetadata = false; // dev only
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "http://localhost:8080/realms/myrealm",
ValidateAudience = true,
ValidAudience = "account", // check the 'aud' claim of your token
ValidateLifetime = true // reject expired tokens
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/orders", () => "Your secret orders 📦")
.RequireAuthorization();
app.Run();The most important idea: your API does not call Keycloak on every request. It downloads the public signing keys (the JWKS) once from the discovery endpoint, then checks each token's signature locally. That is why JWT validation is fast.
Using roles from Keycloak
Authentication tells you who the user is. Authorization decides what they may do. Keycloak puts the user's roles inside the token. You can use them with policies.
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminsOnly", policy =>
policy.RequireRole("admin"));
});
app.MapGet("/admin", () => "Welcome, boss 👔")
.RequireAuthorization("AdminsOnly");One small catch: Keycloak nests realm roles under a realm_access.roles claim, which ASP.NET Core does not read as roles by default. You usually add a tiny piece of code that copies those values into proper role claims. Many teams write a small ClaimsTransformation for this, or map the claim type so RequireRole("admin") just works.
From request to allowed action
Steps
Request
Token in Authorization header
Validate token
Signature, issuer, audience, expiry
Check role
Does the user have 'admin'?
Run code
Return the protected data
Token types you should know
Keycloak gives out a few different tokens. Knowing them avoids confusion.
| Token | What it is for | Lifetime |
|---|---|---|
| ID token | Proves who the user is (for the app) | Short |
| Access token | Sent to APIs to gain access | Short (minutes) |
| Refresh token | Gets a new access token without re-login | Longer |
The access token is the one your API checks. The refresh token lets the front-end stay logged in quietly. The ID token is mostly used by the web app to read the user's name and email.
Common mistakes and how to avoid them
A few errors trip up almost every beginner. Here is a short rescue list.
- Wrong audience. If you see
401even with a valid login, yourValidAudienceprobably does not match theaudclaim. Decode the token at jwt.io and look at the realaudvalue, then setValidAudienceto that. - Redirect URI mismatch. Keycloak is strict. The redirect URI in the client must match your app's URL exactly, including
httpsand the port. - Issuer mismatch. If your app reaches Keycloak at
localhostbut the token issuer sayskeycloak(a Docker hostname), validation fails. Keep the hostnames consistent. - Using http in production.
RequireHttpsMetadata = falseis fine on your laptop, but it must betrueonce you go live.
Quick recap
- Keycloak is a free identity server. It is the security guard that checks users and hands out tokens, so your app never stores passwords.
- A realm is one "building" of users; a client is your app's identity inside it.
- For web apps that log people in, use OpenID Connect with the Authorization Code flow + PKCE. ASP.NET Core does the hard work; you just configure the
Authority,ClientId, andClientSecret. - For APIs that only verify tokens, use JWT Bearer. Set the
Authority,ValidIssuer, andValidAudience. - Your API validates tokens locally using Keycloak's signing keys — it does not call Keycloak on every request, so it stays fast.
- Use roles from the token with authorization policies to control what each user can do.
- Watch out for audience, issuer, and redirect URI mismatches — they cause most beginner errors.
References and further reading
- Integrate Keycloak with ASP.NET Core Using OAuth 2.0 — Milan Jovanović
- Keycloak Server Administration Guide
- ASP.NET Core authentication overview — Microsoft Learn
- Configure JWT bearer authentication in ASP.NET Core — Microsoft Learn
- Implement ASP.NET Core OpenID Connect with Keycloak — Damien Bod
Related Posts
Authentication and Authorization Best Practices in ASP.NET Core
A friendly guide to authentication and authorization in ASP.NET Core for .NET 10 — JWT, cookies, claims, roles, policies, and security best practices with diagrams.
Refresh Tokens and Token Revocation in ASP.NET Core: A Beginner Guide
Learn refresh tokens and token revocation in ASP.NET Core with simple words, diagrams, and code. Short access tokens, rotation, reuse detection, and safe logout.
Building Secure APIs with Role-Based Access Control in ASP.NET Core
Learn role-based access control (RBAC) in ASP.NET Core. Add roles to JWT tokens, guard endpoints with policies, and return correct 401 and 403 codes, with diagrams and code.
API Key Authentication in ASP.NET Core: The Secure Way
Learn how to add API key authentication to your ASP.NET Core API the right way. Use an AuthenticationHandler, hash keys, compare safely, and follow 2026 security best practices, with diagrams and code.
How to Add JWT Authentication to SignalR Hubs in ASP.NET Core
A beginner-friendly guide to securing SignalR hubs with JWT tokens in ASP.NET Core, including the access_token query string trick and the [Authorize] attribute.
Implementing API Gateway Authentication With YARP in .NET
Learn to build a secure API gateway in .NET using YARP. Add authentication, per-route authorization policies, and pass user identity to backend services.