Skip to content

Add method to use Auth0 authentication #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}


Expand All @@ -55,4 +66,4 @@ The _DefaultScheme_ attribute is used to specify what kind of authentication mus

**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.
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System.Net.Mime;
using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
using SimpleAuthentication.Auth0;
using SimpleAuthentication.JwtBearer;

namespace SimpleAuthentication.WebApi.Controllers;
Expand Down Expand Up @@ -58,6 +61,25 @@ public ActionResult<LoginResponse> Refresh(string token, bool validateLifetime =
var newToken = jwtBearerService.RefreshToken(token, validateLifetime, expiration);
return new LoginResponse(newToken);
}

[HttpPost]
[Route("auth0/login")]
[ProducesResponseType(typeof(LoginResponse), StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public ActionResult<LoginResponse> LoginAuth0([FromServices] IAuth0Service auth0Service)
{
// Check for login rights...

// Add custom claims (optional).
var claims = new List<Claim>
{
new(ClaimTypes.GivenName, "Marco"),
new(ClaimTypes.Surname, "Minerva")
};

var token = auth0Service.ObtainTokenAsync(claims);
return new LoginResponse(token.Result);
}
}

public record class LoginRequest(string UserName, string Password);
Expand Down
10 changes: 10 additions & 0 deletions samples/SimpleAuthentication.WebApi/Controllers/MeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ public class MeController : ControllerBase
[ProducesDefaultResponseType]
public ActionResult<User> 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);
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
11 changes: 10 additions & 1 deletion samples/SimpleAuthentication.WebApi/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"Authentication": {
"DefaultScheme": "Bearer", // Optional
"JwtBearer": {
"SchemeName": "Bearer", // Default Bearer
"SchemeName": "Bearer", // Default Bearer
"SecurityKey": "YKgsOiwvDLJe42dyyL3FkhlMAzZZ2Cmr0FTpyLsPE5DA2afd6NbbCV3d5oHDG2rVBaDHH540EUmrzXPPk2LnfanCdERl4apucmu2Ev5oVgN6dGCr8MMxXIIyTaNmmXHSsaONo75UkxQvFtsm9Qsnsz3VxuNzsoqrzqBQdsDvClo1LcrRNNcTdKcvceq1G57PZNxOWFS749wnsqq7r17a9vvinTdYME2umo7DRn8XUiwbdOajCehJfqipIjwbcuoCIrCwwMizKSiidw5KXU7koVvUSV0UH3o4TWHsVBnt5B1os6oPKtCQ63CPqlwHB5Pet4mzA2lhaFROZXbStpigaRJf3J6AOwZurMbo3LhzCpPW6KZwkixMpwCb82ekZvL0tmfQA2LeWDL2esZ9N4N8w8CzxrZt4gyEfywBwsoFohC0ydVznDpwbgCg05ktuczX3FFcsXEErwtY2wu0or0TSrUSnzIrYP26dOOUh4qREPJ7ZnZ5NoQjOMcXkiThdMuy", // Required
"Algorithm": "HS256", // Default HS256
"Issuers": [ "issuer" ], // Optional
Expand All @@ -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": "",
"Audience": "",
"ClientId": "",
"ClientSecret": ""
}
},
"Logging": {
Expand Down
149 changes: 149 additions & 0 deletions src/SimpleAuthentication/Auth0/Auth0Service.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
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;

/// <summary>
/// The auth0 service.
/// </summary>
internal class Auth0Service : IAuth0Service
{
private readonly Auth0Settings auth0Setting;
private readonly IHttpClientFactory httpClientFactory;

/// <summary>
/// Initializes a new instance of the <see cref="Auth0Service"/> class.
/// </summary>
/// <param name="auth0SettingOptions">The auth0 setting options.</param>
/// <param name="httpClientFactory">The http client factory.</param>
public Auth0Service(IOptions<Auth0Settings> auth0SettingOptions, IHttpClientFactory httpClientFactory)
{
auth0Setting = auth0SettingOptions.Value;
this.httpClientFactory = httpClientFactory;
}

/// <summary>
/// Obtains the token async.
/// </summary>
/// <param name="claims">The claims.</param>
/// <returns>A Task.</returns>
public async Task<string> ObtainTokenAsync(IList<Claim>? claims = null)
{
claims ??= new List<Claim>();

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<Auth0TokenResponse>(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
/// <summary>
/// Prepares the http client.
/// </summary>
/// <param name="json">The json.</param>
/// <param name="client">The client.</param>
/// <param name="content">The content.</param>
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"));
}

/// <summary>
/// Configure the content for an http request
/// </summary>
/// <param name="json">The json serialized of the body</param>
/// <returns>the content readey for the request</returns>
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
}

/// <summary>
/// The Auth0TokenResponse class.
/// </summary>
public record class Auth0TokenResponse
{
/// <summary>
/// Gets or sets the token.
/// </summary>
[JsonPropertyName("access_token")]
public string Token { get; set; }

/// <summary>
/// Gets or sets the expires in.
/// </summary>
[JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }

/// <summary>
/// Gets or sets the type.
/// </summary>
[JsonPropertyName("token_type")]
public string Type { get; set; }

/// <summary>
/// Initializes a new instance of the <see cref="Auth0TokenResponse"/> class.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="expiresIn">The expires in.</param>
/// <param name="type">The type.</param>
public Auth0TokenResponse(string token, int expiresIn, string type)
{
this.Token = token;
this.ExpiresIn = expiresIn;
this.Type = type;
}
}
45 changes: 45 additions & 0 deletions src/SimpleAuthentication/Auth0/Auth0Settings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;

namespace SimpleAuthentication.Auth0
{
/// <summary>
/// Options class provides information needed to control Auth0 Authentication handler behavior.
/// </summary>
public class Auth0Settings
{
/// <summary>
/// Gets or sets The authentication scheme name (Default: Bearer).
/// </summary>
public string SchemeName { get; set; } = JwtBearerDefaults.AuthenticationScheme;

/// <summary>
/// Gets or sets the cryptographic algorithm that is used to generate the digital signature (Default: RS256).
/// </summary>
public string Algorithm { get; set; } = "RS256";

/// <summary>
/// Gets or sets the domain.
/// </summary>
public string Domain { get; set; } = null!;

/// <summary>
/// Gets or sets the valid audiences that will be used to check against the token's audience.
/// </summary>
public string Audience { get; set; } = null!;

/// <summary>
/// Gets or sets the client id.
/// </summary>
public string ClientId { get; set; } = null!;

/// <summary>
/// Gets or sets the client secret.
/// </summary>
public string ClientSecret { get; set; } = null!;

/// <summary>
/// Gets or sets the grant type.
/// </summary>
public string GrantType { get; set; } = "client_credentials";
}
}
17 changes: 17 additions & 0 deletions src/SimpleAuthentication/Auth0/IAuth0Service.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Security.Claims;

namespace SimpleAuthentication.Auth0
{
/// <summary>
/// Provides methods for Auth0 Bearer generation and validation.
/// </summary>
public interface IAuth0Service
{
/// <summary>
/// Obtains a bearer token string from Auth0 provider.
/// </summary>
/// <param name="claims">The claims list.</param>
/// <returns>The JWT bearer token.</returns>
Task<string> ObtainTokenAsync(IList<Claim>? claims = null);
}
}
26 changes: 26 additions & 0 deletions src/SimpleAuthentication/SimpleAuthenticationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -134,6 +136,30 @@ static void CheckAddApiKey(AuthenticationBuilder builder, IConfigurationSection
options.DefaultUserName = settings.DefaultUserName;
});
}

static void CheckAddAuth0(AuthenticationBuilder builder, IConfigurationSection section)
{
var auth0Settings = section.Get<Auth0Settings>();
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<Auth0Settings>(section);

builder.AddJwtBearer(auth0Settings.SchemeName, options =>
{
options.Authority = auth0Settings.Domain;
options.Audience = auth0Settings.Audience;
});

builder.Services.TryAddSingleton<IAuth0Service, Auth0Service>();
builder.Services.AddHttpClient();
}
}

/// <summary>
Expand Down
Loading