Feature: Allow custom authorization
See original GitHub issueHiya,
Right now i keep having to add my token into the log view - this can be a bit tedious. Would it be possible to use authorization the same way hangfire does, by allowing custom Authorization filters?
See: https://docs.hangfire.io/en/latest/configuration/using-dashboard.html
The code looks simple enough from a first glance:
public interface IDashboardAsyncAuthorizationFilter
{
Task<bool> AuthorizeAsync([NotNull] DashboardContext context);
}
Hangfire.Dashboard.AspNetCoreDashboardMiddleware
foreach (IDashboardAuthorizationFilter authorizationFilter in this._options.Authorization)
{
if (!authorizationFilter.Authorize((DashboardContext) context))
{
httpContext.Response.StatusCode = AspNetCoreDashboardMiddleware.GetUnauthorizedStatusCode(httpContext);
context = (AspNetCoreDashboardContext) null;
findResult = (Tuple<IDashboardDispatcher, Match>) null;
return;
}
}
foreach (IDashboardAsyncAuthorizationFilter authorizationFilter in this._options.AsyncAuthorization)
{
if (!await authorizationFilter.AuthorizeAsync((DashboardContext) context))
{
httpContext.Response.StatusCode = AspNetCoreDashboardMiddleware.GetUnauthorizedStatusCode(httpContext);
context = (AspNetCoreDashboardContext) null;
findResult = (Tuple<IDashboardDispatcher, Match>) null;
return;
}
}
Startup:
endpoints.MapHangfireDashboard(new DashboardOptions
{
AsyncAuthorization = new[] {
new MyHangfireAuthenticationFilter(
new(
authApiUrl,
publicKeyUrl
),
services.GetRequiredService<ILogger<MYHangfireAuthenticationFilter>>()
)
}
});
Custom filter:
public class MyHangfireAuthenticationFilter : IDashboardAsyncAuthorizationFilter
{
....
public async Task<bool> AuthorizeAsync(DashboardContext context)
{
var httpContext = context.GetHttpContext();
if (_options.ForceSsl && httpContext.Request.Scheme != "https")
{
var redirectUri = new UriBuilder("https", httpContext.Request.Host.ToString(), 443, httpContext.Request.Path).ToString();
httpContext.Response.Redirect(redirectUri, true);
return false;
}
// If cookie has a JWT token we don't have to send a request to the auth api, we can just validate the token
if (httpContext.Request.Cookies.ContainsKey("token"))
{
try
{
_ = new JwtSecurityTokenHandler()
.ValidateToken(httpContext.Request.Cookies["token"],
new()
{
ClockSkew = TimeSpan.Zero,
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
IssuerSigningKey = await GetSecurityKey()
}, out _);
return true;
}
catch(Exception ex)
{
// Validation failed
_logger.LogError(ex, ex.Message);
}
}
// No token or invalid token, check for basic auth headers to validate
var authHeader = httpContext.Request.Headers["Authorization"];
if (!string.IsNullOrWhiteSpace(authHeader))
{
var authValues = AuthenticationHeaderValue.Parse(authHeader);
if (authValues.Scheme.Equals("Basic", StringComparison.InvariantCultureIgnoreCase))
{
if (string.IsNullOrWhiteSpace(authValues.Parameter))
throw new("string.IsNullOrWhiteSpace(authValues.Parameter)");
var authString = Encoding.UTF8.GetString(Convert.FromBase64String(authValues.Parameter));
var parts = authString.Split(':', 2);
if (parts.Length == 2)
{
var body = new
{
Email = parts[0],
Password = parts[1]
};
try
{
var content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8,
"application/json");
// Some custom auth logic with an external server
var result = await HttpClient.PostAsync($"{_options.AuthUrl}/api/authorize/login", content);
if (result.IsSuccessStatusCode)
{
var response = await result.Content.ReadAsStringAsync();
var jsonResponse = JsonSerializer.Deserialize<JsonDocument>(response) ?? throw new NullReferenceException("JsonSerializer.Deserialize<JsonDocument>(response)");
var token = jsonResponse.RootElement.GetProperty("token").GetString() ?? throw new NullReferenceException("jsonResponse.RootElement.GetProperty(\"token\").GetString()");
httpContext.Response.Cookies.Append("token", token);
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
}
}
}
}
await ShowChallenge(httpContext);
return false;
}
private async Task ShowChallenge(HttpContext context)
{
context.Response.StatusCode = 401;
context.Response.Headers.Append("WWW-Authenticate", "Basic realm=\"Hangfire Dashboard\"");
await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("Authentication is required."));
}
private async ValueTask<SecurityKey> GetSecurityKey()
{
if (_key == null)
{
_key = ParsePublicKey(await HttpClient.GetStringAsync(_options.CertificateUrl));
}
return _key;
}
private RsaSecurityKey ParsePublicKey(string key)
{
var rsa = RSA.Create();
var cert = new X509Certificate(Encoding.UTF8.GetBytes(key));
rsa.ImportRSAPublicKey(cert.GetPublicKey(), out _);
return new(rsa);
}
}
public readonly struct HangfireAuthenticationOptions
{
public string AuthUrl { get; }
public string CertificateUrl { get; }
public bool ForceSsl { get; }
public HangfireAuthenticationOptions(string url, string certificateUrl, bool forceSsl = true)
{
AuthUrl = url;
CertificateUrl = certificateUrl;
ForceSsl = forceSsl;
}
}
This way we’ll get the native browser login modal, which makes it easy to log in (especially since the browser can remember my account details)
I’d prefer:
- An Async interface
- DI so that i can inject an ILogger
Issue Analytics
- State:
- Created 2 years ago
- Reactions:1
- Comments:7 (7 by maintainers)
Top Results From Across the Web
Custom Authorization Policy Providers in ASP.NET Core
Learn how to use a custom IAuthorizationPolicyProvider in an ASP.NET Core app to dynamically generate authorization policies.
Read more >Authorization servers - Okta Developer
Okta allows you to create multiple custom authorization servers within a single Okta org that you can use to protect your own resource...
Read more >Sample Use Cases: Rules with Authorization - Auth0
With rules, you can modify or complement the outcome of the decision made by the pre-configured authorization policy to handle more complicated cases...
Read more >Use API Gateway Lambda authorizers - AWS Documentation
A Lambda authorizer (formerly known as a custom authorizer) is an API Gateway feature that uses a Lambda function to control access to...
Read more >Authentication and Authorization - ServiceStack Docs
The Auth Feature also allows you to specify your own custom IUserAuthSession type where you can capture additional metadata with your users session...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
I followed the Swashbuckle Swagger approach for authentication, simple and easy to implement. I don’t think there is a problem with storing token in session storage, however, there is a PR for UI and not merged yet and after merging that, it can be implemented and you are very welcome to help.
@sommmen, you’re welcome to add a description for this use case to the Readme file.