Connection Auth Types
BasicAuth class
The IBasicAuth
needs properties Username
, Password
and BaseUrl
.
Example of an AuthType class of BasicAuth
[ConnectionDefinition(title: "BasicAuth", description: "")]
public class BasicAuth : IBasicAuth
{
[ConnectionProperty(title: "Username", description: "", isRequired: true, isSensitive: false)]
public string Username { get; init; } = string.Empty;
[ConnectionProperty(title: "Password", description: "", isRequired: true, isSensitive: true)]
public string Password { get; init; } = string.Empty;
[ConnectionProperty(title: "Connection Environment", description: "", isRequired: true, isSensitive: false)]
public ConnectionEnvironmentBasicAuth ConnectionEnvironment { get; set; } = ConnectionEnvironmentBasicAuth.Unknown;
public string BaseUrl
{
get
{
switch (ConnectionEnvironment)
{
case ConnectionEnvironmentBasicAuth.Production:
return "http://prod.example.com";
case ConnectionEnvironmentBasicAuth.Test:
return "http://test.example.com";
default:
throw new Exception("No base url was set.");
}
}
}
}
public enum ConnectionEnvironmentBasicAuth
{
Unknown = 0,
Production = 1,
Test = 2
}
BasicAuth handler
Example of an AuthType handling of type Basic
public class BasicAuthHandler : DelegatingHandler
{
private readonly IBasicAuth _basicAuth;
public BasicAuthHandler(IBasicAuth basicAuth)
{
_basicAuth = basicAuth;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Remove("Authorization");
var byteArray = Encoding
.ASCII
.GetBytes($"{_basicAuth.Username}:{_basicAuth.Password}");
request.Headers.Add("Authorization", $"Basic {Convert.ToBase64String(byteArray)}");
return await base.SendAsync(request, cancellationToken);
}
}
ApiKeyAuth class
The IApiKeyAuth
needs properties ApiKey
and BaseUrl
.
Example of an AuthType class of ApiKeyAuth
[ConnectionDefinition(title: "ApiKeyAuth", description: "")]
public class ApiKeyAuth : IApiKeyAuth
{
[ConnectionProperty(title: "ApiKey", description: "", isRequired: true, isSensitive: true)]
public string ApiKey { get; init; } = string.Empty;
[ConnectionProperty(title: "Connection Environment", description: "", isRequired: true, isSensitive: false)]
public ConnectionEnvironmentApiKeyAuth ConnectionEnvironment { get; set; } = ConnectionEnvironmentApiKeyAuth.Unknown;
public string BaseUrl
{
get
{
switch (ConnectionEnvironment)
{
case ConnectionEnvironmentApiKeyAuth.Production:
return "http://prod.example.com";
case ConnectionEnvironmentApiKeyAuth.Test:
return "https://test.example.com";
default:
throw new Exception("No base url was set.");
}
}
}
}
public enum ConnectionEnvironmentApiKeyAuth
{
Unknown = 0,
Production = 1,
Test = 2
}
ApiKeyAuth handler
Example of an AuthType handling of type ApiKey
public class ApiKeyAuthHandler : DelegatingHandler
{
private readonly IApiKeyAuth _apiKeyAuth;
public ApiKeyAuthHandler(IApiKeyAuth apiKeyAuth)
{
_apiKeyAuth = apiKeyAuth;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Remove("X-Api-Key");
request.Headers.Add("X-Api-Key", _apiKeyAuth.ApiKey);
return await base.SendAsync(request, cancellationToken);
}
}
CustomAuth class
The ICustomAuth
only needs property BaseUrl
, and it's meant to be expanded by the properties the API needs.
Example of an AuthType class of CustomAuth
[ConnectionDefinition(title: "CustomAuth", description: "")]
public class CustomAuth : ICustomAuth
{
//Create your own properties here like this:
//[ConnectionProperty(title: "Custom Header", description: "", isRequired: true, isSensitive: false)]
//public string CustomHeader { get; init; } = string.Empty;
[ConnectionProperty(title: "Connection Environment", description: "", isRequired: true, isSensitive: false)]
public ConnectionEnvironmentCustomAuth ConnectionEnvironment { get; set; } = ConnectionEnvironmentCustomAuth.Unknown;
public string BaseUrl
{
get
{
switch (ConnectionEnvironment)
{
case ConnectionEnvironmentCustomAuth.Production:
return "http://prod.example.com";
case ConnectionEnvironmentCustomAuth.Test:
return "http://test.example.com";
default:
throw new Exception("No base url was set.");
}
}
}
}
public enum ConnectionEnvironmentCustomAuth
{
Unknown = 0,
Production = 1,
Test = 2
}
CustomAuth handler
Template of an AuthType handling of type Custom
public class CustomAuthHandler : DelegatingHandler
{
private readonly CustomAuth _customAuth;
public CustomAuthHandler(CustomAuth customAuth)
{
_customAuth = customAuth;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//request.Headers.Remove("X-Custom-Header");
//request.Headers.Add("X-Custom-Header, _customAuth.CustomHeader");
return await base.SendAsync(request, cancellationToken);
}
}
Example of an implemented AuthType handling of type Custom that uses a Username, Password and a User Agent in exchange for a token
public class CustomAuthHandler : DelegatingHandler
{
private readonly CustomAuth _customAuth;
private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
private readonly ILogger<CustomAuthHandler> _logger;
private string? _token;
public CustomAuthHandler(CustomAuth customAuth, ILogger<CustomAuthHandler> logger)
{
_customAuth = customAuth;
_logger = logger;
}
/// <summary>
/// Sends an HTTP request using a Username, Password and a User Agent to get a token authentication.
/// </summary>
/// <param name="request">The HTTP request message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The HTTP response message.</returns>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
await GetToken(request, cancellationToken);
var response = await base.SendAsync(request, cancellationToken);
//If the token that exists has expired, the Status Code will be 401: "Unauthorized"
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
//The method GetToken will either get or refresh the token in order to retry to send the request.
//If the retry fails for a second time, the response will be returned as "Unauthorized".
await GetToken(request, cancellationToken, true);
return await base.SendAsync(request, cancellationToken);
}
return response;
}
/// <summary>
/// Gets or refreshes the token and adds it to the request headers. The use of the semaphore will prevent multiple concurrent logins.
/// 1. If there's no token and "refresh" is false, it means there's no token yet and it will try to authenticate.
/// 2. If there's a token and "refresh" is false, it will only add the same token to the headers.
/// 3. If there's a token and "refresh" is true, it will try to authenticate in order to refresh the token.
/// </summary>
/// <param name="request">The HTTP request message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="refresh">Indicates whether to refresh the token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
private async Task GetToken(HttpRequestMessage request, CancellationToken cancellationToken, bool refresh = false)
{
if (string.IsNullOrEmpty(_token) || refresh)
{
try
{
//Stores the current token to reference later and check if it was already refreshed by another thread.
var previousToken = _token;
// Uses a semaphore to prevent concurrent logins -- this makes it so only one thread can enter this block at a time
await _tokenSemaphore.WaitAsync(cancellationToken);
// If the previous and current token no longer match after entering this block then another thread has already logged in, and there is no need for this thread to continue the log in process
if (previousToken != _token)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
return;
}
var response = await base.SendAsync(GetTokenRequest(), cancellationToken);
if (response.IsSuccessStatusCode)
{
var tokenRespose = await response.Content.ReadFromJsonAsync<JsonDocument>(cancellationToken: cancellationToken);
if (tokenRespose == null)
{
_logger.LogError("Error in JSON response while retrieving token");
throw new JsonException("Error deserializing authentication JSON response while retrieving token");
}
_token = tokenRespose.RootElement.GetProperty("access_token").GetString();
if (_token == null)
{
_logger.LogError($"Response did not contain an 'access_token' property. Token Response Content: {tokenRespose.RootElement.ToString()}");
throw new InvalidOperationException("Response did not contain an 'access_token' property.");
}
}
else
{
_token = null;
_logger.LogError($"Unsuccessful response while retrieving token. Please, verify your credentials. Status Code: {response.StatusCode}");
}
}
finally
{
_tokenSemaphore.Release();
}
}
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
}
public HttpRequestMessage GetTokenRequest()
{
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, _customAuth.BaseUrl)
{
Content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("username", _customAuth.Username),
new KeyValuePair<string, string>("password", _customAuth.Password),
}),
Headers =
{
{ "User-Agent", $"{_customAuth.UserAgent}" }
}
};
return tokenRequest;
}
}
About AuthType OAuth2 classes
Unlike the other types of connections, with OAuth2CodeFlow
, OAuth2ClientCredentials
and OAuth2Password
the AuthType class generated will not implement an interface but extend existing classes OAuth2CodeFlowBase
, OAuth2ClientCredentialsBase
and OAuth2PasswordBase
.
About Client Authentication
The OAuth2 base classes have a property called ClientAuthentication
. The value of this property will define if the authentication request that will exchange the ClientId
and ClientSecret
for a token will add the properties as headers or as content inside the body of the request. If no value is given, the default value will be BasicAuthHeader
.
The ClientAuthentication
enum looks like this:
The ClientAuthentication
enum
public enum ClientAuthentication
{
Unknown = 0,
BasicAuthHeader = 1,
ClientCredentialsInBody = 2
}
OAuth2CodeFlow class
OAuth2CodeFlow
extends an existing class OAuth2CodeFlowBase
, which contains properties AuthorizationUrl
, TokenUrl
, RefreshUrl
, ClientId
, ClientSecret
, Scope
and ClientAuthentication
.
The OAuth2CodeFlowBase
class
public class OAuth2CodeFlowBase
{
public string AuthorizationUrl { get; init; } = string.Empty;
public string TokenUrl { get; init; } = string.Empty;
public string RefreshUrl { get; init; } = string.Empty;
public string ClientId { get; init; } = string.Empty;
public string ClientSecret { get; init; } = string.Empty;
public string Scope { get; init; } = string.Empty;
public string Prompt { get; init; } = string.Empty;
public ClientAuthentication ClientAuthentication { get; init; } = ClientAuthentication.BasicAuthHeader;
}
Example of an AuthType class of OAuth2CodeFlow
[ConnectionDefinition(title: "OAuth2CodeFlow", description: "")]
public class OAuth2CodeFlow : OAuth2CodeFlowBase
{
[ConnectionProperty(title: "Connection Environment", description: "", isRequired: true, isSensitive: false)]
public ConnectionEnvironmentOAuth2CodeFlow ConnectionEnvironment { get; set; } = ConnectionEnvironmentOAuth2CodeFlow.Unknown;
public string BaseUrl
{
get
{
switch (ConnectionEnvironment)
{
case ConnectionEnvironmentOAuth2CodeFlow.Production:
return "http://prod.example.com";
case ConnectionEnvironmentOAuth2CodeFlow.Test:
return "https://test.example.com";
default:
throw new Exception("No base url was set.");
}
}
}
}
public enum ConnectionEnvironmentOAuth2CodeFlow
{
Unknown = 0,
Production = 1,
Test = 2
}
OAuth2ClientCredentials class
OAuth2ClientCredentials
extends an existing class OAuth2ClientCredentialsBase
, which contains properties TokenUrl
, ClientId
, ClientSecret
, Scope
and ClientAuthentication
.
The OAuth2ClientCredentialsBase
class
public class OAuth2ClientCredentialsBase
{
public string TokenUrl { get; init; } = string.Empty;
public string ClientId { get; init; } = string.Empty;
public string ClientSecret { get; init; } = string.Empty;
public string Scope { get; init; } = string.Empty;
public ClientAuthentication ClientAuthentication { get; init; } = ClientAuthentication.BasicAuthHeader;
}
Example of an AuthType class of OAuth2ClientCredentials
[ConnectionDefinition(title: "OAuth2ClientCredentials", description: "")]
public class OAuth2ClientCredentials : OAuth2ClientCredentialsBase
{
[ConnectionProperty(title: "Connection Environment", description: "", isRequired: true, isSensitive: false)]
public ConnectionEnvironmentOAuth2ClientCredentials ConnectionEnvironment { get; set; } = ConnectionEnvironmentOAuth2ClientCredentials.Unknown;
public string BaseUrl
{
get
{
switch (ConnectionEnvironment)
{
case ConnectionEnvironmentOAuth2ClientCredentials.Production:
return "http://prod.example.com";
case ConnectionEnvironmentOAuth2ClientCredentials.Test:
return "https://test.example.com";
default:
throw new Exception("No base url was set.");
}
}
}
}
public enum ConnectionEnvironmentOAuth2ClientCredentials
{
Unknown = 0,
Production = 1,
Test = 2
}
OAuth2ClientPassword class
OAuth2ClientPassword
extends an existing class OAuth2ClientPasswordBase
, which contains properties TokenUrl
, ClientId
, ClientSecret
, Scope
, Username
, Password
and ClientAuthentication
.
The OAuth2PasswordBase
class
public class OAuth2PasswordBase
{
public string TokenUrl { get; init; } = string.Empty;
public string ClientId { get; init; } = string.Empty;
public string ClientSecret { get; init; } = string.Empty;
public string Scope { get; init; } = string.Empty;
[ConnectionProperty(title: "Username", description: "", isRequired: true, isSensitive: false)]
public string Username { get; init; } = string.Empty;
[ConnectionProperty(title: "Password", description: "", isRequired: true, isSensitive: true)]
public string Password { get; init; } = string.Empty;
public ClientAuthentication ClientAuthentication { get; init; } = ClientAuthentication.BasicAuthHeader;
}
username
and password
properties are decorated with the ConnectionProperty
attribute because these properties are part of the connection properties and not part of the OAuth2 client being used to get the token access.
Example of an AuthType class of OAuth2Password
[ConnectionDefinition(title: "OAuth2Password", description: "")]
public class OAuth2Password : OAuth2PasswordBase
{
[ConnectionProperty(title: "Connection Environment", description: "", isRequired: true, isSensitive: false)]
public ConnectionEnvironmentOAuth2Password ConnectionEnvironment { get; set; } = ConnectionEnvironmentOAuth2Password.Unknown;
public string BaseUrl
{
get
{
switch (ConnectionEnvironment)
{
case ConnectionEnvironmentOAuth2Password.Production:
return "http://prod.example.com";
case ConnectionEnvironmentOAuth2Password.Test:
return "https://test.example.com";
default:
throw new Exception("No base url was set.");
}
}
}
}
public enum ConnectionEnvironmentOAuth2Password
{
Unknown = 0,
Production = 1,
Test = 2
}
About OAuth2 Handlers
The AuthType Handler is a DelegatingHandler
and is used to add headers to the requests to establish authentication in a transaction. Depending on the type of authentication needed, the headers will be added.
Unlike the other types of connections, if we're using any OAuth2 type authentication, the handler used by the API client will not be generated by the CLI therefore will not be editable. Instead, the handlers are part of the SDK package, since they follow OAuth2 standards and should not be changed.