Secure Your .NET (6.0+) Api: A Guide to Authentication Setup (Part 2)
- OLIVIER VERPLANCKE

- Jan 10, 2023
- 11 min read
Updated: Jan 4, 2024
This blog post series is about how you can secure your API's using Azure to prevent unauthorized access. This is part 2.
Part 1 is how to set up an API from zero using versioning and swagger.
Part 2 (this post) is how to authorize as a user to the secured API.
Part 3 is how to authorize from 1 API client to another, B2B.
Part 4 is how to implement scope based access.
Part 5 is how to set up 'advanced' configuration: multi environment, secrets ...
Part 6 is how to set up code-first migration using Entity Framework with SQL Server

Introduction
Technology we'll be using:
.NET 6.0 (.NET 8 recently released - recommendation is to set up your APIs in 8)
Azure - Microsoft's cloud platform
Requirements, what do you need:
Azure portal account - we're using Azure as our Identity Provider. Go to https://portal.azure.com and sign up for free.
Solution of Part 1
Concepts we'll explain here
JWT tokens
Access token vs Id token
Identity Provider
OAuth2
Authentication vs Authorization
Before implementing anything, it is important to understand the concepts behind the implementation. Once you understand the concept, you can apply it in any environment, regardless of the language you're using.
Remember the saying: "Give a man a fish, he'll eat for a day. Teach the man how to fish, he'll eat for a lifetime."? It sort of applies here as well. Learning the most common features of a programming language takes a seasoned developer up to 2 months, depending on the language in question. So don't get stuck into the specific implementations of a language, but try to see how it works. We all have to start somewhere, and in this case we'll showcase the concepts using the .NET stack and Azure as our Identity Provider.
The result of this post can be found on my github repo.
Note: I removed the clientId and tenantId values from my configuration files. You'll have to enter those of your own tenant. Otherwise the solution won't work.
One more thing: The topic security is a very broad topic. People write entire books about this. What I am covering in these posts is the basis of setting up your API in a secure way. This is like eating an Elephant. We can only do it one bite at a time.
Let's begin!
JWT Token
"JSON Web Token is a proposed Internet standard for creating data with optional signature and/or optional encryption whose payload holds JSON that asserts some number of claims. The tokens are signed either using a private secret or a public/private key." - Wikipedia
We use JWT tokens in our requests in order for the server (APIs) to authorize web requests.
Example of a JWT token. Encoded format:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
{
alg: "HS256",
typ: "JWT"
}.
{
sub: "1234567890",
name: "John Doe",
iat: 1516239022
}.
[signature]Each property within the pink section is what is called a claim. Typically what you add in your claims is the scopes which authorize you to access given resources of an API.
My go-to website when working with tokens: JSON Web Tokens - jwt.io
The header and signature are used to verify the authenticity of the token. The payload contains the information requested by the user to be stored within it. If the token checks out, you can assume the payload data to be the truth.
You put the JWT token in the headers of your HTTP request.
Header: authorization
Value: Bearer eyJhbGciOiJIUzl1NilsIn...
Access token vs Id token
There are 2 types of JWT tokens you can request, an id token and an access token.
An access token is for passing on to an API to request access to secure resources. An id token is for you to use within your application itself. It proves you have been authenticated against the IDP. Typically an id token is used to pre-fill forms, to show personalized messages within your application ...
Identity provider
"An identity provider (IDP) is a system entity that creates, maintains, and manages identity information for principals and also provides authentication services to relying applications within a federation or distributed network. Identity providers offer user authentication as a service." - Wikipedia
To put it simply; we use a 'centralized' system where we, as a user, enter our credentials. This in exchange allows an application to perform actions in our name.
Think of it in this way:
You go to a doctor. (S)He gives you a signed prescription note for medication. You take that note and in turn go to the apothecary. You hand over the note to the apothecary, who validates the legitimacy of the note. If it checks out, you receive your medication.
This simple story showcases the essence how authentication using JWT tokens works.
In this story the doctor is the Identity Provider and the apothecary is the API which hands over the resource (medication).
You visit the doctor and identify yourself (using an id)- you sign in.The doctor gives you a signed note - you receive a JWT token, stating you are authorized for only this given resource (medication) - claims / scopes within the token. With this signed note, you can now go to the apothecary, who validates the signature - validates jwt token, and gives you the requested resource.
This shows the advantage of a JWT token, the services don't require direct contact with the IDP. A JWT token is a self-contained token.
OAuth2
"OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices." - OAuth
In this article I'll cover the topics of oauth2 which are applicable for our use-case where we wish to set-up authentication & authorization for an SPA to API and API to API.
Flow 1 - client-credentials
Is used to acquire an access token outside the context of a user.
Used between 2 API's.
Flow 2 - authorization code with PKCE (proof key for code exchange)
Used for SPA or MVC applications.
What does PKCE protect you against? The PKCE protects against having a malicious app on the device to steal a token that is intended for another app.
Flow 3 - implicit
"The Implicit flow was a simplified OAuth flow previously recommended for native apps and JavaScript apps where the access token was returned immediately without an extra authorization code exchange step.
It is not recommended to use the implicit flow (and some servers prohibit this flow entirely) due to the inherent risks of returning access tokens in an HTTP redirect without any confirmation that it has been received by the client." - OAuth2.net
I mention it here as I personally have set up this flow for my swagger endpoint. It allows access tokens to be returned from the /authorize code endpoint of your IDP. If you do use it, only use it internally in your organization.
When creating APIs as a commercial product and exposing it to the world, we do not allow this authorization to be used.
Authentication vs Authorization
Authentication verifies the identity of the user, service ... ex: You as a user entering username and password. If correct, it verifies you are that person.
Authorization determines your access rights.
ex: User 'Olivier' is 'administrator'. As an administrator you have elevated permissions.
How to get an access token?
Authorization code flow

The drawing below is based off the one from the very well documented page of Microsoft about SPA authentication & authorization. I just wanted a higher resolution one.

Client credentials flow

Configuration in IDP
Now that we understand how we get a token, let's start by configuring our permissions.
Try to understand the principles of what I'm explaining. We're using the Azure Portal as our Identity Provider. But if you understand the principles of configuring an application and how permissions work using scopes and claims, you can apply this to any other identity provider. Remember "teach the man to fish" quote from before. In the future I'll add an example how you can do the exact same configuration using Keycloak (thanks for the tip, Niels!).
Let's dive into it!
We need to configure the permissions in our IDP. It is the responsibility of the IDP to say if our application or user is authorized to access a given resource.
All the configurations I'm doing here are done as an administrator. This depends on the policies within your company if you'll be able to do these configurations yourself or not.
#1 - App registration
In Azure, configuring IDP rights is done in the "App registrations" blade. If you can't find it, go to the search bar on top of the Azure portal and enter "App registrations".

Select "+new registration"
Give it a name: "authdemo-dev"
Supported account types: "Accounts in this organizational directory only"
Redirect url (optional): Select SPA and enter "https://localhost:7156/oauth2-redirect.html"
Click on register

Now we have an application registered within Azure which we can use to apply authentication with. Go to the Authentication tab to see our redirect url we configured.
In there, set "Allow public client flows" to "Yes" and click <Save>
#2 - configuring scopes
We talked about scopes before. They're used to authorize access to resources. Here we define how many we wish to configure. For our use-case we'll add 2. A read scope and manage scope. These are in preparation for the last part (4) of this series of blog posts - scope / role based access.
- Navigate to the Token configuration tab.
- Click on "Add a scope"
- This will prompt a drawer "You'll need to set an Application ID Uri before you can add a permission". It is the prefix for scopes and in access tokens, it is the value of the audience claim. Also referred to as an identifier URI.
- To make it readable, we'll give it a human readable name: api://authdemodev. In the past I've used the default suggested name, which is "api://<guid>", the guid being your application client id.

- Click on Save and continue
- Now you get a drawer asking to enter scope name, who can consent, display values ... We'll just enter a scope name and description and leave the consent to "Admins only". This isn't as important for our demo. We'll add a scope for read and one for manage (to be able to add/update/delete data in our system).


With this we registered our API and stated which scopes we wish to work with.
In our IDP (Azure portal) there is only 1 step remaining, specify which client has access to our API's protected resources.
#3 - Allow a client application to access our API's protected resources
An API is typically accessed from multiple clients. This can be a web app, mobile app, desktop app, swagger ... So for multiple, interactive, clients we can specify multiple levels of access. For instance, we can make a management portal for our back-office team which requires all features our API's have to offer. For our mobile app however, which is used by our customers, we only want them to be able to read data.
Since we're configuring swagger, which is the test platform for our API, we will obviously allow it to have access to all of our scopes.
How I typically configure swagger is that the client id is the same as that of the API, as both belong together. This means we'll use the client id of the API for our swagger ui. Our own API is going to be a "client" of itself if you will.
Adding a client application
Get your app registration's client id, you can find this in the "Overview" tab. Copy it to your clipboard.
In the same tab where we defined our scopes, you'll see at the bottom "+ Add a client application".
A drawer appears asking for the client id & authorized scopes. We enter our client id and select both scopes


That's it for our IDP configuration. We now have everything, from the IDP's point of view, ready to set up the authorization for our swagger client in our API.
A quick recap of what we did
Add an app registration - this our identity resource in our Azure tenant
Add scopes - granularity of authorization levels within our application
Authorize a client access to the given scopes
Next step is now to configure our API to secure itself with this information.
Configuring API with IDP information
Now we'll configure the IDP information within our application. As we're developing in .NET and are using Azure, we're going to make use of some small benefits this combination has.
Let's start by installing a few additional NuGet packages into our API. All these packages will ask to install some additional dependencies (other NuGet packages they're depending on). Allow the installation of those.
Microsoft.Extensions.Dependency.Injection: Make sure you use the correct version. The version of the NuGet package directly corresponds to your chosen .NET version. In our case we'll install v6.0.1 (highest available for .NET 6 at the moment of writing).
This is going to allow us to add authentication using extension methods provided by Microsoft
.AddAuthentication(...);
.AddHttpContextAccessor();
.AddCors();Microsoft.AspNetcore.Authentication.JwtBearer: Same here, use the version corresponding to your .NET version. In our case we'll install v6.0.13
This allows us to configure the out of the box working of the bearer scheme.
Microsoft.Identity.Web: I used 1.25.10 (highest available)
This is going to allow us to add a predefined configuration model in our appsettings which will parse all of the IDP fields out of the box. Instead of having to write all of it yourself.
.AddMicrosoftIdentityWebApi(...)Create a new static extensions class , AuthenticationExtensions in Program.Authentication.cs
Add the following:
public static void LoadAuthentication(this IServiceCollection services, ConfigurationManager configuration)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(configuration, "Auth");
services.AddHttpContextAccessor();
services.AddCors();
}
public static void UseAuthenticationAndAuthorization(this WebApplication app)
{
//must add UseAuthentication before UseAuthorization
app.UseAuthentication();
app.UseAuthorization();
}The part of adding the microsoft identity is what I mentioned before using the out of the box model provided by Microsoft to use.
.AddMicrosoftIdentityWebApi(configuration, "Auth")In our appsettings.Development.json we'll add the following configuration
TenantId and ClientId are both to be found on the "Overview" tab in your app registration.
"Auth": {
"Instance": "https://login.microsoft.com/",
"TenantId": "<guid>", // copy your own tenant id here
"ClientId": "<guid>", // application client id
"Audience": "api://authdemodev"
}Notice we added AddHttpContextAccessor(); and AddCors(); The first one is so we are able to access our user principle who is authenticated via the call using the IHttpContextAccessor interface. We are not using it in this part of our demo yet, but later on we'll showcase its use. To use it, simply inject your IHttpContextAccessor interface in the constructor of your service(s). Cors is for cross origin resource sharing. Browser security prevents a web page from making requests to a different domain than the one that served the web page. This restriction is called the same-origin policy. The same-origin policy prevents a malicious site from reading sensitive data from another site. Sometimes, you might want to allow other sites to make cross-origin requests to your app. Enable Cross-Origin Requests (CORS) in ASP.NET Core | Microsoft Learn
In the Program.cs file, add the following entries
builder.Services.AddAuthentication(configuration);
...
app.UseAuthenticationAndAuthorization();With this we added basic authentication and authorization to our API.
Press F5 to debug.
When executing the http DELETE, you no longer will get the error "System.InvalidOperationException: Endpoint HTTP: DELETE /movies/{id} contains authorization metadata, but a middleware was not found that supports authorization."
Instead, it'll say you're unauthorized:

Configuring Swagger with IDP
Add configuration values in appSettings.Development.json
Update the Auth entry in the settings file by adding:
"Swagger":{
"ClientId": "8061....c1ee"
"Scopes":[
"api://authdemodev/read",
"api://authdemodev/manage"
]
}#1 - Update the SwaggerExtensions class methods
Extend the .UseSwaggerUI(options => { ... }); method
c.EnablePersistAuthorization();
c.OauthClientId(app.Configuration["Auth:Swagger:ClientId"]);
c.OAuthScopes(app.Configuration.GetSection("Auth:Swagger:Scopes")
.Get<string[]>());
c.OAuthUsePkce();
Here we tell to our swagger UI to fill out the client id and scopes by default.
c.EnablePersistAuthorization() is self-explanatory, it persists you having logged in to the swagger. You don't need to click on the "Authorize" button every time you debug your application. This saves 10seconds every time you click on debug. Per 100 times you clicked on debug it saves a total of 15minutes. Not bad for 1 line in your setup right? And you reach that 100 times very fast.
Extend .AddSwaggerGen(options => { ... });
var authUrl = $"{configuration["Auth:Instance"]}{configuration["Auth:TenantId"]}/oauth2/v2.0";
var authorizationUrl = $"{authUrl}/authorize";
var tokenUrl = $"{authUrl}/token";
var scheme = new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Name = "Authorization",
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(authorizationUrl),
TokenUrl = new Uri(tokenUrl)
}
},
Type = SecuritySchemeType.OAuth2
};
c.AddSecurityDefinition("OAuth", scheme);
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Id = "OAuth", Type = ReferenceType.SecurityScheme }
},
Array.Empty<string>()
}
});All what the above does is tell Swagger how to authenticate against our configured IDP. Which urls, which id's, which scopes etc to use. This is the security definition. As we're using a NuGet package, it's up to the us as developers to read up on the documentation how to set this up properly.
After all of this has been written, your Program.cs class should look like this:

Test your solution now by pressing <F5>
You will now notice there is an extra button on the Swagger ui <Authorize>.
Secure endpoints can only be called after we authorize ourselves. You can see if you're already logged in (or not) based on the lock icon of the Authorize button (open = not logged in, closed = logged in).
Click on it and you'll be prompted with a menu

Click authorize, you'll have to log in as a user who is known on your configured tenant.
After this, execute the http DELETE method. You should now get an HTTP 200 response back.

Our bearer token, which is passed in the header of the request, can be inspected by copying the value and pasting it into the https://jwt.io website. Notice the scp property containing our 2 scopes we defined in the IDP. Afterwards we configured those for Swagger to be used in our appsettings.Development.json

Recap
In this post went over important concepts which are relevant for OAuth configuration. We configured an IDP, in Azure to allow access to our secure API.
We set up security for our API, only authorized users have access.
We configured our Swagger to act on the user's behalf to call protected resources.
The source code of this post can be found on my github repo.
Stay tuned for Part 3 - B2B - API to API security.



Comments