TL;DR: This article will show you how to build your Web API with the new ASP.NET Core 3.0 and how to integrate with Auth0 in order to secure them. Following the steps described in this tutorial, you will end up building a simple Web API project, whose full code you can find in this GitHub repository.
"Learn how to build your Web API with ASP.NET Core 3.0."
Tweet This
Prerequisites
Before starting to build your Web API, you need to ensure you have installed the right tools on your machine. In particular, since you are going to use ASP.NET Core 3.0, you need to check if you have installed the .NET Core 3.0 SDK by typing the following command in a terminal window:
dotnet --version
You should get as a result the value 3.0.100
or above. If don't, you should download the .NET Core 3.0 SDK and install it on your machine.
If you are going to use Visual Studio, be aware that you need to use Visual Studio 2019 16.3 or Visual Studio for Mac 8.3 or above.
Note: If you update Visual Studio to the latest version, you will get .NET Core 3.0 SDK bundled.
Creating an ASP.NET Core Web API Project
You are now ready to build your Web API with ASP.NET Core 3.0. The Web API you are going to build will provide a few endpoints that allow you to manage a glossary of terms. So, you will be able to perform the typical CRUD (Create, Retrieve, Update, Delete) operations on the list of term definitions.
In order to create this project, type the following commands in a terminal window:
dotnet new webapi -o Glossary
This command creates a folder named Glossary
and generates inside it the content for the ASP.NET Core project from the webapi
template.
If you are using Visual Studio, you can create your project by selecting the API template as shown in the following picture:
Then follow the steps shown by the wizard to generate the project.
Note: In the rest of the article, the .NET Core CLI (Command-Line Interface) will be used to manage the project, but you can run the same steps by using Visual Studio as well.
Running the Sample Project
Once the project has been created, you should make sure everything is okay. So, in the terminal window, move to the Glossary
folder and launch the sample application by typing the following commands:
cd Glossary
dotnet run
If this is the very first time you run an ASP.NET Core application, you should trust the HTTPS development certificate included in the .NET Core SDK. This task depends on your operating system. Please, take a look at the official documentation to apply the proper procedure.
If everything is okay, open a terminal window and type the following command:
curl --insecure https://localhost:5001/weatherforecast
You should get a bunch of data similar to the following:
[
{
"date": "2019-10-03T11:51:27.955275+02:00",
"temperatureC": 9,
"temperatureF": 48,
"summary": "Mild"
},
{
"date": "2019-10-04T11:51:27.95531+02:00",
"temperatureC": -18,
"temperatureF": 0,
"summary": "Scorching"
},
{
"date": "2019-10-05T11:51:27.955311+02:00",
"temperatureC": -6,
"temperatureF": 22,
"summary": "Balmy"
},
{
"date": "2019-10-06T11:51:27.955311+02:00",
"temperatureC": 35,
"temperatureF": 94,
"summary": "Balmy"
},
{
"date": "2019-10-07T11:51:27.955312+02:00",
"temperatureC": -13,
"temperatureF": 9,
"summary": "Warm"
}
]
This is the output of the sample Web API application that comes with the webapi
project template.
Note: The flag
--insecure
you passed to thecurl
command disables the certificate validation thatcurl
would make by default. This check would cause an error since the HTTPS development certificate provided by the .NET Core SDK is a self-signed certificate.
In the remainder of the article, you will use curl
as the HTTP client to test the Web API. However, you can use the HTTP client you prefer, such as Postman or Insomnia.
To stop the Web API application, simply hit CTRL+C in the terminal window it is running into.
Anatomy of the Project
Regardless you created your project with the .NET Core CLI or Visual Studio, you will get the same collection of files and folders, like the ones shown in the following picture:
Taking a quick look at a few of these files gives you an idea of how a generic ASP.NET Core application works. In particular, the Program.cs
file contains the starting point of the application. Its content looks like the following:
//Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Glossary
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
The static Main()
method is the method that is automatically called when the application starts. It simply calls the CreateHostBuilder()
method to create and configure a host for the application and eventually to run it. A host is an object that collects and provides services for the application. For example, in the ASP.NET context, it provides an HTTP server implementation, middleware components, configuration services, and so on.
As you can see, the webBuilder.UseStartup()
method refers to the Startup
type. This type is defined by the Startup
class in the Startup.cs
file. The default content of this file is shown below:
//Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Glossary
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
The Startup
class allows you to set up services for the current application. In particular, it allows you to register services via the ConfigureServices()
method and to configure the HTTP request pipeline via the Configure()
method. In the body of the latter method, you find a few statements that declare the middleware to use. Please, read the official documentation to get more information about the built-in middleware. Later on, you'll make some changes to the Configure()
method in order to integrate Auth0 services.
The rest of the application code is located in the WeatherForecast.cs
and WeatherForecastController.cs
files. These files implement respectively the model and the Web APIs controller for the sample application coming with the .NET Core template. You are going to get rid of them and create your glossary application.
Creating a CRUD API
Now that you have ensured that the whole development environment is working properly and have taken a tour to understand the project's main elements, start to create your Web API.
As said before, your API will implement the CRUD operations on a glossary. Like most Web APIs, also your API will use an approach inspired to the REST architecture.
To get started, remove the WeatherForecast.cs
file from the root of the project and the WeatherForecastController.cs
file from the Controllers
folder.
Creating the model
As a first step, create the model for the glossary Web API. It will be the representation of the resource that will be exposed to the clients. In particular, it will be implemented as a class representing the single glossary item. So, add a file named GlossaryItem.cs
in the root folder of the project and put the following content in it:
//GlossaryItem.cs
namespace Glossary
{
public class GlossaryItem
{
public string Term { get; set; }
public string Definition { get; set; }
}
}
This class simply defines a glossary item as a term and its definition.
Creating the controller
Now, you need to create the API controller to handle HTTP requests. Create the GlossaryController.cs
in the Controllers
folder and put the following code inside it:
//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace Glossary.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class GlossaryController: ControllerBase
{
private static List<GlossaryItem> Glossary = new List<GlossaryItem> {
new GlossaryItem
{
Term= "Access Token",
Definition = "A credential that can be used by an application to access an API. It informs the API that the bearer of the token has been authorized to access the API and perform specific actions specified by the scope that has been granted."
},
new GlossaryItem
{
Term= "JWT",
Definition = "An open, industry standard RFC 7519 method for representing claims securely between two parties. "
},
new GlossaryItem
{
Term= "OpenID",
Definition = "An open standard for authentication that allows applications to verify users are who they say they are without needing to collect, store, and therefore become liable for a user’s login information."
}
};
}
}
A controller in ASP.NET Core is a class inheriting from ControllerBase
. The base class provides properties and methods that are useful for handling HTTP requests, as you will see later on.
At this stage, the GlossaryController
class simply defines the static variable Glossary
as a small list of glossary items. In a real-world scenario, the glossary items should be persisted into a database, but to keep things simple, your application will rely on this volatile solution.
The class is decorated with two attributes: ApiController
and Route
.
The ApiController
attribute enables a few handy behaviors, like the automatic HTTP 400 responses when the model is in error, the automatic binding of URL parameters to method parameters, and similar.
The Route
attribute allows you to map a URL pattern to the controller. In this specific case, you are mapping the api/[controller]
URL pattern to the controller. At runtime, the [controller]
placeholder is replaced by the controller class name without the Controller
suffix. That means that the GlossaryController
will be mapped to the api/glossary
URL, and this will be the base URL for all the actions implemented by the controller. The Route
attribute can be also applied to the methods of the controller, as you will see.
Getting a list of items
Once you have the basic infrastructure for your controller, you need to add an action to it. An action is a public method of a controller mapped to an HTTP verb. So, add a new method to the GlossaryController
class as shown in the following:
//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace Glossary.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class GlossaryController: ControllerBase
{
private static List<GlossaryItem> Glossary = new List<GlossaryItem> {
//leave the glossary untouched
};
//new code
[HttpGet]
public ActionResult<List<GlossaryItem>> Get()
{
return Ok(Glossary);
}
//end new code
}
}
The method Get()
allows the client to get the whole list of glossary items. It is decorated with the HttpGet
attribute which maps the method to HTTP GET requests sent to the api/glossary
URL.
The return type of the method is ActionResult<List<GlossaryItem>>
. This means that the method will return a List<GlossaryItem>
type object or an object deriving from ActionResult
type. The ActionResult
type represents the HTTP status codes to be returned as a response. As said before, the ControllerBase
class provides a few features to deal with HTTP requests. Some of these features are methods that create HTTP status code in a readable way, like Ok()
, NotFound()
, BadRequest()
, and so on. The Ok(Glossary)
value returned by the Get()
method represents the 200 OK
HTTP status code with the representation of the Glossary
variable as the body of the response.
"In ASP.NET Core, an action is a public method of a controller mapped to an HTTP verb."
Tweet This
Note: An ASP.NET action may return different types: a specific type,
IActionResult
, orActionResult<T>
. Each return type has pros and cons. See the documentation for more information.
To test this first action implementation, run the application by typing the following command in a terminal window:
dotnet run
Note: If you are already running your web app, stop it and run it again. If you don't want manually to stop and start your application while doing changes to the source files, you can run the following command:
dotnet watch run
This causes your application to automatically restart whenever you change the source code.
Then, type the following command in another terminal window:
curl --insecure https://localhost:5001/api/glossary
The output of this command should be similar to the following:
[
{
"term": "Access Token",
"definition": "A credential that can be used by an application to access an API. It informs the API that the bearer of the token has been authorized to access the API and perform specific actions specified by the scope that has been granted."
},
{
"term": "JWT",
"definition": "An open, industry standard RFC 7519 method for representing claims securely between two parties."
},
{
"term": "OpenID",
"definition": "An open standard for authentication that allows applications to verify users are who they say they are without needing to collect, store, and therefore become liable for a user’s login information."
}
]
Getting a single item
The next action you are going to create will allow your client to get a single glossary item. The action will expect a term as input and will provide an instance of GlossaryItem
as a result.
Add a new method definition to the controller class after the previous action definition, as in the following:
//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace Glossary.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class GlossaryController: ControllerBase
{
//leave the rest untouched
//new code
[HttpGet]
[Route("{term}")]
public ActionResult<GlossaryItem> Get(string term)
{
var glossaryItem = Glossary.Find(item =>
item.Term.Equals(term, StringComparison.InvariantCultureIgnoreCase));
if (glossaryItem == null)
{
return NotFound();
} else
{
return Ok(glossaryItem);
}
}
//end new code
}
}
As you can see, you have an overloaded definition of the Get()
method mapped again to the HTTP GET verb. However, in this case, you have a Route
attribute for the action. This attribute appends a new variable element to the URL of the action. So, this action will respond to the HTTP request sent to the URL pattern api/glossary/{term}
. The {term}
part of the pattern can be replaced by any non-empty string and its value will be mapped to the term
parameter.
This method checks if the requested term exists in the Glossary
list and returns the glossary item. It the term doesn't exist, it returns a 404 Not Found
HTTP status code.
Now, restart the application and test this action by typing the following command in a terminal window:
curl --insecure https://localhost:5001/api/glossary/jwt
The result of this command should be the definition of the JWT term:
{
"term": "JWT",
"definition": "An open, industry standard RFC 7519 method for representing claims securely between two parties. "
}
Interested in getting up-to-speed with JWTs as soon as possible?
Download the free ebookCreating an item
Going ahead with the CRUD operations implementation, you need to allow the client to add a new term to the glossary. As a first step, include the System.IO
namespace in the controller class by adding the using System.IO
statement. The using section of the file GlossaryController.cs
should look like the in the following code:
//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.IO;
namespace Glossary.Controllers
{
// ... leave the rest untouched ...
}
You need the System.IO
namespace to use the Path
object, as you will see very soon.
Then, append the Post()
method to the controller class:
//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.IO;
namespace Glossary.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class GlossaryController: ControllerBase
{
//leave the rest untouched
//new code
[HttpPost]
public ActionResult Post(GlossaryItem glossaryItem)
{
var existingGlossaryItem = Glossary.Find(item =>
item.Term.Equals(glossaryItem.Term, StringComparison.InvariantCultureIgnoreCase));
if (existingGlossaryItem != null)
{
return Conflict("Cannot create the term because it already exists.");
}
else
{
Glossary.Add(glossaryItem);
var resourceUrl = Path.Combine(Request.Path.ToString(), Uri.EscapeUriString(glossaryItem.Term));
return Created(resourceUrl, glossaryItem);
}
}
//end new code
}
}
In this case, the action is mapped to the HTTP POST verb via the HttpPost
attribute. The Post()
method has also a glossaryItem
parameter whose value comes from the body of the HTTP POST request. Here, the method checks if the term to be created already exists. If it is so, a 409 Conflict
HTTP status code will be returned. Otherwise, the new item is appended to the Glossary
list. By following the REST guidelines, the action returns a 201 Created
HTTP status code. The response includes the newly created item in the body and its URL in the Location
HTTP header.
Run again the application and add a new glossary item by typing the following command in a terminal window:
curl --insecure \
--verbose \
--request POST \
--url https://localhost:5001/api/glossary \
--header 'content-type: application/json' \
--data '{ "term": "MFA", "definition": "An authentication process that considers multiple factors."}'
As a result of executing this command you should get a long output due to the --verbose
flag. If you look at the end of the output text you should see something similar to the following:
* upload completely sent off: 92 out of 92 bytes
< HTTP/1.1 201 Created
< Date: Thu, 03 Oct 2019 09:34:00 GMT
< Content-Type: application/json; charset=utf-8
< Server: Kestrel
< Transfer-Encoding: chunked
< Location: /api/glossary/MFA
<
* Connection #0 to host localhost left intact
{"term":"MFA","definition":"An authentication process that considers multiple factors."}
Notice the 201 Created
HTTP status code, the value /api/glossary/MFA
for the Location
header and the content of the response body. This is what you said to return at the end of the action execution.
To verify that the new item has been actually added to the glossary, type the following command at the terminal window:
curl --insecure https://localhost:5001/api/glossary/mfa
You should get the newly added item.
Note: Be sure to not stop the application between the item addition and the item existence check. Remember that the glossary is implemented as a static variable, so, when the application stops, you lose any changes.
Updating an item
After adding an item, allow the client of your Web API to update it by appending the Put()
method to the controller class, as shown below:
//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.IO;
namespace Glossary.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class GlossaryController: ControllerBase
{
//leave the rest untouched
//new code
[HttpPut]
public ActionResult Put(GlossaryItem glossaryItem)
{
var existingGlossaryItem = Glossary.Find(item =>
item.Term.Equals(glossaryItem.Term, StringComparison.InvariantCultureIgnoreCase));
if (existingGlossaryItem == null)
{
return BadRequest("Cannot update a nont existing term.");
} else
{
existingGlossaryItem.Definition = glossaryItem.Definition;
return Ok();
}
}
//end new code
}
}
The Put()
method is decorated with the HttpPut
attribute that maps it to the HTTP PUT verb. In short, it checks if the glossary item to be updated exists in the Glossary
list. If the item doesn't exist, it returns a 400 Bad Request
HTTP status code. Otherwise, it updates the item's definition and returns a 200 OK
HTTP status code.
Test this new action by running the application and typing the following command in a terminal window:
curl --insecure \
--request PUT \
--url https://localhost:5001/api/glossary \
--header 'content-type: application/json' \
--data '{ "term": "JWT", "definition": "An open, industry standard RFC 7519 method for representing claims securely between two parties. Auth0 uses JWT format for ID Tokens."}'
To verify if the update was successful, get the glossary item you just modified by typing the following command:
curl --insecure https://localhost:5001/api/glossary/jwt
It should return the glossary item with the new definition.
Deleting an item
Finally, you implement the deletion of an item from the glossary by appending the Delete()
method to the controller class:
//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.IO;
namespace Glossary.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class GlossaryController: ControllerBase
{
//leave the rest untouched
//new code
[HttpDelete]
[Route("{term}")]
public ActionResult Delete(string term)
{
var glossaryItem = Glossary.Find(item =>
item.Term.Equals(term, StringComparison.InvariantCultureIgnoreCase));
if (glossaryItem == null)
{
return NotFound();
}
else
{
Glossary.Remove(glossaryItem);
return NoContent();
}
}
//end new code
}
}
The HttpDelete
attribute maps the method Delete()
to the DELETE HTTP verb. The Route
attribute appends a new element to the URL the same way you learned when implemented the action that gets a single glossary item. So, when a DELETE HTTP request hits the api/glossary/{term}
URL pattern, the method checks if the item exists in the Glossary
list. If it doesn't exist, a 404 Not Found
HTTP status code is returned. Otherwise, the Delete()
method removes the item from the Glossary
list and returns a 204 No Content
HTTP status code.
After you had added this code, run the application and type the following command in a terminal window:
curl --insecure \
--request DELETE \
--url https://localhost:5001/api/glossary/openid
Then, to check that the glossary item no longer exists in the glossary, run the following command:
curl --insecure -I --request GET https://localhost:5001/api/glossary/openid
You should get a 404 Not Found
response similar to the following:
HTTP/1.1 404 Not Found
Date: Thu, 03 Oct 2019 10:57:15 GMT
Content-Type: application/problem+json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
You can check that the other glossary items still exist by getting all the items with the command you already know:
curl --insecure https://localhost:5001/api/glossary
You should get all the glossary items but the one related to openID.
Securing the API with Auth0
You completed the implementation of your glossary Web API. It allows your client to manage the terms and their definitions through the typical CRUD operations over HTTP, but maybe your system is not so safe. In fact, the current implementation allows anyone to perform any available operation on the glossary. Most probably, you want to allow everyone to get a single glossary item or the full glossary, but only authorized users should be enabled to create, update, and delete glossary items.
Take a look at how to protect these three actions by integrating your ASP.NET Core Web API with Auth0 services.
Create the API application
To start securing your API, you need to access the Auth0 Dashboard. If you haven't an Auth0 account, you can sign up for a free one. Now, move to the API section of the Dashboard and follow these steps:
- Click on Create API
- Provide a friendly name for your API (for example, Glossary API) and a unique identifier in the URL format (for example, https://glossary.com)
- Leave the signing algorithm to RS256 and click the Create button
These steps make Auth0 aware of your Web API and will allow you to control access.
Note: While you are in the Auth0 Dashboard, take note of your Auth0 domain, you will need it soon. This is a string in the form
YOUR-TENANT-NAME.auth0.com
whereYOUR-TENANT-NAME
is the name you provided when you created your account with Auth0. For more information, check the documentation.
Configure the Web API
Now, back in your ASP.NET Core application, open the appsettings.json
configuration file and replace its content with the following:
//appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"Auth0": {
"Domain": "YOUR_AUTH0_DOMAIN",
"Audience": "YOUR_UNIQUE_IDENTIFIER"
}
}
Replace the YOUR_AUTH0_DOMAIN
placeholder with your Auth0 domain and the YOUR_UNIQUE_IDENTIFIER
placeholder with the value you provided as a unique identifier of your API.
Integrate with Auth0
In order to interact with the Auth0 authorization services, your application needs to be able to handle tokens in the JWT (JSON Web Token) format. You can accomplish this by installing the Microsoft.AspNetCore.Authentication.JwtBearer
library. Move in your application folder and type the following command in a terminal window:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
When the installation is complete, open the Startup.cs
file and add the JwtBearer
namespace. The using
section of the file should look as shown below:
//Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Authentication.JwtBearer;
namespace Glossary
{
// ... leave the rest untouched ...
}
In the same file, replace the ConfigureServices()
method of the Startup
class with the following code:
//Startup.cs
// ... leave the rest untouched ...
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = $"https://{Configuration["Auth0:Domain"]}/";
options.Audience = Configuration["Auth0:Audience"];
});
services.AddControllers();
}
// ... leave the rest untouched ...
You added the authentication service by specifying the JWT bearer scheme and providing the authority and audience values from the appsettings.json
configuration file.
Finally, replace the Configure()
method of the Startup
class with the following code:
//Startup.cs
// ... leave the rest untouched ...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
// ... leave the rest untouched ...
Compared to the previous version, you just added the app.UseAuthentication()
statement to enable authentication for your application.
Securing the endpoints
Almost everything is ready to secure your application. You just need to protect the actions that allow clients to create, update, and delete glossary items. So, open the GlossaryController.cs
file in the Controllers
folder and add the Microsoft.AspNetCore.Authorization
namespace. The using
section of the file will look like the following:
//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using Microsoft.AspNetCore.Authorization;
namespace Glossary.Controllers
{
// ... leave the rest untouched ...
}
Now, add the Authorize
attribute to the Post()
, Put()
, and Delete()
methods. As an example, your Delete()
method should become like shown below:
//Controllers/GlossaryController.cs
...
[HttpDelete]
[Route("{term}")]
[Authorize]
public ActionResult Delete(string term)
{
var glossaryItem = Glossary.Find(item =>
item.Term.Equals(term, StringComparison.InvariantCultureIgnoreCase));
if (glossaryItem == null)
{
return NotFound();
}
else
{
Glossary.Remove(glossaryItem);
return NoContent();
}
}
...
As you can see, the only difference is the Authorize
attribute.
Note: You may be wondering if the order of the attributes applied to a method or to a controller class matters. Their order is internally defined and it depends on the stage of the HTTP pipeline they are bound to and on their scope (global, class or method scope). So, from a syntactical point of view, it doesn't matter if you put, for example, the
Authorize
attribute before or after theRoute
one. For more information on this point, check the official documentation.
"Integrating an ASP.NET Core Web API application with Auth0 is a matter of a few lines of code."
Tweet This
Testing the Secure Web API
In order to ensure that all works as expected, run the application and check if the not protected actions still work as before. So, type the following command in a terminal window:
curl --insecure https://localhost:5001/api/glossary/jwt
You should get again the glossary item corresponding to the JWT term.
Now, try to add a new item to the glossary by typing again the following command:
curl --insecure \
--verbose \
--request POST \
--url https://localhost:5001/api/glossary \
--header 'content-type: application/json' \
--data '{ "term": "MFA", "definition": "An authentication process that considers multiple factors."}'
This time you should get a 401 Unauthorized
HTTP status code. In fact, the last part of your output should be similar to the following:
< HTTP/1.1 401 Unauthorized
< Date: Thu, 03 Oct 2019 15:20:15 GMT
< Server: Kestrel
< Content-Length: 0
< WWW-Authenticate: Bearer
This means that your action is protected. So, in order to add a new item to the glossary, you need to get an access token from Auth0. The simplest way is to access again the API section of your Auth0 Dashboard, select the API you created before and select the Test tab. In this section, you can get a temporary token to test your Web API by clicking the Copy Token link as shown in the following picture:
Now, head back to your terminal window and type the following command, where the YOUR_TOKEN placeholder must be replaced with the copied token:
curl --insecure \
--verbose \
--request POST \
--url https://localhost:5001/api/glossary \
--header 'content-type: application/json' \
--header 'authorization: Bearer YOUR_TOKEN' \
--data '{ "term": "MFA", "definition": "An authentication process that considers multiple factors."}'
Now you should get a successful 201 Created
HTTP status code as before securing the API.
To ensure that actually the new glossary item has been added, run the following command:
curl --insecure https://localhost:5001/api/glossary/mfa
It should return the newly added glossary item. Now your Web API is ready to be securely consumed by clients.
Recap
In this article, you learned to create a new Web API project with ASP.NET Core 3.0. You started by exploring the sample project generated by the CLI command dotnet new
and continued by replacing the existing code with yours. You implemented the CRUD operations on a simple glossary and learned how to map methods to HTTP verbs by leveraging the proper attributes. Also, you discovered how ASP.NET Core lets you return HTTP responses in a readable way. Finally, you secured a subset of the CRUD operations by integrating the Web API application with Auth0.
You can download the full source code of the application from GitHub.