ASP.NET Core updates in .NET 7 Preview 5

Daniel Roth

.NET 7 Preview 5 is now available and includes many great new improvements to ASP.NET Core.

Here’s a summary of what’s new in this preview release:

  • JWT authentication improvements & automatic authentication configuration
  • Minimal API’s parameter binding support for argument list simplification

For more details on the ASP.NET Core work planned for .NET 7 see the full ASP.NET Core roadmap for .NET 7 on GitHub.

Get started

To get started with ASP.NET Core in .NET 7 Preview 5, install the .NET 7 SDK.

If you’re on Windows using Visual Studio, we recommend installing the latest Visual Studio 2022 preview. If you’re on macOS, we recommend installing the latest Visual Studio 2022 for Mac preview.

To install the latest .NET WebAssembly build tools, run the following command from an elevated command prompt:

dotnet workload install wasm-tools

Note: Building .NET 6 Blazor projects with the .NET 7 SDK and the .NET 7 WebAssembly build tools is currently not supported. This will be addressed in a future .NET 7 update: dotnet/runtime#65211.

Upgrade an existing project

To upgrade an existing ASP.NET Core app from .NET 7 Preview 4 to .NET 7 Preview 5:

  • Update all Microsoft.AspNetCore.* package references to 7.0.0-preview.5.*.
  • Update all Microsoft.Extensions.* package references to 7.0.0-preview.5.*.

See also the full list of breaking changes in ASP.NET Core for .NET 7.

JWT authentication improvements & automatic authentication configuration

Configuring authentication (AuthN) and authorization (AuthZ) for an ASP.NET Core app today requires numerous changes, including adding and configuring services, and adding middleware at different stages of the app startup process. We’ve received feedback that users find configuring authentication and authorization one of the hardest things about building APIs with ASP.NET Core. Given how critically important correctly configuring authentication and authorization are to securing web apps, we’ve made some improvements aimed at simplifying the most common aspects of this area for ASP.NET Core, with an initial focus on JWT bearer authentication, which is commonly used to protect web APIs.

Simplified authentication configuration

Authentication options can now be automatically configured directly from the app’s configuration system, due to the addition of a default configuration section when configuring authentication via the new Authentication property on WebApplicationBuilder like so:

var builder = WebApplication.CreateBuilder(args);

builder.Authentication.AddJwtBearer(); // New top-level property for setting up authentication

var app = builder.Build();

This new property provides a central place in your app’s code to setup authentication, providing easy access to the AuthenticationBuilder instance from which authentication schemes can be added and configured. Setting up authentication via this new property will also take care of automatically adding the required middleware to the request pipeline, similar to how WebApplicationBuilder already does this for routing.

Here’s an example of an app setup to use JWT bearer authentication with two endpoints, one that requires authorization and one that doesn’t (requires the Microsoft.AspNetCore.Authentication.JwtBearer NuGet package):

using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

builder.Authentication.AddJwtBearer();

var app = builder.Build();

app.MapGet("/", () => "Hello, World!");
app.MapGet("/secret", (ClaimsPrincipal user) => $"Hello {user.Identity?.Name}. This is a secret!")
    .RequireAuthorization();

app.Run();

Furthermore, individual authentication schemes can have their options be automatically set from the app’s configuration, making it easier to configure them between different environments (e.g. local development vs. production). For this release, only the JWT bearer scheme has been updated to support this mechanism but we’ll update more authentication schemes to support this in the future.

Here’s an example of an app’s appsettings.Development.json file, updated to set authentication options via the new "Authentication" section:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Authentication": {
    "DefaultScheme" : "JwtBearer",
    "Schemes": {
      "JwtBearer": {
        "Audiences": [ "http://localhost:5000", "https://localhost:5001" ],
        "ClaimsIssuer": "dotnet-user-jwts"
      }
    }
  }
}

Note that authentication schemes must still be added by code for them to have their options set via the new configuration section.

Endpoint-specific authorization policies

The previous example included an endpoint definition that only allowed authenticated users to access it (RequireAuthorization()). But what if the endpoint’s authorization requirements are slightly more complex, e.g. only allowing users with a specific “scope” claim? A set of authorization requirements is defined in a “policy” which is normally defined globally as part of setting up authorization in the app’s services and then referred to by its name when configuring the endpoint. This is good for reuse but can be difficult to discover and overly complex for some scenarios.

For cases where an authorization policy doesn’t need to be shared between endpoints, you can now easily define an authorization policy directly on an endpoint via metadata like so:

app.MapGet("/special-secret", () => "This is a special secret!")
    .RequireAuthorization(p => p.RequireClaim("scope", "myapi:secrets"));

Now that we have endpoints protected by JWT authentication, it would be nice to easily verify that they’re configured correctly in a local development environment, without the need for a full identity and user management service. For that, we’ll need something to issue JWTs to use with our app locally, which is the job of the new dotnet user-jwts command line tool.

Managing development-time JWTs with dotnet user-jwts

If we try to access the protected endpoints from our previous examples using a tool like Postman, curl, or dotnet httprepl, we’ll receive an error in the form of a response with an HTTP 401 (unauthorized) status code, indicating that the request didn’t provide any authentication details and thus is unauthorized to access that resource:

MyWebApi$ curl -i http://localhost:5000
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Tue, 07 Jun 2022 23:39:10 GMT
Server: Kestrel
Transfer-Encoding: chunked

Hello, World!
MyWebApi$ curl -i http://localhost:5000/secret
HTTP/1.1 401 Unauthorized
Content-Length: 0
Date: Tue, 07 Jun 2022 23:38:07 GMT
Server: Kestrel
WWW-Authenticate: Bearer
MyWebApi$

As the app is configured to use JWT bearer authentication, we need to provide a JWT with the request. In fully deployed systems, the JWT would typically be provided by a server acting as a Security token service (STS), perhaps in response to logging in via a set of credentials. But for the purpose of working with our API during local development, we can use the new dotnet user-jwts command line tool to create and manage app-specific local JWTs.

The user-jwts tool is similar in concept to the existing user-secrets tools, in that it can be used to manage values for the app that are only valid for the current user (the developer) on the current machine. In fact, the user-jwts tool utilizes the user-secrets infrastructure to manage the key that the JWTs will be signed with, ensuring it’s stored safely in the user profile.

First, we have to initialize the user secrets system for our project by calling dotnet user-secrets init and dotnet user-secrets list (note this will be done for you automatically by dotnet user-jwts in a future preview release):

MyWebApi$ dotnet user-jwts create
Project does not contain a user secrets ID.
MyWebApi$ dotnet user-secrets init
Set UserSecretsId to 'b78e7e01-e648-421a-9ccd-e1a31dd58529' for MSBuild project 'MyWebApi.csproj'. 
MyWebApi$ dotnet user-secrets list
No secrets configured for this application.

Now we can use the user-jwts tool to create a JWT to use with our example app:

MyWebApi$ dotnet user-jwts create
New JWT saved with ID '643a8abc'.
MyWebApi$ dotnet user-jwts print 643a8abc --show-full
Found JWT with ID 'b0498b94'
{
  "Id": "b0498b94",
  "Scheme": "Bearer",
  "Name": "damia",
  "Audience": "https://localhost:7188",
  "NotBefore": "2022-06-08T00:03:31+00:00",
  "Expires": "2022-09-08T00:03:31+00:00",
  "Issued": "2022-06-08T00:03:31+00:00",
  "Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImRhbWlhIiwic3ViIjoiZGFtaWEiLCJqdGkiOiJiMDQ5OGI5NCIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0NjYxMSwiZXhwIjoxNjYyNTk1NDExLCJpYXQiOjE2NTQ2NDY2MTEsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.4lS34bXQdmubMf7JIpa6kSraVPpIe9nA-2Ptni2GdMM",
  "Scopes": [],
  "Roles": [],
  "CustomClaims": {}
}
Token Header: {"alg":"HS256","typ":"JWT"}
Token Payload: {"unique_name":"damia","sub":"damia","jti":"b0498b94","aud":["https://localhost:7188","http://localhost:5056"],"nbf":1654646611,"exp":1662595411,"iat":1654646611,"iss":"dotnet-user-jwts"}
Compact Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImRhbWlhIiwic3ViIjoiZGFtaWEiLCJqdGkiOiJiMDQ5OGI5NCIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0NjYxMSwiZXhwIjoxNjYyNTk1NDExLCJpYXQiOjE2NTQ2NDY2MTEsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.4lS34bXQdmubMf7JIpa6kSraVPpIe9nA-2Ptni2GdMM
MyWebApi$ 

The create command took care of updating our project’s appsettings.Development.json file and user secrets store with the required configuration values for the JWT bearer authentication scheme to recognize the user JWTs. The print command printed out the JWT value we can include in our requests’ Authorization header to test out our protected APIs (note the --show-full option is required right now to retrieve the JWT value but in a future release this will be shown by default when using user-jwt create and user-jwt print).

Now let’s try accessing our protected API again, this time with the JWT value created by the tool:

MyWebApi$ curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImRhbWlhIiwic3ViIjoiZGFtaWEiLCJqdGkiOiJiMDQ5OGI5NCIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0NjYxMSwiZXhwIjoxNjYyNTk1NDExLCJpYXQiOjE2NTQ2NDY2MTEsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.4lS34bXQdmubMf7JIpa6kSraVPpIe9nA-2Ptni2GdMM" http://localhost:5000/secret
Hello damian. This is a secret!
MyWebApi$ 

You can create JWTs with different claims to explore and verify your app’s authorization configuration. Let’s create and use a JWT with a custom user name:

MyWebApi$ dotnet user-jwts create --name MyTestUser
New JWT saved with ID '5d285409'.
MyWebApi$ dotnet user-jwts print 5d285409 --show-full
Found JWT with ID '5d285409'
{
  "Id": "5d285409",
  "Scheme": "Bearer",
  "Name": "MyTestUser",
  "Audience": "https://localhost:7188",
  "NotBefore": "2022-06-08T00:53:57+00:00",
  "Expires": "2022-09-08T00:53:57+00:00",
  "Issued": "2022-06-08T00:53:57+00:00",
  "Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ik15VGVzdFVzZXIiLCJzdWIiOiJNeVRlc3RVc2VyIiwianRpIjoiNWQyODU0MDkiLCJhdWQiOlsiaHR0cHM6Ly9sb2NhbGhvc3Q6NzE4OCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTA1NiJdLCJuYmYiOjE2NTQ2NDk2MzcsImV4cCI6MTY2MjU5ODQzNywiaWF0IjoxNjU0NjQ5NjM3LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.Lggk5aPZm0gRmMi180HNOt1_XDs6Fa4QsAHmaHaHPhc",
  "Scopes": [],
  "Roles": [],
  "CustomClaims": {}
}
Token Header: {"alg":"HS256","typ":"JWT"}
Token Payload: {"unique_name":"MyTestUser","sub":"MyTestUser","jti":"5d285409","aud":["https://localhost:7188","http://localhost:5056"],"nbf":1654649637,"exp":1662598437,"iat":1654649637,"iss":"dotnet-user-jwts"}
Compact Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ik15VGVzdFVzZXIiLCJzdWIiOiJNeVRlc3RVc2VyIiwianRpIjoiNWQyODU0MDkiLCJhdWQiOlsiaHR0cHM6Ly9sb2NhbGhvc3Q6NzE4OCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTA1NiJdLCJuYmYiOjE2NTQ2NDk2MzcsImV4cCI6MTY2MjU5ODQzNywiaWF0IjoxNjU0NjQ5NjM3LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.Lggk5aPZm0gRmMi180HNOt1_XDs6Fa4QsAHmaHaHPhc
MyWebApi$ curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ik15VGVzdFVzZXIiLCJzdWIiOiJNeVRlc3RVc2VyIiwianRpIjoiNWQyODU0MDkiLCJhdWQiOlsiaHR0cHM6Ly9sb2NhbGhvc3Q6NzE4OCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTA1NiJdLCJuYmYiOjE2NTQ2NDk2MzcsImV4cCI6MTY2MjU5ODQzNywiaWF0IjoxNjU0NjQ5NjM3LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.Lggk5aPZm0gRmMi180HNOt1_XDs6Fa4QsAHmaHaHPhc" http://localhost:5000/secret
Hello MyTestUser. This is a secret!
MyWebApi$ 

Finally, let’s create a JWT with a custom claim allowing access to the most secret API in our example app:

MyWebApi$ dotnet user-jwts create --name AnotherUser --scope "myapi:secrets"
JWT for user 'AnotherUser' created with id 'e9b480cb'.
MyWebApi$ dotnet user-jwts print e9b480cb
Found JWT with ID 'e9b480cb'
{
  "Id": "e9b480cb",
  "Scheme": "Bearer",
  "Name": "AnotherUser",
  "Audience": "https://localhost:7188",
  "NotBefore": "2022-06-08T00:57:43+00:00",
  "Expires": "2022-09-08T00:57:43+00:00",
  "Issued": "2022-06-08T00:57:43+00:00",
  "Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkFub3RoZXJVc2VyIiwic3ViIjoiQW5vdGhlclVzZXIiLCJqdGkiOiJlOWI0ODBjYiIsInNjb3BlIjoibXlhcGk6c2VjcmV0cyIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0OTg2MywiZXhwIjoxNjYyNTk4NjYzLCJpYXQiOjE2NTQ2NDk4NjMsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.U88zp4my_Po0WMsZ1irVFraKTJWHIsOy8MiQ2TkmteE",
  "Scopes": [
    "myapi:secrets"
  ],
  "Roles": [],
  "CustomClaims": {}
}
Token Header: {"alg":"HS256","typ":"JWT"}
Token Payload: {"unique_name":"AnotherUser","sub":"AnotherUser","jti":"e9b480cb","scope":"myapi:secrets","aud":["https://localhost:7188","http://localhost:5056"],"nbf":1654649863,"exp":1662598663,"iat":1654649863,"iss":"dotnet-user-jwts"}
Compact Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkFub3RoZXJVc2VyIiwic3ViIjoiQW5vdGhlclVzZXIiLCJqdGkiOiJlOWI0ODBjYiIsInNjb3BlIjoibXlhcGk6c2VjcmV0cyIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0OTg2MywiZXhwIjoxNjYyNTk4NjYzLCJpYXQiOjE2NTQ2NDk4NjMsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.U88zp4my_Po0WMsZ1irVFraKTJWHIsOy8MiQ2TkmteE
MyWebApi$ curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkFub3RoZXJVc2VyIiwic3ViIjoiQW5vdGhlclVzZXIiLCJqdGkiOiJlOWI0ODBjYiIsInNjb3BlIjoibXlhcGk6c2VjcmV0cyIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0OTg2MywiZXhwIjoxNjYyNTk4NjYzLCJpYXQiOjE2NTQ2NDk4NjMsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.U88zp4my_Po0WMsZ1irVFraKTJWHIsOy8MiQ2TkmteE" http://localhost:5000/special-secret
This is a special secret!
MyWebApi$ 

Using the new user-jwts tool we’ve been able to verify the APIs in our example app are configured for authorization in the way we intended. The app could of course now be configured to support an actual JWT provider according to that provider’s requirements.

You can explore the commands and options available on user-jwts with the --help option, e.g.:

  • dotnet user-jwts --help
  • dotnet user-jwts create --help

Minimal API parameter binding for argument lists

This preview extends parameter binding for minimal APIs to support refactoring a minimal API that takes a set of parameters into one that takes a single object with top level properties that represent what were once arguments.

For example, the following API lists all the products in a given category:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Requires the Microsoft.EntityFrameworkCore.InMemory package
builder.Services.AddDbContext<MyDb>(options => options.UseInMemoryDatabase("products"));
var app = builder.Build();

app.MapGet("/categories/{categoryId}/products", (int categoryId, int pageSize, int page, ILogger<Program> logger, MyDb db) =>
{
    logger.LogInformation("Getting products for page {Page}", page);
    return db.Products.Where(p => p.CategoryId == categoryId).Skip((page - 1) * pageSize).Take(pageSize);
});

app.Run();

record Product (int Id, string Name, int CategoryId);

class MyDb : DbContext
{
    public MyDb(DbContextOptions options) : base(options) { }
    public DbSet<Product> Products { get; set; }
}

You can now refactor your API parameters to a type and add the new attribute AsParameters to your parameter, like this:

app.MapGet("/categories/{categoryId}/products", ([AsParameters] ProductRequest req) =>
{
    req.Logger.LogInformation("Getting products for page {Page}", req.Page);
    return req.Db.Products.Where(p => p.CategoryId == req.CategoryId).Skip((req.Page - 1) * req.PageSize).Take(req.PageSize);
});

record struct ProductRequest(
    int CategoryId, 
    int PageSize, 
    int Page, 
    ILogger<ProductRequest> Logger, 
    MyDb Db);

The parameter binding rules will be applied to the new type’s top level properties or parameterized constructor parameters. Also, the same binding attributes (FromRoute, FromQuery, FromServices, etc.) are supported and can be applied to them.

Let’s update the previous example to bind both Page and PageSize from the request headers instead of query string:

// You will need to include 'using Microsoft.AspNetCore.Mvc;'
record struct ProductRequest(
    int CategoryId,
    [FromHeader(Name = "PageSize")] int PageSize,
    [FromHeader(Name = "Page")] int Page,
    ILogger<ProductRequest> Logger, 
    MyDb Db);

Both classes and structs are supported (the usage of structs is recommended to avoid additional memory allocation). However, abstract types and interfaces are not supported. In our previous example, the same type (currently a record struct) could be define as a class:

class ProductRequest
{
    public int CategoryId { get; set; }
    [FromHeader(Name = "PageSize")]
    public int PageSize { get; set; }
    [FromHeader(Name = "Page")]
    public int Page { get; set; }
    public ILogger<ProductRequest> Logger { get; set; }
    public MyDb Db { get; set; }
}

The following rules are applied during the parameter binding:

Classes

  • A public parameterless constructor will be used if present
  • A public parameterized constructor will be used if a single constructor is present and all arguments have a matching (case-insensitive) public property.
    • If a constructor parameter does not match with a property, InvalidOperationException will be thrown if binding is attempted.
  • Throw InvalidOperationException when more than one parameter is declared and the parameterless constructor is not present.
  • Throw InvalidOperationException if a suitable constructor cannot be found.

Structs

  • A declared public parameterless constructor will always be used if present
  • A public parameterized constructor will be use if a single constructor is present and all arguments have a matching (case-insensitive) public property.
    • If a constructor parameter does not match with a property, InvalidOperationException will be thrown if binding is attempted.
  • Since struct always has a default constructor, the default constructor will be used if it is the only one present or more than one parameterized constructor is present.

Note: When binding using a parameterless constructor all public settable properties will be bound.

Give feedback

We hope you enjoy this preview release of ASP.NET Core in .NET 7. Let us know what you think about these new improvements by filing issues on GitHub.

Thanks for trying out ASP.NET Core!

16 comments

Discussion is closed. Login to edit/delete existing comments.

  • Felipe Costa Gualberto 0

    Hi, thanks for the hard work. I don’t if I’m the only one who thinks like this, but the effort and hype about Minimal APIs is something I don’t understand.
    Even in small-medium projects, writing minimal APIs seems to create bad code and unorganized files. It is definitely a feature that I am not willing to use. Writing controllers is so much better and clear.
    I wonder which kind of feedback Microsoft got to dedicate such precious time into Minimal APIs.

    • Michael Taylor 0

      While I agree that minimal APIs can be misused (like everything else) they do have some benefits. One case I can think of is in forwarding situations. For example suppose that your API requires authentication but doesn’t actually authenticate itself but defers to another system. It would make sense for your API to expose an endpoint for authentication but it would just forward that request onto the actual authentication service. Creating the controller boilerplate just to have a single action (or 2) that does nothing but call another service is pretty wasteful of your time. A minimal API call would be good here. Using controllers for more complex situations would then make sense.

      Another use case is for demo code. A lot of samples will have the startup code, controller and models in separate files which makes it harder to talk about them. This is especially true when the focus isn’t actually the MVC aspect but perhaps something that the code is doing such as using an ORM or something. Getting the boilerplate code out of the way is reasonable here.

      But honestly I think the minimal API approach is really just a stepping stone to where we actually want to be at which is what I refer to as endpoint routing. Endpoint routing is basically the API equivalent of Razor Pages. Controllers are god classes no matter how you break things up. The only way to avoid this is to create controllers with a single action but this can be difficult to manage and requires more code than desired. Endpoint routing (again, my term) means each endpoint is a separate class with all the logic needed to handle that one request. No more god classes. Just simple classes that do 1 thing. Of course having a bunch of little classes compared to one large class is a preference but minimal APIs are starting to set us up for that world I believe. Right now you’d have to create separate classes for each endpoint and then define a single action. With the minimal API you can do away with the class and just write the code. DI is already built in. Of course this would bloat your startup code a lot and that is where extension methods could help out. Imagine you have an extension method called AddProductEndpoints. This extension method uses the minimal API to register the endpoints and functionality needed to support products in your API. Right now controllers are handled automatically but you have to follow the conventions to get them to work. With this newer approach conventions become less important and therefore it is easier to do, I believe.

    • Bram Musters 0

      I toally agree with Felipe. There seems to be too much on MInimal API’s.

      The AuthN/Z changes look promising though!

    • Charles Roddie 0

      Minimal APIs should not be in dotnet. Dotnet is statically typed, and so if you put in right types you don’t get type errors, and if you put in the wrong types you get type errors. Minimal APIs break this and I think anyone who allows these in a codebase hasn’t understood the benefits of type safety. The reason people like them is that they are short and you can do a lot in a few characters, and they give a impression of being simple when they are in fact extremely complex things with unclear hidden logic.

      • Michael Taylor 0

        The minimal API is simply methods on existing types. All the strongly typed rules of the language are still followed because none of this has anything to do with the C# language. Please clarify where you believe that minimal APIs allow you to skip strongly typed rules and why that isn’t possible using the more verbose controller approach.

      • David FowlerMicrosoft employee 0

        Minimal APIs are strongly typed. In fact that’s why they are so nice! It’s the ability to write an MVC like action as a function outside of a class. It’s not clear to me what you mean by “and if you put in the wrong types you get type errors”. If you could show me a side by side with what you consider strongly typed in something like MVC (which I assume you think is more strongly typed?), that would be great!

    • Peyman Ranjbar 0

      Minimal API’s target audiences are mostly newcomers, not someone with years of experience. that’s the reason for this much investment.
      Although I found them quite handy when creating sample projects for libraries, playing with certain functionality, and so on (besides the tiny projects).

  • Nathan Berkowitz 0

    Thanks for the new AddJwtBearer(). Amazing!

    Next should be to add a blazor wasm project template with an authentication and authorization UI using JWT and blazor components (not razor pages), so the login will have the same look and feel as the entire web app.

    Thanks for your work.

  • Hassan Salihu 0

    Hi Daniel Roth, thanks for the hard work. Just a suggestion, I think it will be a good idea for the GA to allow devs choose API project from visual studio then select Auth type JWT or other auth type. Then devs can edit the boilerplate as they wish.

  • hamid 0

    Hi Daniel and thank you for efforts and improvements.
    After installing dotnet SDK 7.0.100-preview.5.22307.18 and upgrading my app packages , the application doesn’t start in Edge and Chrome any more.
    and Following error appears:
    failed to launch debug adapter. … unable to launch browser…
    The application still runs in Firefox. To enable my app running on Edge/Chrome I need to downgrade to dotnet 6.0.
    Thanks.

  • Jan SeriÅ¡ 0

    Hi I read about API with authn and auhtz testing from postman and curl. What about full testing with JWT tokens from Swagger UI?

    BTW it would be nice if you used real examples instead of terms like secret, scope, claims. I can imagine nothing from the part in the article about the advanced JWT authorization you showed.

    The term secret is very overloaded in the terminology of. NET auth.

    Also it is very confusing that. NET Auth uses the name RequireAuthorization() or [Authorized] for what is in fact plain Authentication with empty set of authorization rules only.

    Also what is the point of gRPC protobuf when if you want to do embedded. NET Auth, you do JWT and that is a real opposite of the super effective serialization when you then do some json in fact in every request

    Does the new [AsParameters] use reflection on every request?

    • Bruno OliveiraMicrosoft employee 0

      No, there is no reflection per request on [AsParameters]. What it does is a codegen that will create a new instance and set the properties (without reflection), similar to what you would do in your application: The implementation is very optimized and when using struct it is almost no overhead.

  • Daniel Bradley 0

    Hi, firstly thanks for this great post. I followed the steps above but I always get the error returned from the WebAPI:

    www-authenticate: Bearer error=”invalid_token”, error_description=”The signature key was not found”

    What did I miss? Or should I raise an issue?

    • Rafiki AssumaniMicrosoft employee 0

      Hi Daniel, thank you for trying out the JWT tool. The authentication challenge error that you are seeing is often a result of misconfigurations. It appears that key used for signing the JWT is not being correctly loaded during the token validation process. Can you create an issue on ASPNET Core repo with a GitHub reproduction sample app to help you diagnose the problem?

  • Parveen Kaur 0

    Thanks sir! Ref: “Simplified authentication configuration”

    1. so basically JWTBearerOptions are being moved to the config files?
    2. default AuthN scheme can be put in the config files too?

    Providing a similar thing for builder.Authentication.AddScheme() for custom overrides of AuthenticationHandler::HandleAuthenticateAsync would be helpful too.

    The JSON in your code may slightly be modified – to “DefaultScheme”: “Bearer” instead of “DefaultScheme”: “JwtBearer”.

    I am a big fan of minimal api, there’s lot that can be easily done through them !

Feedback usabilla icon