In a previous blog post, we looked at how to migrate the authentication code of a front-end solution written with ASP.NET Core to using the latest Microsoft.Identity.Web library for .NET. That was part 1 of our short series. For part 2, we will look at how to modernize the auth code in our back-end API using Microsoft.Identity.Web again.
Updating the Web API
I was quite excited upgrading the API to .NET 5 because not only I could get rid of unnecessary code but I was also able to harden the security of the API at the same time. The majority of the redundant code was the identity-related extension classes
-
AzureAdAuthenticationBuilderExtension.cs
-
AzureAdOptions
The code in these classes is part of the Microsoft.Identity.Web now :)
Once these files are removed, we can update the *.csproj file and change it from this:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
</ItemGroup>
</Project>
to this
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Identity.Web" Version="1.3.0"/>
</ItemGroup>
</Project>
The only dependency on the updated project is Microsoft.Identity.Web. We can now proceed with the code updates. Open Startup.cs
and update the ConfigureServices()
method from this
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));
services.AddMvc();
}
to this
public void ConfigureServices(IServiceCollection services)
{
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
services.AddControllers();
}
Finally, we need to update the ToDoListController.cs
to take advantage of the newer, simplified way to authorize and validate incoming tokens. The old code
[Authorize]
[Route("api/[controller]")]
public class TodoListController : Controller
{
static ConcurrentBag<TodoItem> todoStore = new ConcurrentBag<TodoItem>();
// GET: api/values
[HttpGet]
public IEnumerable<TodoItem> Get()
{
string owner = (User.FindFirst(ClaimTypes.NameIdentifier))?.Value;
return todoStore.Where(t => t.Owner == owner).ToList();
}
// POST api/values
[HttpPost]
public void Post([FromBody]TodoItem Todo)
{
string owner = (User.FindFirst(ClaimTypes.NameIdentifier))?.Value;
todoStore.Add(new TodoItem { Owner = owner, Title = Todo.Title });
}
}
Can be updated to this:
[Authorize]
[Route("[controller]")]
public class TodoListController : Controller
{
static ConcurrentBag<TodoItem> todoStore = new ConcurrentBag<TodoItem>();
static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };
// GET: api/values
[HttpGet]
public IEnumerable<TodoItem> Get()
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
string owner = (User.FindFirst(ClaimTypes.NameIdentifier))?.Value;
return todoStore.Where(t => t.Owner == owner).ToList();
}
// POST api/values
[HttpPost]
public void Post([FromBody]TodoItem Todo)
{
string owner = (User.FindFirst(ClaimTypes.NameIdentifier))?.Value;
todoStore.Add(new TodoItem { Owner = owner, Title = Todo.Title });
}
}
The important change here is the new addition of HttpContext.VerifyUserHasAnyAcceptedScope()
which ensures that the incoming access token has the right scopes.
Source Code
There is a fully working solution showing how the code works end to end on GitHub.
Before the migration
After the migration
Conclusion
Microsoft.Identity.Web has made developers' lives much easier when it comes to authenticating users and acquiring or managing tokens. Although not groundbreaking, the new code is much simpler and more secure by leveraging the Microsoft.Identity.Web library.
Top comments (0)