In this article we will see how we can add JWT Token authentication to our Minimal API and how we will utilise Swagger to test it out
You can watch the full video on Youtube
Starting Github Repo:
(https://github.com/mohamadlawand087/v52-minimalAPi)
Final Github Rep:
(https://github.com/mohamadlawand087/MinimalApi-JWT)
Once we have pull our application we need to install a nuget package
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
Now that we have completed the initial setup we need to start building our Minimal API
We will be using Swagger to test our MinimalAPI so we will start by updating our Swagger Configuration
var securityScheme = new OpenApiSecurityScheme()
{
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JSON Web Token based security",
};
var securityReq = new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
};
var contact = new OpenApiContact()
{
Name = "Mohamad Lawand",
Email = "hello@mohamadlawand.com",
Url = new Uri("http://www.mohamadlawand.com")
};
var license = new OpenApiLicense()
{
Name = "Free License",
Url = new Uri("http://www.mohamadlawand.com")
};
var info = new OpenApiInfo()
{
Version = "v1",
Title = "Minimal API - JWT Authentication with Swagger demo",
Description = "Implementing JWT Authentication in Minimal API",
TermsOfService = new Uri("http://www.example.com"),
Contact = contact,
License = license
};
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(o =>
{
o.SwaggerDoc("v1", info);
o.AddSecurityDefinition("Bearer", securityScheme);
o.AddSecurityRequirement(securityReq);
});
After our builder we need to add the following to enable Swagger
app.UseSwagger();
app.UseSwaggerUI();
Once our Swagger is configured, let us now add our JWT configuration.
The first item of our configuration is updating our app settings to the following
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Jwt": {
"Issuer": "mohamadlawand.com",
"Audience": "mohamadlawand.com",
"Key": "ijurkbdlhmklqacwqzdxmkkhvqowlyqa"
},
"AllowedHosts": "*"
}
Next we need to add our JWT config to our builder
// Add JWT configuration
builder.Services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = true
};
});
builder.Services.AddAuthorization();
builder.Services.AddEndpointsApiExplorer();
Now we need to enable authentication and authorisation to our app
app.UseAuthentication();
app.UseAuthorization();
Since we are expecting a dto lets create our authentication dto
record UserDto (string UserName, string Password);
Now we need to get our endpoint which will give us the ability to generate and utilise our JWT token
app.MapPost("/security/getToken", [AllowAnonymous] (UserDto user) =>
{
if (user.UserName=="admin@mohamadlawand.com" && user.Password=="P@ssword")
{
var issuer = builder.Configuration["Jwt:Issuer"];
var audience = builder.Configuration["Jwt:Audience"];
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
// Now its ime to define the jwt token which will be responsible of creating our tokens
var jwtTokenHandler = new JwtSecurityTokenHandler();
// We get our secret from the appsettings
var key = Encoding.ASCII.GetBytes(builder.Configuration["Jwt:Key"]);
// we define our token descriptor
// We need to utilise claims which are properties in our token which gives information about the token
// which belong to the specific user who it belongs to
// so it could contain their id, name, email the good part is that these information
// are generated by our server and identity framework which is valid and trusted
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new []
{
new Claim("Id", "1"),
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Email, user.UserName),
// the JTI is used for our refresh token which we will be convering in the next video
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
}),
// the life span of the token needs to be shorter and utilise refresh token to keep the user signedin
// but since this is a demo app we can extend it to fit our current need
Expires = DateTime.UtcNow.AddHours(6),
Audience = audience,
Issuer = issuer,
// here we are adding the encryption alogorithim information which will be used to decrypt our token
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
};
var token = jwtTokenHandler.CreateToken(tokenDescriptor);
var jwtToken = jwtTokenHandler.WriteToken(token);
return Results.Ok(jwtToken);
}
else
{
return Results.Unauthorized();
}
});
The last part is to make our endpoint secure by adding the authorise attribute
app.MapGet("/items", [Authorize] async (ApiDbContext db) =>
{
return await db.Items.ToListAsync();
});
app.MapPost("/items", [Authorize] async (ApiDbContext db, Item item) => {
if( await db.Items.FirstOrDefaultAsync(x => x.Id == item.Id) != null)
{
return Results.BadRequest();
}
db.Items.Add(item);
await db.SaveChangesAsync();
return Results.Created( $"/Items/{item.Id}",item);
});
app.MapGet("/items/{id}", [Authorize] async (ApiDbContext db, int id) =>
{
var item = await db.Items.FirstOrDefaultAsync(x => x.Id == id);
return item == null ? Results.NotFound() : Results.Ok(item);
});
app.MapPut("/items/{id}", [Authorize] async (ApiDbContext db, int id, Item item) =>
{
var existItem = await db.Items.FirstOrDefaultAsync(x => x.Id == id);
if(existItem == null)
{
return Results.BadRequest();
}
existItem.Title = item.Title;
existItem.IsCompleted = item.IsCompleted;
await db.SaveChangesAsync();
return Results.Ok(item);
});
app.MapDelete("/items/{id}", [Authorize] async (ApiDbContext db, int id) =>
{
var existItem = await db.Items.FirstOrDefaultAsync(x => x.Id == id);
if(existItem == null)
{
return Results.BadRequest();
}
db.Items.Remove(existItem);
await db.SaveChangesAsync();
return Results.NoContent();
});
Please comment your questions and subscribe to my YouTube channel
Top comments (1)
Hi, thanks for the tutorial, well written and easy to follow.
One thing - the github repos your reference no longer exist?