Nowadays, RESTful APIs are the standard way of exposing backends to applications.
They allow you to share your business logic between different clients with a low level of coupling through a super-standardized protocol: HTTP.
One of the biggest challenges when building REST API is authentication. Typically, we manage this with JWTs. Unfortunately, ASP.NET Core doesn’t fully support this out-of-the-box.
The good news is that the Stormpath ASP.NET Core library allows us to add JWT authentication to any API with minimal configuration.
In this tutorial, we will create a REST API in ASP.NET Core to manage a list of books.
Our example API will allow users to register and login to manage their books. This simple project could be the base for a future social media application that connects readers and supports book reviews.
The source code is available on GitHub, so feel free to check the finished code and play with it.
Let’s get started!
Create the Web API Project
Open up Visual Studio, and create a new ASP.NET Core Web Application project.
Select the “Web API” template and make sure authentication is set to “No Authentication”.
Now, we are going to create our Book model. Add a folder named “Models” at the root of the project, and then inside of it create the Book class:
public class Book { public int Id { get; set; } public string Title { get; set; } public string Author { get; set; } public DateTime PublishedDate { get; set; } }
Create a new folder named “Services”. To save our books, we are going to use a useful new great feature available in EF Core: the in-memory data provider. This feature is awesome because we don’t have to spend time setting up a database to test our API. Later on, we can easily swap this provider with one that uses a persistent storage like a database, for example.
If you’re interesting in exploring EF Core as an in-memory data provider further, check out Nate’s article on the subject!
Set Up Entity Framework Core
Right-click on your project and select “Manage NuGet packages”. Then, add the package Microsoft.EntityFrameworkCore.InMemory
Add the BooksAPIContext
class inside the Models folder, which will implement the DbContext
class and will be responsible for the interactions between our application and the data provider.
public class BooksAPIContext : DbContext { public BooksAPIContext(DbContextOptions options) : base(options) { } public DbSet<Book> Books { get; set; } }
On the ConfigureServices
method of the Startup
class, we are going to configure our context to use the in-memory data provider:
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddDbContext<BooksAPIContext>(x => x.UseInMemoryDatabase()); // Add framework services. services.AddMvc(); }
Create the IBookRepository
interface inside of the Services folder:
public interface IBookRepository { Book Add(Book book); IEnumerable<Book> GetAll(); Book GetById(int id); void Delete(Book book); void Update(Book book); }
And a concrete InMemoryBookRepository
class that will use the BookAPIContext
to interact with the in-memory database:
public class InMemoryBookRepository : IBookRepository { private readonly BooksAPIContext _context; public InMemoryBookRepository(BooksAPIContext context) { _context = context; } public Book Add(Book book) { var addedBook = _context.Add(book); _context.SaveChanges(); book.Id = addedBook.Entity.Id; return book; } public void Delete(Book book) { _context.Remove(book); _context.SaveChanges(); } public IEnumerable<Book> GetAll() { return _context.Books.ToList(); } public Book GetById(int id) { return _context.Books.SingleOrDefault(x => x.Id == id); } public void Update(Book book) { var bookToUpdate = GetById(book.Id); bookToUpdate.Author = book.Author; bookToUpdate.Title = book.Title; bookToUpdate.PublishedDate = book.PublishedDate; _context.Update(bookToUpdate); _context.SaveChanges(); } }
Don’t forget to register the repository as an injectable service within the ConfigureService
in the Startup
class:
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddDbContext<BooksAPIContext>(x => x.UseInMemoryDatabase()); services.AddTransient<IBookRepository, InMemoryBookRepository>(); // Add framework services. services.AddMvc(); }
Now that you have set up your data layer let’s dive into the Web API controller!
Create the Note Web API Controller
Before going any further, make sure to delete the boilerplate ValuesController
that the framework created automatically. Also, modify the launchSettings.json
file and make sure the launch URL of the profile you are using is pointing to a valid URL. In this example, we will point to our book controller:
{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:63595/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "book", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "BooksAPI": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000/book", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }
Create a Web API Controller Class
in the Controllers
folder and name it BookController
.
The auto-generated code will look like this:
[Route("api/[controller]")] public class BookController : Controller { // GET: api/values [HttpGet] public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } // GET api/values/5 [HttpGet("{id}")] public string Get(int id) { return "value"; } // POST api/values [HttpPost] public void Post([FromBody]string value) { } // PUT api/values/5 [HttpPut("{id}")] public void Put(int id, [FromBody]string value) { } // DELETE api/values/5 [HttpDelete("{id}")] public void Delete(int id) { } }
The framework automatically generated a lot of code for us. There is a method for each HTTP verb that our controller will handle. As a refresher, the REST API standard uses each HTTP verb for a different action over our resources:
You will also see the controller has a Route
attribute, with the value api/[controller]
. This defines the base route for all of this controller endpoints, which in this case is api/book
. We will change this to make the base route book
alone. It should look like this:
Route(“[controller]”)
This attribute can also be applied at method-level if you need to define custom routes for a specific endpoint.
The Get
method (as well as the Put
and Delete
methods) have in their HTTP verb Attribute an “id” element:
HttpGet("{id}")
This is a placeholder for the “id” parameter in the URL of the endpoint. For example, for the Get method, the URL will be:
/book/{id}
The framework automagically maps the parameters defined in these attributes to the parameters of the method in the controller. Awesome, huh?
We will now write the code to handle each request to our API:
[Route("[controller]")] public class BookController : Controller { private readonly IBookRepository _bookRepository; public BookController(IBookRepository bookRepository) { _bookRepository = bookRepository; } // GET: book [HttpGet] public IEnumerable<Book> Get() { return _bookRepository.GetAll(); } // GET book/5 [HttpGet("{id}", Name = "GetBook")] public IActionResult Get(int id) { var book = _bookRepository.GetById(id); if (book == null) { return NotFound(); } return Ok(book); } // POST book [HttpPost] public IActionResult Post([FromBody]Book value) { if (value == null) { return BadRequest(); } var createdBook = _bookRepository.Add(value); return CreatedAtRoute("GetBook", new { id = createdBook.Id }, createdBook); } // PUT book/5 [HttpPut("{id}")] public IActionResult Put(int id, [FromBody]Book value) { if (value == null) { return BadRequest(); } var note = _bookRepository.GetById(id); if (note == null) { return NotFound(); } value.Id = id; _bookRepository.Update(value); return NoContent(); } // DELETE book/5 [HttpDelete("{id}")] public IActionResult Delete(int id) { var book = _bookRepository.GetById(id); if (book == null) { return NotFound(); } _bookRepository.Delete(book); return NoContent(); } }
Now we’re ready to test our API!
Add JWT authentication using Stormpath
So far this is a totally public API, so any user can get, create, edit, and delete any book they want. That’s not very secure! We will now add authentication to our API through JWT tokens.
If you want to refresh your knowledge, check out our overview of token authentication and JWTs!
As today, ASP.NET Core supports protecting routes with Bearer header JWTs. But, unlike the ASP.NET 4.x Web API framework, it doesn’t have support for issuing them. To do this, you will need to write custom middleware or use external packages. There are several options; you can read Nate’s article to learn more about this.
Lucky for us, Token Authentication with JWT becomes extremely easy using the Stormpath ASP.NET Core library – I’ll show you how.
Get your Stormpath API credentials
To communicate with Stormpath, your application needs a set of API Keys. Grab them from your Stormpath account (If you haven’t already registered for Stormpath, you can create a free developer account here).
Once you have them, you should store them in environment variables. Open up the command line and execute these commands:
setx STORMPATH_CLIENT_APIKEY_ID "<your_api_key_id>" setx STORMPATH_CLIENT_APIKEY_SECRET "<your_api_key_secret>"
Restart Visual Studio to pick up the environment variables from your OS.
Integrate Stormpath with the Web API
Right-click on your project and select “Manage NuGet packages”. Them, add the package Stormpath.AspNetCore
.
To use Stormpath API for Access Token authentication, add this configuration in the ConfigureServices
method in the Startup.cs
.
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddStormpath(new StormpathConfiguration() { Web = new WebConfiguration() { // This explicitly tells the Stormpath middleware to only serve JSON responses (appropriate for an API). // By default, HTML responses are served too. Produces = new[] {"application/json"}, Oauth2 = new WebOauth2RouteConfiguration() { Uri = "/token", } } }); ... }
As a personal preference, I changed the default token endpoint URI ("/oauth/token"
) to /token
.
Options that are not overridden by explicit configuration will retain their default values.
Make sure you add the Stormpath middleware before any middleware that requires protection, such as MVC.
You can [learn more about configuration options in the Stormpath Product Documentation]((https://docs.stormpath.com/dotnet/aspnetcore/latest/configuration.html#configuration).
Now, find the Configure method and add Stormpath to your middleware pipeline.
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseStormpath(); app.UseMvc(); }
Finally, to effectively protect the controller, add the Authorize
attribute:
[Authorize] [Route("[controller]")] public class BookController : Controller { ... }
That’s all! Exhausted yet?
Test Your Web API with Postman
Now, let’s test our Web API. I’m using Postman for this tutorial, but feel free to use any REST client you like. To register a new user, we need to make a POST request to the /register
endpoint, passing the required data on the body:
{ "givenName": "MyAPIUser", "surname": "Tester", "email": "test3@example.com", "password": "TestTest1" }
With our user, we are going to get a token by POSTing to the /token
endpoint.
Payload should be a URL-encoded form with your credentials:
grant_type: password username: test3@example.com password: TestTest1
The access token you received should be sent to the server on every request, on the Authorization
header. The value of the header should be Bearer <your_token>
:
Let’s add a new book:
The status code 201 indicates our application has created a new book successfully.
If you view your list of books now, you should see the one you just created has been added:
Congratulations! You just created a web API with ASP.NET Core.
Learn More
- Source Code
- OpenID Connect for User Auth in ASP.NET Core
- Our ASP.NET Core Token Authentication Guide
- Store & Protect Sensitive Data in ASP.NET Core
The post Build a REST API for your Mobile Apps with ASP.NET Core appeared first on Stormpath User Identity API.