> For the complete documentation index, see [llms.txt](/docs/llms.txt)

# .NET API | FusionAuth Docs

Quickstart integration of .NET Web API with FusionAuth.

# .NET API

In this quickstart, you are going to learn how to integrate a .NET resource server with FusionAuth. You will protect an API resource from unauthorized usage. You'll be building it for [ChangeBank](https://www.youtube.com/watch?v=CXDxNCzUspM), a global leader in converting dollars into coins.

The Docker Compose file and source code for a complete application are available at [https://github.com/FusionAuth/fusionauth-quickstart-dotnet-api](https://github.com/FusionAuth/fusionauth-quickstart-dotnet-api).

## Prerequisites[#](#prerequisites)

*   [.NET 7](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) : This is the main .NET framework.
*   [Docker](https://www.docker.com): The quickest way to stand up FusionAuth. (There are [other ways](/docs/get-started/download-and-install/fusionauth-app).

This app has been tested with .NET 7. This example should work with other compatible versions of .NET.

Optionally, you can also install [Visual Studio](https://visualstudio.microsoft.com/) to make it easier to work with .NET through an IDE.

## General Architecture[#](#general-architecture)

A client wants access to an API resource at `/resource`. However, it is denied this resource until it acquires an access token from FusionAuth.

```mermaid
    sequenceDiagram
        participant Client
        participant App as Resource Server
        participant FusionAuth

        Client ->> App : GET /resource
        App ->> Client : 401 Not Authorized
        Client ->> FusionAuth : POST /api/login
        FusionAuth ->> Client : 200 Ok<br/>(token)
        Client ->> App : GET /resource
        App ->> Client : 200 Ok<br/>(resource) 
```

Resource Server Authentication with FusionAuth

While the access token is acquired via the Login API above, this is for simplicity of illustration. The token can be, and typically is, acquired through one of the [OAuth grants](/docs/lifecycle/authenticate-users/oauth/).

## Getting Started[#](#getting-started)

In this section, you'll get FusionAuth up and running and create a resource server which will serve the API.

### Clone The Code[#](#clone-the-code)

First off, grab the code from the repository and change into that directory.

```
git clone https://github.com/FusionAuth/fusionauth-quickstart-dotnet-api.git
cd fusionauth-quickstart-dotnet-api
```

### Run FusionAuth Via Docker[#](#run-fusionauth-via-docker)

You'll find a Docker Compose file (`docker-compose.yml`) and an environment variables configuration file (`.env`) in the root directory of the repo.

Assuming you have Docker installed, you can stand up FusionAuth on your machine with the following.

```
docker compose up -d
```

Here you are using a bootstrapping feature of FusionAuth called [Kickstart](/docs/get-started/download-and-install/development/kickstart). When FusionAuth comes up for the first time, it will look at the `kickstart/kickstart.json` file and configure FusionAuth to your specified state.

If you ever want to reset the FusionAuth application, you need to delete the volumes created by Docker Compose by executing `docker compose down -v`, then re-run `docker compose up -d`.

FusionAuth will be initially configured with these settings:

*   Your client Id is `e9fdb985-9173-4e01-9d73-ac2d60d1dc8e`.
*   Your client secret is `super-secret-secret-that-should-be-regenerated-for-production`.
*   Your example teller username is `teller@example.com` and the password is `password`. They will have the role of `teller`.
*   Your example customer username is `customer@example.com` and the password is `password`. They will have the role of `customer`.
*   Your admin username is `admin@example.com` and the password is `password`.
*   The base URL of FusionAuth is `http://localhost:9011/`.

You can log in to the [FusionAuth admin UI](http://localhost:9011/admin) and look around if you want to, but with Docker and Kickstart, everything will already be configured correctly.

If you want to see where the FusionAuth values came from, they can be found in the [FusionAuth app](http://localhost:9011/admin). The tenant Id is found on the Tenants page. To see the Client Id and Client Secret, go to the Applications page and click the `View` icon under the actions for the ChangeBank application. You'll find the Client Id and Client Secret values in the `OAuth configuration` section.

The `.env` file contains passwords. In a real application, always add this file to your `.gitignore` file and never commit secrets to version control.

## Create Your ASP.NET Web API Resource Server[#](#create-your-aspnet-web-api-resource-server)

Now you are going to create an ASP.NET API application. While this section builds a simple API, you can use the same configuration to integrate an existing API with FusionAuth.

We are going to be building an API backend for a banking application called ChangeBank. This API will have two endpoints:

*   `make-change`: This endpoint will allow you to call GET with a `total` amount and receive a response indicating how many nickels and pennies are needed to make change. Valid roles are `customer` and `teller`.
*   `panic`: Tellers may call this endpoint to call the police in case of an incident. The only valid role is `teller`.

Both endpoints will be protected such that a valid JSON web token (JWT) will be required in the `Authorization` header or in a cookie named `app.at` in order to be accessed. Additionally, the JWT must have a `roles` claim containing the appropriate role to use the endpoint.

If you simply want to run the application, there is a completed version in the `complete-application` directory. You can use the following commands to get it up and running if you do not want to create your own.

```
cd complete-application
dotnet run
```

You can then follow the instructions in the [Run The API](#run-the-api) section.

Now, create a base ASP.NET API project.

```
dotnet new webapi -o your-api && cd your-api
```

### Add Security[#](#add-security)

This app uses the standard [.NET JWTBearer package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JWTBearer), which simplifies extracting and validating JWT tokens from FusionAuth.

Install the .NET JWTBearer package and other supporting libraries.

```
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer -v 7.0.14
dotnet add package Microsoft.AspNetCore.OpenApi -v 7.0.10
dotnet add package System.IdentityModel.Tokens.Jwt -v 7.0.3
dotnet add package Microsoft.IdentityModel.Protocols.OpenIdConnect -v 7.0.3
```

Next, replace your `appsettings.json` file with settings specific to the FusionAuth Issuer and Client Id (Audience).

```
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Urls": "http://localhost:5001",
  "Authentication": {
    "Schemes": {
      "Bearer": {
        "Authority": "http://localhost:9011",
        "ValidAudiences": [
          "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e"
        ]
      }
    }
  }
}
```

Replace the code in the `appsettings.Development.json` file with the following.

```
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "Authentication": {
    "Schemes": {
      "Bearer": {
        "RequireHttpsMetadata": "false"
      }
    }
  }
}
```

The `appsettings.Development.json` file will be used when you run the application in development mode. It will enable some useful development options, mainly that HTTPS will not be required for the Issuer (FusionAuth) server. In production, you must use HTTPS for the Issuer server.

Update the `Program.cs` file to configure ASP.NET authentication to use JWT tokens. Replace the code in the `Program.cs` file with the following code.

```
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddAuthentication()
    .AddJwtBearer(options =>
    {
        options.Events = new()
        {
            OnMessageReceived = context =>
            {
                // Extract the token from a cookie if available.
                context.Token = context.Request.Cookies["app.at"];
                return Task.CompletedTask;
            }
        };
    });

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

//app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();
```

The `Program.cs` file configures the authentication services to use JWT bearer tokens to authenticate users through the `.AddJwtBearer()` method. The Issuer and Audience settings used by the JWT verification code are set in the `appsettings.json` file under the `Authentication` section. The `options.Events` section configures the JWT bearer authentication to use the `OnMessageReceived` event to read the JWT from the `app.at` cookie if it is present. By default, the JWT bearer authentication will only read the JWT from the `Authorization` header. With this code, the JWT can be passed in either the `Authorization` header or the `app.at` cookie.

The application is now configured to use FusionAuth JWTs for authentication. Next, you'll add some routes to the application.

### Write C# Code[#](#write-c-code)

Now you are going to write some C# code. You are going to write two API controllers and their corresponding model objects.

### Add Model Objects[#](#add-model-objects)

We will need response objects for the API to return. One is the `PanicResponse`, which returns a message when a successful `POST` call is made to `/panic`.

Create a `Models` folder.

```
mkdir Models
```

In the `Models` folder, create a new class `PanicResponse.cs`.

```
touch Models/PanicResponse.cs
```

Add the following code to it.

```
namespace Models
{
    public class PanicResponse
    {
        public PanicResponse(string message)
        {
            Message = message;
        }

        public string Message { get; set; }
    }
}
```

Next, you need an object to show the breakdown from making change. This object will hold a double `total` and Integers `nickels` and `pennies`. In the `Models` folder, create a new class `Change.cs`.

```
touch Models/Change.cs
```

Add the following code to it.

```
namespace Models
{
    public class Change
    {
        public double Total { get; set; }

        public int Nickels { get; set; }

        public int Pennies { get; set; }

        public Change()
        {
        }
    }
}
```

### Add Controllers[#](#add-controllers)

Now you need to add controllers to handle API calls. In the `Controllers` folder, add a new class `MakeChangeController.cs`.

```
touch Controllers/MakeChangeController.cs
```

Add the following code to it.

```
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Models;

namespace Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class MakeChangeController : ControllerBase
    {
        [HttpGet]
        [Authorize(Roles = "teller,customer")]
        public Change Get(double total)
        {
            var change = new Change();
            change.Total = total;

            change.Nickels = Convert.ToInt32(Math.Floor(total / 0.05));
            change.Pennies = Convert.ToInt32(Math.Round((total - 0.05 * change.Nickels) / 0.01));
            return change;
        }
    }
}
```

In this class, a `total` is an HTTP request query parameter that is converted to a `double`, and the `get` method divides the `total` into `nickels` and `pennies` and returns the response object.

Note the `[Authorize("teller,customer")]` attribute which adds the magic of ASP.NET role-based authorization to the route. The ASP.NET JWT Bearer package automatically reads out the roles claimed in the user token, and then adds them to the user object. For example, a decoded JWT for a teller user would look like this:

```
{
  "aud": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e",
  "exp": 1689289585,
  "iat": 1689285985,
  "iss": "http://localhost:9011",
  "sub": "00000000-0000-0000-0000-111111111111",
  "jti": "ebaa4184-2320-47dd-925b-2e18756c635f",
  "authenticationType": "PASSWORD",
  "email": "teller@example.com",
  "email_verified": true,
  "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e",
  "roles": [
    "teller"
  ],
  "auth_time": 1689285985,
  "tid": "d7d09513-a3f5-401c-9685-34ab6c552453"
}
```

ASP.NET reads the **roles** field, adds those roles to the user, and tests the user for the roles via the **Authorize** attribute.

Now, add a controller for tellers to call in an emergency. In the `Controllers` folder add a class `PanicController.cs`.

```
touch Controllers/PanicController.cs
```

Add the following code to it.

```
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Models;

namespace Controllers;

[ApiController]
[Route("[controller]")]
public class PanicController : ControllerBase
{
    [HttpPost]
    [Authorize(Roles = "teller")]
    public PanicResponse Post()
    {
        return new PanicResponse("We've called the police!");
    }
}
```

This class listens on `/panic` for a POST request and returns a response indicating that the police were called. The attributes are the same as the `MakeChangeController.cs` route, except that this is a `POST` request, so it is named `Post` and has the `[HttpPost]` attribute marking it as such.

## Run The API[#](#run-the-api)

Start the API resource server with the following command.

```
dotnet run
```

This command will build and run the project, which should be running on port 5001.

### Get A Token[#](#get-a-token)

There are [several ways to acquire a token](/docs/lifecycle/authenticate-users/oauth/) in FusionAuth but for this example, you will use the [Login API](/docs/apis/login) to keep things simple.

First, try the requests as the `teller@example.com` user. Based on the kickstart configuration this user has the `teller` role and should be able to call both `/make-change` and `/panic`.

Acquire an access token for `teller@example.com` by making the following request in a new terminal window.

```
curl --location 'http://localhost:9011/api/login' \
--header 'Authorization: this_really_should_be_a_long_random_alphanumeric_value_but_this_still_works' \
--header 'Content-Type: application/json' \
--data-raw '{
  "loginId": "teller@example.com",
  "password": "password",
  "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e"
}'
```

Copy the `token` from the response, which should look like the following.

```
{
   "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InVOYl9iQzFySHZZTnZMc285VzRkOEprZkxLWSJ9.eyJhdWQiOiJlOWZkYjk4NS05MTczLTRlMDEtOWQ3My1hYzJkNjBkMWRjOGUiLCJleHAiOjE2ODkzNTMwNTksImlhdCI6MTY4OTM1Mjk5OSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MDExIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMTExMTExMTExMTExIiwianRpIjoiY2MzNWNiYjUtYzQzYy00OTRjLThmZjMtOGE4YWI1NTI0M2FjIiwiYXV0aGVudGljYXRpb25UeXBlIjoiUEFTU1dPUkQiLCJlbWFpbCI6InRlbGxlckBleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhcHBsaWNhdGlvbklkIjoiZTlmZGI5ODUtOTE3My00ZTAxLTlkNzMtYWMyZDYwZDFkYzhlIiwicm9sZXMiOlsiY3VzdG9tZXIiLCJ0ZWxsZXIiXSwiYXV0aF90aW1lIjoxNjg5MzUyOTk5LCJ0aWQiOiJkN2QwOTUxMy1hM2Y1LTQwMWMtOTY4NS0zNGFiNmM1NTI0NTMifQ.WLzI9hSsCDn3ZoHKA9gaifkd6ASjT03JUmROGFZaezz9xfVbO3quJXEpUpI3poLozYxVcj2hrxKpNT9b7Sp16CUahev5tM0-4_FaYlmUEoMZBKo2JRSH8hg-qVDvnpeu8nL6FXxJII0IK4FNVwrQVFmAz99ZCf7m5xruQSziXPrfDYSU-3OZJ3SRuvD8bMopSiyRvZLx6YjWfBsvGSmMXeh_8vHG5fVkq5w1IkaDdugHnivtJIivHuCfl38kQBgw9rAqJLJoKRHHW0Ha7vHIcS6OCWWMDIIVspLyQNcLC16pL9Nss_5v9HMofow1OvQ9sUSMrbbkipjKq2peSjG7qA",
   "tokenExpirationInstant": 1689353059670,
   "user": {
      ...
   }
}
```

### Make The Request[#](#make-the-request)

You can provide the token to the resource API via a cookie named `app.at` or in the HTTP `Authorization` header.

Now make a request to `/make-change` with a query parameter `total=5.12` using either of the above methods.

This is a request using the `Authorization` header.

```
curl --location 'http://localhost:5001/makeChange?total=5.12' \
--header 'Authorization: Bearer <your_token>'
```

This is a request using a cookie.

```
curl --location 'http://localhost:5001/makeChange?total=5.12' \
--cookie 'app.at=<your_token>'
```

Your response should look like below.

```
{
  "total": 5.12,
  "nickels": 102,
  "pennies": 2
}
```

You were authorized, success! You can try making the request without the `Authorization` header or with a different string rather than a valid token, and see that you are denied access.

Next, call the `/panic` endpoint because you are in trouble!

Call the endpoint using the `Authorization` header as follows.

```
curl --location --request POST 'http://localhost:5001/panic' \
--header 'Authorization: Bearer <your_token>'
```

Call the endpoint using a cookie like this.

```
curl --location --request POST 'http://localhost:5001/panic' \
--cookie 'app.at=<your_token>'
```

This is a POST not a GET because you want all your emergency calls to be non-idempotent.

Your response should look like the following.

```
{
  "message": "We've called the police!"
}
```

Nice, help is on the way!

Now let's try as `customer@example.com`, who has the role `customer`. Acquire a token for `customer@example.com`.

```
curl --location 'http://localhost:9011/api/login' \
--header 'Authorization: this_really_should_be_a_long_random_alphanumeric_value_but_this_still_works' \
--header 'Content-Type: application/json' \
--data-raw '{
  "loginId": "customer@example.com",
  "password": "password",
  "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e"
}'
```

Your response should look like the following.

```
{
  "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InVOYl9iQzFySHZZTnZMc285VzRkOEprZkxLWSJ9.eyJhdWQiOiJlOWZkYjk4NS05MTczLTRlMDEtOWQ3My1hYzJkNjBkMWRjOGUiLCJleHAiOjE2ODkzNTQxMjMsImlhdCI6MTY4OTM1MzUyMywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MDExIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMjIyMjIyMjIyMjIyIiwianRpIjoiYjc2YWMwMGMtMDdmNi00NzkzLTgzMjgtODM4M2M3MGU4MWUzIiwiYXV0aGVudGljYXRpb25UeXBlIjoiUEFTU1dPUkQiLCJlbWFpbCI6ImN1c3RvbWVyQGV4YW1wbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImFwcGxpY2F0aW9uSWQiOiJlOWZkYjk4NS05MTczLTRlMDEtOWQ3My1hYzJkNjBkMWRjOGUiLCJyb2xlcyI6WyJjdXN0b21lciJdLCJhdXRoX3RpbWUiOjE2ODkzNTM1MjMsInRpZCI6ImQ3ZDA5NTEzLWEzZjUtNDAxYy05Njg1LTM0YWI2YzU1MjQ1MyJ9.T1bELQ6a_ItOS0_YYpvqhIVknVMNeamcoC7BWnPjg2lgA9XpCmFA2mVnycoeuz-mSOHbp2cCoauP5opxehBR2lCn4Sz0If6PqgJgXKEpxh5pAxCPt91UyfjH8hGDqE3rDh7E4Hqn7mb-dFFwdfX7CMdKvC3dppMbXAGCZTl0LizApw5KIG9Wmt670339pSf5lzD38P9WAG5Wr7fAmVrIJPVu6yv2FoR-pMYD2lnAF63HWKknrWB-khmhr9ZKRLXKhP1UK-ThY1FSnmpp8eNblsBqCxf6WaYxYkdp5_F2e56M4sQwHzrg4P9tZGVCmMri4dShF3Ck7OGa7hel-iIPew",
  "tokenExpirationInstant": 1689354123118,
  "user": {
    ...
  }
}
```

Now use the token to call `/make-change` with a query parameter `total=3.24`.

Use the `Authorization` header as follows.

```
curl --location 'http://localhost:5001/makeChange?total=3.24' \
--header 'Authorization: Bearer <your_token>'
```

Or use a cookie like this.

```
curl --location 'http://localhost:5001/makeChange?total=3.24' \
--cookie 'app.at=<your_token>'
```

Your response should look like the following.

```
{
  "total": 3.24,
  "nickels": 64,
  "pennies": 4
}
```

So far so good. Now let's try to call the `/panic` endpoint. We'll add the `-i` flag to see the headers of the response.

Make the call using the `Authorization` header like this.

```
curl -i --request POST 'http://localhost:5001/panic' \
--header 'Authorization: Bearer <your_token>'
```

Or using a cookie like this.

```
curl -i --request POST 'http://localhost:5001/panic' \
--cookie 'app.at=<your_token>'
```

Your response should look like the following.

```
HTTP/1.1 403 Forbidden
Content-Length: 0
Date: Wed, 11 Oct 2023 08:29:09 GMT
Server: Kestrel
```

Uh oh, I guess you are not allowed to do that. Notice that it returns a `403 Forbidden` rather than a `401 Unauthorized`, which means it has authenticated the user but the user is forbidden to access that particular resource.

Enjoy your secured resource server!

## Made it this far? Want a free t-shirt? We got ya.

Thank you for spending some time getting familiar with FusionAuth.

\*Offer only valid in the United States and Canada, while supplies last.

## Next Steps[#](#next-steps)

This quickstart is a great way to get a proof of concept up and running quickly, but to run your API in production, there are some things you're going to want to do.

### FusionAuth Integration

*   Rather than call the Login API, you're probably going to want to use the [Authorization Code grant](/docs/lifecycle/authenticate-users/oauth/#example-authorization-code-grant), which keeps all sensitive credentials within the bounds of FusionAuth. You can customize the [hosted login pages](/docs/customize/look-and-feel/).
*   You may want to generate a token using the [Client Credentials grant](/docs/lifecycle/authenticate-users/oauth/#example-client-credentials-grant) if you are calling the API from another service.

### Security

*   Customize the [token expiration times and policies](/docs/lifecycle/authenticate-users/oauth/#configure-application-oauth-settings) in FusionAuth
*   [Make sure you know how to securely consume a token](/articles/tokens/building-a-secure-jwt#consuming-a-jwt)
*   Secure your API [using an API gateway](/docs/extend/examples/api-gateways/) rather than at the framework layer.

## Troubleshooting[#](#troubleshooting)

*   I get `Failed to connect to localhost port 9011: Couldn't connect to server` when I call the Login API.

Ensure FusionAuth is running in the Docker container. You should be able to log in as the admin user, `admin@example.com` with a password of `password` at [http://localhost:9011/admin](http://localhost:9011/admin).

*   The `/panic` endpoint doesn't work when I call it.

Make sure you are making a `POST` call and using a token with the `teller` role.

*   It still doesn't work

You can always pull down a complete running application and compare what's different.

```
git clone https://github.com/FusionAuth/fusionauth-quickstart-dotnet-api.git
cd fusionauth-quickstart-dotnet-api
docker compose up -d
cd complete-application
dotnet run
```