Secure and explore ASP.NET Core Web APIs
How to create a ASP.NET Core Web API, secure it with JSON Web Tokens and explore it with Swagger UI and Postman.
You can view the example code in this post at https://github.com/hlaueriksson/ConductOfCode
ASP.NET Core Web API
I installed the new Visual Studio 2017 and created a new ASP.NET Core Web Application.
Then I added these dependencies:
- Swagger with Swashbuckle.AspNetCore
- JSON Web Tokens (JWT) with Microsoft.AspNetCore.Authentication.JwtBearer
The subject in this blog post is the StackController
:
-
The controller provides a REST interface for an in-memory stack. It’s the same example code I have used in a previous blog post.
-
The
[Authorize]
attribute specifies that the actions in the controller requires authorization. It will be handled with JSON Web Tokens. The configuration for this will be done in theStartup
class. -
The actions are decorated with
SwaggerResponse
attributes. This makes Swashbuckle understand the types returned for different status codes.
The appsettings.json
file has some custom configuration for the JWT authentication:
-
In this example we will use three things when issuing tokens;
Audience
,Issuer
and theSigningKey
-
The values for
Audience
andIssuer
can be an arbitrary string. They will be used as claims and the tokens that are issued will contain them. -
The
SigningKey
is used when generating the hashed signature for the token. The key must be kept secret. You probably want to use the Secret Manager to secure the key.
The TokenOptions
class is the type safe representation of the configuration in the appsettings:
-
The
Type
defaults toBearer
, which is the schema used by JWT. -
The expiration of the tokens defaults to one hour.
The TokenOptions
will be used in two places in the codebase. Therefor I extracted some convenience methods into TokenOptionsExtensions
:
-
GetExpiration
returns aDateTime
(UTC) indicating when the issued token should expire. -
GetSigningCredentials
returns an object that will be used for generating the token signature.HmacSha256
is the algorithm used. -
GetSymmetricSecurityKey
returns an object that wraps the value of theSigningKey
as a byte array.
The
Startup
class configures the request pipeline that handles all requests made to the application.
Swagger, SwaggerUI and JwtBearerAuthentication is configured here:
ConfigureServices:
-
The
AddOptions
method adds services required for using options. -
The
Configure<TokenOptions>
method registers a configuration instance whichTokenOptions
will bind against. TheTokenOptions
fromappsettings.json
will be accessible and available for dependency injection. -
The
AddSwaggerGen
method registers the Swagger generator.
Configure:
-
The
UseSwagger
method exposes the generated Swagger as JSON endpoint. It will be available under the route/swagger/v1/swagger.json
. -
The
UseSwaggerUI
method exposes Swagger UI, the auto-generated interactive documentation. It will be available under the route/swagger
. -
The
InjectOnCompleteJavaScript
method injects JavaScript to invoke when the Swagger UI has successfully loaded. I will get back to this later. -
The
UseStaticFiles
method enables static file serving. The injected JavaScript for the Swagger UI is served from thewwwroot
folder. -
The
UseJwtBearerAuthentication
method adds JWT bearer token middleware to the web application pipeline.Audience
andIssuer
will be used to validate the tokens. TheSigningKey
for token signatures is specified here. The authorization to theStackController
will now be handled with JWT.
Read more about Swashbuckle/Swagger here: https://github.com/domaindrivendev/Swashbuckle.AspNetCore
To issue tokens, let’s introduce the AuthenticationController
:
-
An
IOptions<TokenOptions>
object is injected to the constructor. The configuration is used when tokens are issues. -
The
JwtSecurityToken
class is used to represent a JSON Web Token. -
The
JwtSecurityTokenHandler
class writes a JWT as a JSON Compact serialized format string. -
Actual authentication is out of the scope of this blog post. You may want to look at IdentityServer4.
-
You probably want to require SSL/HTTPS for the API.
The response from the Token
action looks like this:
Audience
, Issuer
and Expiration
are included in the JWT payload:
If you copy the value of the access_token
, you can use https://jwt.io to view the decoded content of the JWT:
When accessing the StackController
, a JWT should be sent in the HTTP Authorization header:
Read more about JWT here: https://jwt.io/introduction/
The request and response classes:
Swagger UI
We can explore the API and the StackController
with Swagger UI.
The Swagger UI in this example is available at http://localhost:50480/swagger/
The Swagger specification file looks like this: swagger.json
Because the API is using JwtBearerAuthentication, we will now get a 401
Unauthorized
if we don’t provide the correct HTTP Authorization header.
To fix this we can inject some JavaScript to Swagger UI with Swashbuckle. I was reading Customize Authentication Header in SwaggerUI using Swashbuckle by Steve Michelotti before I was able to do this myself.
There are two approaches and two scripts located in the wwwroot
folder of the project:
authorization1.js
-
JQuery is used to post to the
AuthenticationController
and get a valid JWT -
When the response is returned, the
access_token
is added to the authorization header -
The
StackController
actions should now return responses with status codes200
Inject the script to Swagger UI in Startup.cs
:
The result looks like this:
-
Enter a username and password, click the Get token button to set the authorization header
-
The Get token button only needs to be clicked once per page load
authorization2.js
-
CryptoJS is used to generate a valid JWT
- The JWT payload and the
SigningKey
must be known
- The JWT payload and the
-
The generated token is added to the authorization header
-
The
StackController
actions should now return responses with status codes200
Inject the scripts to Swagger UI in Startup.cs
:
- The
crypto-js
script is injected from a CDN.
The result looks like this:
- Enter audience, issuer, and signing key. A token is generated and the authorization header is set every time the Try it out! button for an action is clicked.
Postman
We can explore the API and the StackController
with Postman.
Download: https://www.getpostman.com
Import the Swagger specification file:
Then the API is available in a collection:
Because the API is using JwtBearerAuthentication, we will now get a 401
Unauthorized
if we don’t provide the correct HTTP Authorization header.
There are two approaches to fix this.
Tests
The Token
action in the AuthenticationController
issues tokens.
Add some JavaScript in the Tests tab:
-
When the response is returned, the access_token is stored in the global variable
Authorization
-
The request to the
Token
action only needs to be sent once per token lifetime (one hour)
Add Authorization to all actions in the Headers tab:
- The token is accessed via the global variable
{{Authorization}}
The StackController
actions should now return responses with status codes 200
.
Pre-request Script
We can add some JavaScript to Postman that generates a valid JWT. I was reading JWT Authentication in Postman before I was able to do this myself.
-
CryptoJS is used to generate a valid JWT
- The JWT payload and the
SigningKey
must be known
- The JWT payload and the
-
The generated token is stored in the global variable
Authorization
Add the content of the authorization3.js
file to global variable authorize
:
Create an environment and add variables:
Audience
:http://localhost:50480
Issuer
:ConductOfCode
SigningKey
:cc4435685b40b2e9ddcb357fd79423b2d8e293b897d86f5336cb61c5fd31c9a3
Add JavaScript to all actions in the Pre-request Script tab:
-
The JavaScript is accessed from the global variable
authorize
and evaluated. -
The function
authorize
is executed and the token is generated.
The StackController
actions should now return responses with status codes 200
.
The collection can be exported:
- Perfect for version control
The export file looks like this: ConductOfCode.postman_collection.json
Troubleshooting
If you are having problems with JavaScript in Postman, read Enabling Chrome Developer Tools inside Postman.