Authentication is a standard feature which can be added with very few steps to generated autorest clients. This will work regardless of whether the swagger.json contains the ‘Authorization’ header or not.
Use case
Call an API which requires an Authentication header from within another API, a client application or tests. This post is concentrating on the changes required in the client.
Note
The approach described here will work best for APIs which require authentication for all endpoints. If the API contains a mix of authenticated and unauthenticated methods, it will likely work better to generate the client without credentials and add credentials via delegating handlers or new method on the generated client. See https://github.com/Azure/autorest/issues/930 for ideas.
Pre-Requisite
An autorest client, generated from a swagger.json file. If there are problems generating the client for an API, then check my post Fix Autorest Problems when Generating Client with Swashbuckle swagger.json.
Step 1: Specify ‘add-credentials’
Add the flag ‘add-credentials: true’ to the parameters passed to the autorest code generator or the language parameters in the readme.md file as shown below.
[... shortened]
csharp:
namespace: Clients
add-credentials: true
[... shortened]
After re-generating the client, the constructor will now require credentials to be passed in and throw an exception if the credentials are NULL.
Step 2: Get a Token
The implementation on how to retrieve a token will look different depending on the system. The token could be defined in configuration or retrieved from an Authentication Server. In real life this could be Azure Active Directory Authentication, some other cloud or internal authentication server.
In our sample solution a minimal Identity Server has been added, which supports client credentials, similar to this standard Identity Server quickstart sample. This allows the full solution to work locally.
- The consumer (for instance tests or any other client) authenticates with the client credential grant to the Identity Server and retrieves a JWT. This JWT will be referred to as ‘access token’ in this post and is a string looking like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- The access token will be sent in the Authorization header for each request to the book API.
Note: Any token issued by the authentication server would work, regardless of whether it is a reference token or a JWT. See https://leastprivilege.com/2015/11/25/reference-tokens-and-introspection/ for an explanation of the difference.
The standard library for token retrieval in C# is IdentityModel, below sample code for retrieving the access token in C#:
var httpClient = new HttpClient();
var disco = await httpClient.GetDiscoveryDocumentAsync("http://localhost:5000");
if (disco.IsError)
{
// deal with error
}
// request token
// client credentials would be used in service-to-service authentication and the
// returned token would likely be a relatively long lived JWT
var tokenResponse = await httpClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "book-api-consumer",
ClientSecret = "secret",
Scope = "book-api.full-access"
});
if (tokenResponse.IsError)
{
// deal with error
}
var accessToken = tokenResponse.AccessToken;
The link to some very basic typescript code can be found in the following sections.
Step 3: Initialize the Autorest Client with Credentials
The access token is passed into the API client when it is instantiated. After that the client can be used as before.
C# Sample:
var apiClient = new BookApiClient(new TokenCredentials(accessToken),
httpClient, false)
{
BaseUri = new Uri("http://localhost:5001")
};
See full code in GitHub
Typescript Sample:
const client = new BookApiClient(new TokenCredentials(response.body.access_token),
{ baseUri: 'http://localhost:5001' });
See full typescript code in GitHub
Full Changes to Add Authorization
The following changes were made to a very basic ASP.NET Core service to add authorization:
- Server Side Changes to require authorization
- Add Identity Server to the solution,
- Require authorization in the Book API,
- Client side changes
- Update the generated client,
- Update the C# and Typescript consumer to request and use credentials
The full set of changes can be found in this pull-request: https://github.com/AngelaE/openapi-sample/pull/1