From 4fecc5345d2b4aafc526aecc9c46326697f9b331 Mon Sep 17 00:00:00 2001 From: Michele Tolve Date: Mon, 6 Jun 2022 11:42:54 +0200 Subject: [PATCH 1/8] Add method to use Auth0 authentication --- .../Controllers/IdentityController.cs | 41 ++++++++++++++++++- .../Controllers/MeController.cs | 10 +++++ .../SimpleAuthentication.WebApi.csproj | 1 + .../appsettings.json | 11 ++++- .../Auth0/Auth0Service.cs | 38 +++++++++++++++++ .../Auth0/Auth0Settings.cs | 40 ++++++++++++++++++ .../Auth0/IAuth0Service.cs | 17 ++++++++ .../SimpleAuthenticationExtensions.cs | 25 +++++++++++ .../Swagger/AuthenticationOperationFilter.cs | 8 +++- 9 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 src/SimpleAuthentication/Auth0/Auth0Service.cs create mode 100644 src/SimpleAuthentication/Auth0/Auth0Settings.cs create mode 100644 src/SimpleAuthentication/Auth0/IAuth0Service.cs diff --git a/samples/SimpleAuthentication.WebApi/Controllers/IdentityController.cs b/samples/SimpleAuthentication.WebApi/Controllers/IdentityController.cs index f1fd907..dda0a6d 100644 --- a/samples/SimpleAuthentication.WebApi/Controllers/IdentityController.cs +++ b/samples/SimpleAuthentication.WebApi/Controllers/IdentityController.cs @@ -1,6 +1,9 @@ using System.Net.Mime; using System.Security.Claims; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using SimpleAuthentication.Auth0; using SimpleAuthentication.JwtBearer; namespace SimpleAuthentication.WebApi.Controllers; @@ -58,10 +61,46 @@ public ActionResult Refresh(string token, bool validateLifetime = var newToken = jwtBearerService.RefreshToken(token, validateLifetime, expiration); return new LoginResponse(newToken); } + + [HttpPost] + [Route("auth0")] + public LoginResponseAuth0? LoginAuth0([FromServices] IAuth0Service auth0Service) + { + // Check for login rights... + + // Add custom claims (optional). + var claims = new List + { + new(ClaimTypes.GivenName, "Marco"), + new(ClaimTypes.Surname, "Minerva") + }; + + var token = auth0Service.ObtainTokenAsync(claims); + return JsonConvert.DeserializeObject(token.Result); + } } public record class LoginRequest(string UserName, string Password); public record class LoginResponse(string Token); -public record class ValidationResponse(bool IsValid, User? User); \ No newline at end of file +public record class ValidationResponse(bool IsValid, User? User); + +public record class LoginResponseAuth0 +{ + [JsonProperty("access_token")] + public string Token { get; set; } + + [JsonProperty("expires_in")] + public int ExpiresIn { get; set; } + + [JsonProperty("token_type")] + public string Type { get; set; } + + public LoginResponseAuth0(string token, int expiresIn, string type) + { + this.Token = token; + this.ExpiresIn = expiresIn; + this.Type = type; + } +} diff --git a/samples/SimpleAuthentication.WebApi/Controllers/MeController.cs b/samples/SimpleAuthentication.WebApi/Controllers/MeController.cs index 5bdf36b..67a9c1a 100644 --- a/samples/SimpleAuthentication.WebApi/Controllers/MeController.cs +++ b/samples/SimpleAuthentication.WebApi/Controllers/MeController.cs @@ -16,6 +16,16 @@ public class MeController : ControllerBase [ProducesDefaultResponseType] public ActionResult GetWithBearer() => new User(User.Identity!.Name); + + [Authorize(AuthenticationSchemes = "ApiKey")] + [HttpGet("authorize-apikey")] + public User GetWithApiKey() + => new(User.Identity!.Name); + + [Authorize(AuthenticationSchemes = "Auth0")] + [HttpGet("authorize-auth0")] + public User GetWithAuth0() + => new("Auth0 default user"); } public record class User(string? UserName); \ No newline at end of file diff --git a/samples/SimpleAuthentication.WebApi/SimpleAuthentication.WebApi.csproj b/samples/SimpleAuthentication.WebApi/SimpleAuthentication.WebApi.csproj index 954285f..6353f5a 100644 --- a/samples/SimpleAuthentication.WebApi/SimpleAuthentication.WebApi.csproj +++ b/samples/SimpleAuthentication.WebApi/SimpleAuthentication.WebApi.csproj @@ -8,6 +8,7 @@ + diff --git a/samples/SimpleAuthentication.WebApi/appsettings.json b/samples/SimpleAuthentication.WebApi/appsettings.json index 6898315..ece2a03 100644 --- a/samples/SimpleAuthentication.WebApi/appsettings.json +++ b/samples/SimpleAuthentication.WebApi/appsettings.json @@ -2,7 +2,7 @@ "Authentication": { "DefaultAuthenticationScheme": "Bearer", // Optional "JwtBearer": { - "SchemeName": "Bearer", // Default Bearer + "SchemeName": "Bearer", // Default Bearer "SecurityKey": "YKgsOiwvDLJe42dyyL3FkhlMAzZZ2Cmr0FTpyLsPE5DA2afd6NbbCV3d5oHDG2rVBaDHH540EUmrzXPPk2LnfanCdERl4apucmu2Ev5oVgN6dGCr8MMxXIIyTaNmmXHSsaONo75UkxQvFtsm9Qsnsz3VxuNzsoqrzqBQdsDvClo1LcrRNNcTdKcvceq1G57PZNxOWFS749wnsqq7r17a9vvinTdYME2umo7DRn8XUiwbdOajCehJfqipIjwbcuoCIrCwwMizKSiidw5KXU7koVvUSV0UH3o4TWHsVBnt5B1os6oPKtCQ63CPqlwHB5Pet4mzA2lhaFROZXbStpigaRJf3J6AOwZurMbo3LhzCpPW6KZwkixMpwCb82ekZvL0tmfQA2LeWDL2esZ9N4N8w8CzxrZt4gyEfywBwsoFohC0ydVznDpwbgCg05ktuczX3FFcsXEErwtY2wu0or0TSrUSnzIrYP26dOOUh4qREPJ7ZnZ5NoQjOMcXkiThdMuy", // Required "Algorithm": "HS256", // Default HS256 "Issuers": [ "issuer" ], // Optional @@ -21,6 +21,15 @@ // to validate the API Key. //"ApiKeyValue": "f1I7S5GXa4wQDgLQWgz0", "DefaultUserName": "ApiUser" // Required when ApiKeyValue is used + }, + "Auth0":{ + // This parameter are getteedtaken from https://auth0.com/ only for test + "SchemeName": "Auth0", + "Algorithm": "RS256", + "Domain": "https://dev-a6-vyksc.us.auth0.com", // The domain created on https://auth0.com/ + "Audience": "https://github.com/micheletolve", + "ClientId": "ipSAr24nCse9QIAlpN6nm2sYdarlaVY5", + "ClientSecret": "dr-qxPyLT2O7eDzCdzal9CHAe-V7t-aouZWBsDNCUsCk6r-rOjrVRQtZ9zGL7wCT" } }, "Logging": { diff --git a/src/SimpleAuthentication/Auth0/Auth0Service.cs b/src/SimpleAuthentication/Auth0/Auth0Service.cs new file mode 100644 index 0000000..63e744d --- /dev/null +++ b/src/SimpleAuthentication/Auth0/Auth0Service.cs @@ -0,0 +1,38 @@ +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text; +using Microsoft.Extensions.Options; + +namespace SimpleAuthentication.Auth0 +{ + internal class Auth0Service : IAuth0Service + { + private readonly Auth0Settings auth0Setting; + + public Auth0Service(IOptions auth0SettingOptions) + { + auth0Setting = auth0SettingOptions.Value; + } + + public async Task ObtainTokenAsync(IList? claims = null) + { + string responseContent; + HttpClient httpClient = new HttpClient(); + httpClient.BaseAddress = new Uri(auth0Setting.Domain); + httpClient.DefaultRequestHeaders + .Accept + .Add(new MediaTypeWithQualityHeaderValue("application/json")); + + var request = new HttpRequestMessage(HttpMethod.Post, "/oauth/token"); + request.Content = new StringContent("{\"client_id\":\"ipSAr24nCse9QIAlpN6nm2sYdarlaVY5\",\"client_secret\":\"dr-qxPyLT2O7eDzCdzal9CHAe-V7t-aouZWBsDNCUsCk6r-rOjrVRQtZ9zGL7wCT\",\"audience\":\"https://github.com/micheletolve\",\"grant_type\":\"client_credentials\"}", + Encoding.UTF8, + "application/json"); + + using (var responseMessage = await httpClient.SendAsync(request)) + { + responseContent = await responseMessage.Content.ReadAsStringAsync(); + } + return responseContent; + } + } +} \ No newline at end of file diff --git a/src/SimpleAuthentication/Auth0/Auth0Settings.cs b/src/SimpleAuthentication/Auth0/Auth0Settings.cs new file mode 100644 index 0000000..fc850e2 --- /dev/null +++ b/src/SimpleAuthentication/Auth0/Auth0Settings.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; + +namespace SimpleAuthentication.Auth0 +{ + /// + /// Options class provides information needed to control Auth0 Authentication handler behavior. + /// + public class Auth0Settings + { + /// + /// Gets or sets The authentication scheme name (Default: Auth0). + /// + public string SchemeName { get; set; } = JwtBearerDefaults.AuthenticationScheme; + + /// + /// Gets or sets the cryptographic algorithm that is used to generate the digital signature (Default: RS256). + /// + public string Algorithm { get; set; } = "RS256"; + + /// + /// Gets or sets the domain. + /// + public string Domain { get; set; } = null!; + + /// + /// Gets or sets the valid audiences that will be used to check against the token's audience. + /// + public string Audience { get; set; } = null!; + + /// + /// Gets or sets the client id. + /// + public string CilentId { get; set; } = null!; + + /// + /// Gets or sets the client secret. + /// + public string CilentSecret { get; set; } = null!; + } +} \ No newline at end of file diff --git a/src/SimpleAuthentication/Auth0/IAuth0Service.cs b/src/SimpleAuthentication/Auth0/IAuth0Service.cs new file mode 100644 index 0000000..389aaa1 --- /dev/null +++ b/src/SimpleAuthentication/Auth0/IAuth0Service.cs @@ -0,0 +1,17 @@ +using System.Security.Claims; + +namespace SimpleAuthentication.Auth0 +{ + /// + /// Provides methods for Auth0 Bearer generation and validation. + /// + public interface IAuth0Service + { + /// + /// Obtains a bearer token string from Auth0 provider. + /// + /// The claims list. + /// + Task ObtainTokenAsync(IList? claims = null); + } +} \ No newline at end of file diff --git a/src/SimpleAuthentication/SimpleAuthenticationExtensions.cs b/src/SimpleAuthentication/SimpleAuthenticationExtensions.cs index 7651a20..9d39120 100644 --- a/src/SimpleAuthentication/SimpleAuthenticationExtensions.cs +++ b/src/SimpleAuthentication/SimpleAuthenticationExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.Net.Http.Headers; using Microsoft.OpenApi.Models; using SimpleAuthentication.ApiKey; +using SimpleAuthentication.Auth0; using SimpleAuthentication.JwtBearer; using SimpleAuthentication.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; @@ -64,6 +65,7 @@ public static ISimpleAuthenticationBuilder AddSimpleAuthentication(this Authenti { CheckAddJwtBearer(builder, configuration.GetSection($"{sectionName}:JwtBearer")); CheckAddApiKey(builder, configuration.GetSection($"{sectionName}:ApiKey")); + CheckAddAuth0(builder, configuration.GetSection($"{sectionName}:Auth0")); return new DefaultSimpleAuthenticationBuilder(configuration, builder); @@ -134,6 +136,29 @@ static void CheckAddApiKey(AuthenticationBuilder builder, IConfigurationSection options.DefaultUserName = settings.DefaultUserName; }); } + + static void CheckAddAuth0(AuthenticationBuilder builder, IConfigurationSection section) + { + var auth0Settings = section.Get(); + if (auth0Settings is null) + { + return; + } + + ArgumentNullException.ThrowIfNull(auth0Settings.SchemeName, nameof(Auth0Settings.SchemeName)); + ArgumentNullException.ThrowIfNull(auth0Settings.Domain, nameof(Auth0Settings.Domain)); + ArgumentNullException.ThrowIfNull(auth0Settings.Audience, nameof(Auth0Settings.Audience)); + + builder.Services.Configure(section); + + builder.AddJwtBearer(auth0Settings.SchemeName, options => + { + options.Authority = auth0Settings.Domain; + options.Audience = auth0Settings.Audience; + }); + + builder.Services.TryAddSingleton(); + } } /// diff --git a/src/SimpleAuthentication/Swagger/AuthenticationOperationFilter.cs b/src/SimpleAuthentication/Swagger/AuthenticationOperationFilter.cs index bfe0961..4508834 100644 --- a/src/SimpleAuthentication/Swagger/AuthenticationOperationFilter.cs +++ b/src/SimpleAuthentication/Swagger/AuthenticationOperationFilter.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; using SimpleAuthentication.ApiKey; +using SimpleAuthentication.Auth0; using SimpleAuthentication.JwtBearer; using Swashbuckle.AspNetCore.SwaggerGen; @@ -18,12 +19,14 @@ internal class AuthenticationOperationFilter : IOperationFilter private readonly IAuthorizationPolicyProvider authorizationPolicyProvider; private readonly JwtBearerSettings jwtBearerSettings; private readonly ApiKeySettings apiKeySettings; + private readonly Auth0Settings auth0Settings; - public AuthenticationOperationFilter(IAuthorizationPolicyProvider authorizationPolicyProvider, IOptions jwtBearerSettingsOptions, IOptions apiKeySettingsOptions) + public AuthenticationOperationFilter(IAuthorizationPolicyProvider authorizationPolicyProvider, IOptions jwtBearerSettingsOptions, IOptions apiKeySettingsOptions, IOptions auth0SettingsOptions) { this.authorizationPolicyProvider = authorizationPolicyProvider; jwtBearerSettings = jwtBearerSettingsOptions.Value; apiKeySettings = apiKeySettingsOptions.Value; + auth0Settings = auth0SettingsOptions.Value; } public void Apply(OpenApiOperation operation, OperationFilterContext context) @@ -49,6 +52,9 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) CheckAddSecurityRequirement(operation, hasApiKeyHeaderAuthentication ? $"{apiKeySettings.SchemeName} in Header" : null); CheckAddSecurityRequirement(operation, hasApiKeyQueryAuthentication ? $"{apiKeySettings.SchemeName} in Query String" : null); + var hasAuth0Authentication = !string.IsNullOrWhiteSpace(auth0Settings.Domain); + CheckAddSecurityRequirement(operation, hasAuth0Authentication ? $"{auth0Settings.SchemeName} Bearer" : null); + operation.Responses.TryAdd(StatusCodes.Status401Unauthorized.ToString(), GetResponse(HttpStatusCode.Unauthorized.ToString())); operation.Responses.TryAdd(StatusCodes.Status403Forbidden.ToString(), GetResponse(HttpStatusCode.Forbidden.ToString())); } From e9b49ea1f0bc782fc7229e77c5cbb05f4fb994d4 Mon Sep 17 00:00:00 2001 From: Michele Tolve Date: Tue, 7 Jun 2022 12:18:07 +0200 Subject: [PATCH 2/8] Update appsettings.json Removed the personal access data from the file settings --- samples/SimpleAuthentication.WebApi/appsettings.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/SimpleAuthentication.WebApi/appsettings.json b/samples/SimpleAuthentication.WebApi/appsettings.json index ece2a03..1f3e7ba 100644 --- a/samples/SimpleAuthentication.WebApi/appsettings.json +++ b/samples/SimpleAuthentication.WebApi/appsettings.json @@ -26,10 +26,10 @@ // This parameter are getteedtaken from https://auth0.com/ only for test "SchemeName": "Auth0", "Algorithm": "RS256", - "Domain": "https://dev-a6-vyksc.us.auth0.com", // The domain created on https://auth0.com/ - "Audience": "https://github.com/micheletolve", - "ClientId": "ipSAr24nCse9QIAlpN6nm2sYdarlaVY5", - "ClientSecret": "dr-qxPyLT2O7eDzCdzal9CHAe-V7t-aouZWBsDNCUsCk6r-rOjrVRQtZ9zGL7wCT" + "Domain": "", + "Audience": "", + "ClientId": "", + "ClientSecret": "" } }, "Logging": { From 63939b4b50fc85a5bdc92a339b862292790e047e Mon Sep 17 00:00:00 2001 From: Michele Tolve Date: Tue, 7 Jun 2022 12:30:58 +0200 Subject: [PATCH 3/8] Remove Newtosoft.Json and renamed class --- .../Controllers/IdentityController.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/samples/SimpleAuthentication.WebApi/Controllers/IdentityController.cs b/samples/SimpleAuthentication.WebApi/Controllers/IdentityController.cs index dda0a6d..4562caa 100644 --- a/samples/SimpleAuthentication.WebApi/Controllers/IdentityController.cs +++ b/samples/SimpleAuthentication.WebApi/Controllers/IdentityController.cs @@ -1,8 +1,8 @@ using System.Net.Mime; using System.Security.Claims; +using System.Text.Json; +using System.Text.Json.Serialization; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using SimpleAuthentication.Auth0; using SimpleAuthentication.JwtBearer; @@ -64,7 +64,7 @@ public ActionResult Refresh(string token, bool validateLifetime = [HttpPost] [Route("auth0")] - public LoginResponseAuth0? LoginAuth0([FromServices] IAuth0Service auth0Service) + public Auth0LoginResponse? LoginAuth0([FromServices] IAuth0Service auth0Service) { // Check for login rights... @@ -76,7 +76,7 @@ public ActionResult Refresh(string token, bool validateLifetime = }; var token = auth0Service.ObtainTokenAsync(claims); - return JsonConvert.DeserializeObject(token.Result); + return JsonSerializer.Deserialize(token.Result); } } @@ -86,18 +86,18 @@ public record class LoginResponse(string Token); public record class ValidationResponse(bool IsValid, User? User); -public record class LoginResponseAuth0 +public record class Auth0LoginResponse { - [JsonProperty("access_token")] + [JsonPropertyName("access_token")] public string Token { get; set; } - [JsonProperty("expires_in")] + [JsonPropertyName("expires_in")] public int ExpiresIn { get; set; } - [JsonProperty("token_type")] + [JsonPropertyName("token_type")] public string Type { get; set; } - public LoginResponseAuth0(string token, int expiresIn, string type) + public Auth0LoginResponse(string token, int expiresIn, string type) { this.Token = token; this.ExpiresIn = expiresIn; From 42b8563b91d5400f9ee356e334408b46dc1358bb Mon Sep 17 00:00:00 2001 From: Michele Tolve Date: Tue, 7 Jun 2022 12:45:35 +0200 Subject: [PATCH 4/8] Fixed the default scheme name --- src/SimpleAuthentication/Auth0/Auth0Settings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SimpleAuthentication/Auth0/Auth0Settings.cs b/src/SimpleAuthentication/Auth0/Auth0Settings.cs index fc850e2..b7b87ea 100644 --- a/src/SimpleAuthentication/Auth0/Auth0Settings.cs +++ b/src/SimpleAuthentication/Auth0/Auth0Settings.cs @@ -8,7 +8,7 @@ namespace SimpleAuthentication.Auth0 public class Auth0Settings { /// - /// Gets or sets The authentication scheme name (Default: Auth0). + /// Gets or sets The authentication scheme name (Default: Bearer). /// public string SchemeName { get; set; } = JwtBearerDefaults.AuthenticationScheme; From 98fe28af9a867866b2bd013c5264a344bc496960 Mon Sep 17 00:00:00 2001 From: Michele Tolve Date: Tue, 7 Jun 2022 12:49:44 +0200 Subject: [PATCH 5/8] Add return description. --- src/SimpleAuthentication/Auth0/IAuth0Service.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SimpleAuthentication/Auth0/IAuth0Service.cs b/src/SimpleAuthentication/Auth0/IAuth0Service.cs index 389aaa1..84ccdf3 100644 --- a/src/SimpleAuthentication/Auth0/IAuth0Service.cs +++ b/src/SimpleAuthentication/Auth0/IAuth0Service.cs @@ -11,7 +11,7 @@ public interface IAuth0Service /// Obtains a bearer token string from Auth0 provider. /// /// The claims list. - /// + /// The JWT bearer token. Task ObtainTokenAsync(IList? claims = null); } } \ No newline at end of file From b69215e05c6a7cfa8047bb792d077949425eb4d5 Mon Sep 17 00:00:00 2001 From: Michele Tolve Date: Tue, 7 Jun 2022 15:45:14 +0200 Subject: [PATCH 6/8] Fixed code. Now the value for the request content not is hard-coded and use the HttpClientFactory to create a typed client. --- .../Auth0/Auth0Service.cs | 88 ++++++++++++++----- .../Auth0/Auth0Settings.cs | 9 +- .../SimpleAuthenticationExtensions.cs | 1 + 3 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/SimpleAuthentication/Auth0/Auth0Service.cs b/src/SimpleAuthentication/Auth0/Auth0Service.cs index 63e744d..a7784bc 100644 --- a/src/SimpleAuthentication/Auth0/Auth0Service.cs +++ b/src/SimpleAuthentication/Auth0/Auth0Service.cs @@ -1,38 +1,80 @@ using System.Net.Http.Headers; using System.Security.Claims; using System.Text; +using System.Text.Json; using Microsoft.Extensions.Options; -namespace SimpleAuthentication.Auth0 +namespace SimpleAuthentication.Auth0; + +internal class Auth0Service : IAuth0Service { - internal class Auth0Service : IAuth0Service + private readonly Auth0Settings auth0Setting; + private readonly IHttpClientFactory httpClientFactory; + + public Auth0Service(IOptions auth0SettingOptions, IHttpClientFactory httpClientFactory) + { + auth0Setting = auth0SettingOptions.Value; + this.httpClientFactory = httpClientFactory; + } + + public async Task ObtainTokenAsync(IList? claims = null) { - private readonly Auth0Settings auth0Setting; + string clientId = auth0Setting.ClientId; + string clientIdSecret = auth0Setting.ClientSecret; + var jsonObject = new + { + client_id = auth0Setting.ClientId, + client_secret = auth0Setting.ClientSecret, + audience = auth0Setting.Audience, + grant_type = auth0Setting.GrantType + }; + + string json = JsonSerializer.Serialize(value: jsonObject); + PrepareHttpClient(json, out HttpClient client, out StringContent content); + HttpResponseMessage httpResponseMessage = await client.PostAsync("/oauth/token", content); - public Auth0Service(IOptions auth0SettingOptions) + if (!httpResponseMessage.IsSuccessStatusCode) { - auth0Setting = auth0SettingOptions.Value; + return null; } - public async Task ObtainTokenAsync(IList? claims = null) + return await httpResponseMessage.Content.ReadAsStringAsync(); + } + + private void PrepareHttpClient(string json, out HttpClient client, out StringContent content) + { + try { - string responseContent; - HttpClient httpClient = new HttpClient(); - httpClient.BaseAddress = new Uri(auth0Setting.Domain); - httpClient.DefaultRequestHeaders - .Accept - .Add(new MediaTypeWithQualityHeaderValue("application/json")); - - var request = new HttpRequestMessage(HttpMethod.Post, "/oauth/token"); - request.Content = new StringContent("{\"client_id\":\"ipSAr24nCse9QIAlpN6nm2sYdarlaVY5\",\"client_secret\":\"dr-qxPyLT2O7eDzCdzal9CHAe-V7t-aouZWBsDNCUsCk6r-rOjrVRQtZ9zGL7wCT\",\"audience\":\"https://github.com/micheletolve\",\"grant_type\":\"client_credentials\"}", - Encoding.UTF8, - "application/json"); - - using (var responseMessage = await httpClient.SendAsync(request)) - { - responseContent = await responseMessage.Content.ReadAsStringAsync(); - } - return responseContent; + var baseUri = new Uri(auth0Setting.Domain); + + client = httpClientFactory.CreateClient(auth0Setting.SchemeName); + client.Timeout = TimeSpan.FromSeconds(30); + + content = SetContent(json); + + client.BaseAddress = baseUri; + client.DefaultRequestHeaders.Host = baseUri.Host; + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + } + catch (Exception) + { + client = null; + content = null; } } + + /// + /// Configure the content for an http request + /// + /// The json serialized of the body + /// the content readey for the request + private static StringContent? SetContent(string json) + { + if (string.IsNullOrEmpty(json)) + return null; + + StringContent content = new(json, Encoding.UTF8, "application/json"); + content.Headers.ContentLength = json.Length; + return content; + } } \ No newline at end of file diff --git a/src/SimpleAuthentication/Auth0/Auth0Settings.cs b/src/SimpleAuthentication/Auth0/Auth0Settings.cs index b7b87ea..7e04c79 100644 --- a/src/SimpleAuthentication/Auth0/Auth0Settings.cs +++ b/src/SimpleAuthentication/Auth0/Auth0Settings.cs @@ -30,11 +30,16 @@ public class Auth0Settings /// /// Gets or sets the client id. /// - public string CilentId { get; set; } = null!; + public string ClientId { get; set; } = null!; /// /// Gets or sets the client secret. /// - public string CilentSecret { get; set; } = null!; + public string ClientSecret { get; set; } = null!; + + /// + /// Gets or sets the grant type. + /// + public string GrantType { get; set; } = "client_credentials"; } } \ No newline at end of file diff --git a/src/SimpleAuthentication/SimpleAuthenticationExtensions.cs b/src/SimpleAuthentication/SimpleAuthenticationExtensions.cs index 9d39120..ae3f083 100644 --- a/src/SimpleAuthentication/SimpleAuthenticationExtensions.cs +++ b/src/SimpleAuthentication/SimpleAuthenticationExtensions.cs @@ -158,6 +158,7 @@ static void CheckAddAuth0(AuthenticationBuilder builder, IConfigurationSection s }); builder.Services.TryAddSingleton(); + builder.Services.AddHttpClient(); } } From febb47a766b932179a489a31ee6db8f99fed1689 Mon Sep 17 00:00:00 2001 From: Michele Tolve Date: Wed, 15 Jun 2022 09:04:55 +0200 Subject: [PATCH 7/8] Return LginResponse on auth0 login request Fixed code, now use LoginResponse because the information about the expiration and else is inside the token. --- .../Controllers/IdentityController.cs | 29 +-- .../Auth0/Auth0Service.cs | 227 ++++++++++++------ .../Auth0/IAuth0Service.cs | 32 +-- 3 files changed, 170 insertions(+), 118 deletions(-) mode change 100644 => 100755 src/SimpleAuthentication/Auth0/Auth0Service.cs mode change 100644 => 100755 src/SimpleAuthentication/Auth0/IAuth0Service.cs diff --git a/samples/SimpleAuthentication.WebApi/Controllers/IdentityController.cs b/samples/SimpleAuthentication.WebApi/Controllers/IdentityController.cs index 4562caa..256db90 100644 --- a/samples/SimpleAuthentication.WebApi/Controllers/IdentityController.cs +++ b/samples/SimpleAuthentication.WebApi/Controllers/IdentityController.cs @@ -63,8 +63,10 @@ public ActionResult Refresh(string token, bool validateLifetime = } [HttpPost] - [Route("auth0")] - public Auth0LoginResponse? LoginAuth0([FromServices] IAuth0Service auth0Service) + [Route("auth0/login")] + [ProducesResponseType(typeof(LoginResponse), StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public ActionResult LoginAuth0([FromServices] IAuth0Service auth0Service) { // Check for login rights... @@ -76,7 +78,7 @@ public ActionResult Refresh(string token, bool validateLifetime = }; var token = auth0Service.ObtainTokenAsync(claims); - return JsonSerializer.Deserialize(token.Result); + return new LoginResponse(token.Result); } } @@ -84,23 +86,4 @@ public record class LoginRequest(string UserName, string Password); public record class LoginResponse(string Token); -public record class ValidationResponse(bool IsValid, User? User); - -public record class Auth0LoginResponse -{ - [JsonPropertyName("access_token")] - public string Token { get; set; } - - [JsonPropertyName("expires_in")] - public int ExpiresIn { get; set; } - - [JsonPropertyName("token_type")] - public string Type { get; set; } - - public Auth0LoginResponse(string token, int expiresIn, string type) - { - this.Token = token; - this.ExpiresIn = expiresIn; - this.Type = type; - } -} +public record class ValidationResponse(bool IsValid, User? User); \ No newline at end of file diff --git a/src/SimpleAuthentication/Auth0/Auth0Service.cs b/src/SimpleAuthentication/Auth0/Auth0Service.cs old mode 100644 new mode 100755 index a7784bc..e3042d5 --- a/src/SimpleAuthentication/Auth0/Auth0Service.cs +++ b/src/SimpleAuthentication/Auth0/Auth0Service.cs @@ -1,80 +1,149 @@ -using System.Net.Http.Headers; -using System.Security.Claims; -using System.Text; -using System.Text.Json; -using Microsoft.Extensions.Options; - -namespace SimpleAuthentication.Auth0; - -internal class Auth0Service : IAuth0Service -{ - private readonly Auth0Settings auth0Setting; - private readonly IHttpClientFactory httpClientFactory; - - public Auth0Service(IOptions auth0SettingOptions, IHttpClientFactory httpClientFactory) - { - auth0Setting = auth0SettingOptions.Value; - this.httpClientFactory = httpClientFactory; - } - - public async Task ObtainTokenAsync(IList? claims = null) - { - string clientId = auth0Setting.ClientId; - string clientIdSecret = auth0Setting.ClientSecret; - var jsonObject = new - { - client_id = auth0Setting.ClientId, - client_secret = auth0Setting.ClientSecret, - audience = auth0Setting.Audience, - grant_type = auth0Setting.GrantType - }; - - string json = JsonSerializer.Serialize(value: jsonObject); - PrepareHttpClient(json, out HttpClient client, out StringContent content); - HttpResponseMessage httpResponseMessage = await client.PostAsync("/oauth/token", content); - - if (!httpResponseMessage.IsSuccessStatusCode) - { - return null; - } - - return await httpResponseMessage.Content.ReadAsStringAsync(); - } - - private void PrepareHttpClient(string json, out HttpClient client, out StringContent content) - { - try - { - var baseUri = new Uri(auth0Setting.Domain); - - client = httpClientFactory.CreateClient(auth0Setting.SchemeName); - client.Timeout = TimeSpan.FromSeconds(30); - - content = SetContent(json); - - client.BaseAddress = baseUri; - client.DefaultRequestHeaders.Host = baseUri.Host; - client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - } - catch (Exception) - { - client = null; - content = null; - } - } - - /// - /// Configure the content for an http request - /// - /// The json serialized of the body - /// the content readey for the request - private static StringContent? SetContent(string json) - { - if (string.IsNullOrEmpty(json)) - return null; - - StringContent content = new(json, Encoding.UTF8, "application/json"); - content.Headers.ContentLength = json.Length; - return content; - } +using System.IdentityModel.Tokens.Jwt; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Protocols; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Tokens; + + +namespace SimpleAuthentication.Auth0; + +/// +/// The auth0 service. +/// +internal class Auth0Service : IAuth0Service +{ + private readonly Auth0Settings auth0Setting; + private readonly IHttpClientFactory httpClientFactory; + + /// + /// Initializes a new instance of the class. + /// + /// The auth0 setting options. + /// The http client factory. + public Auth0Service(IOptions auth0SettingOptions, IHttpClientFactory httpClientFactory) + { + auth0Setting = auth0SettingOptions.Value; + this.httpClientFactory = httpClientFactory; + } + + /// + /// Obtains the token async. + /// + /// The claims. + /// A Task. + public async Task ObtainTokenAsync(IList? claims = null) + { + claims ??= new List(); + + var jsonObject = new + { + client_id = auth0Setting.ClientId, + client_secret = auth0Setting.ClientSecret, + audience = auth0Setting.Audience, + grant_type = auth0Setting.GrantType + }; + + string json = JsonSerializer.Serialize(value: jsonObject); + PrepareHttpClient(json, out HttpClient client, out StringContent content); + + try + { + HttpResponseMessage httpResponseMessage = await client.PostAsync("/oauth/token", content); + + if (httpResponseMessage.IsSuccessStatusCode) + { + var response = httpResponseMessage.Content.ReadAsStringAsync(); + var token = JsonSerializer.Deserialize(response.Result)!; + + claims.Update(ClaimTypes.Expiration, token.ExpiresIn.ToString()); + claims.Update(ClaimTypes.AuthenticationInstant, DateTime.UtcNow.ToString()); + + return token.Token; + } + + return httpResponseMessage.ReasonPhrase!; + } + catch (HttpRequestException e) + { + throw new HttpRequestException($"Error occurred while sending the request to obtain the Jwt Token from Auth0 provider. Error {e.Message}"); + //return e.Message; + } + } + + #region PrivateMethod + /// + /// Prepares the http client. + /// + /// The json. + /// The client. + /// The content. + private void PrepareHttpClient(string json, out HttpClient client, out StringContent content) + { + var baseUri = new Uri($"https:/{auth0Setting.Domain}"); + content = SetContent(json); + + client = httpClientFactory.CreateClient(auth0Setting.SchemeName); + client.Timeout = TimeSpan.FromSeconds(30); + client.BaseAddress = baseUri; + client.DefaultRequestHeaders.Host = baseUri.Host; + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + } + + /// + /// Configure the content for an http request + /// + /// The json serialized of the body + /// the content readey for the request + private static StringContent SetContent(string json) + { + if (string.IsNullOrEmpty(json)) + return null; + + StringContent content = new(json, Encoding.UTF8, "application/json"); + content.Headers.ContentLength = json.Length; + return content; + } + #endregion +} + +/// +/// The Auth0TokenResponse class. +/// +public record class Auth0TokenResponse +{ + /// + /// Gets or sets the token. + /// + [JsonPropertyName("access_token")] + public string Token { get; set; } + + /// + /// Gets or sets the expires in. + /// + [JsonPropertyName("expires_in")] + public int ExpiresIn { get; set; } + + /// + /// Gets or sets the type. + /// + [JsonPropertyName("token_type")] + public string Type { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The token. + /// The expires in. + /// The type. + public Auth0TokenResponse(string token, int expiresIn, string type) + { + this.Token = token; + this.ExpiresIn = expiresIn; + this.Type = type; + } } \ No newline at end of file diff --git a/src/SimpleAuthentication/Auth0/IAuth0Service.cs b/src/SimpleAuthentication/Auth0/IAuth0Service.cs old mode 100644 new mode 100755 index 84ccdf3..ef2c8db --- a/src/SimpleAuthentication/Auth0/IAuth0Service.cs +++ b/src/SimpleAuthentication/Auth0/IAuth0Service.cs @@ -1,17 +1,17 @@ -using System.Security.Claims; - -namespace SimpleAuthentication.Auth0 -{ - /// - /// Provides methods for Auth0 Bearer generation and validation. - /// - public interface IAuth0Service - { - /// - /// Obtains a bearer token string from Auth0 provider. - /// - /// The claims list. - /// The JWT bearer token. - Task ObtainTokenAsync(IList? claims = null); - } +using System.Security.Claims; + +namespace SimpleAuthentication.Auth0 +{ + /// + /// Provides methods for Auth0 Bearer generation and validation. + /// + public interface IAuth0Service + { + /// + /// Obtains a bearer token string from Auth0 provider. + /// + /// The claims list. + /// The JWT bearer token. + Task ObtainTokenAsync(IList? claims = null); + } } \ No newline at end of file From 1b109d0250fbf411200e3638324b62f849b2248c Mon Sep 17 00:00:00 2001 From: Michele Tolve Date: Wed, 15 Jun 2022 09:12:10 +0200 Subject: [PATCH 8/8] Update file --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) mode change 100644 => 100755 README.md diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 2075002..dfef536 --- a/README.md +++ b/README.md @@ -45,7 +45,18 @@ Authentication can be totally configured adding an _Authentication_ section in t // to validate the API Key. //"ApiKeyValue": "f1I7S5GXa4wQDgLQWgz0", "DefaultUserName": "ApiUser" // Required ApiKeyValue is used - } + }, + "Auth0":{ + // This parameters are taken from https://auth0.com/ and you can use these only + // for test or you can create your personal api easily from dashboard. + "SchemeName": "Auth0", + "Algorithm": "RS256", + "Domain": "dev-a6-vyksc.us.auth0.com", + "Audience": "https://github.com/micheletolve", + "ClientId": "ipSAr24nCse9QIAlpN6nm2sYdarlaVY5", + "ClientSecret": "dr-qxPyLT2O7eDzCdzal9CHAe-V7t-aouZWBsDNCUsCk6r-rOjrVRQtZ9zGL7wCT", + "GrantType": "client_credentials" + } } @@ -55,4 +66,4 @@ The _DefaultAuthenticationScheme_ attribute is used to specify what kind of auth **Contribute** -The project is constantly evolving. Contributions are welcome. Feel free to file issues and pull requests on the repo and we'll address them as we can. +The project is constantly evolving. Contributions are welcome. Feel free to file issues and pull requests on the repo and we'll address them as we can.