Trying to force a user after login to change password - asp.net-core-5.0

I'm trying to force an authenticated user to change password based on a database Boolean flag. I have used asp.net core resource filter to do that. When ChangePassword flag is on I'm hitting this error. If the ChangePassword flag is is false, application doesn't encounter any error.
InvalidOperationException: If an IAsyncResourceFilter provides a result value by setting the Result property of ResourceExecutingContext to a non-null value, then it cannot call the next filter by invoking ResourceExecutionDelegate.
My codes are as follows -
public class ChangePasswordFilter: IAsyncResourceFilter
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IUrlHelperFactory _urlHelperFactory;
public ChangePasswordFilter(UserManager<ApplicationUser> userManager, IUrlHelperFactory urlHelperFactory)
{
_userManager = userManager;
_urlHelperFactory = urlHelperFactory;
}
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
var HttpContext = context.HttpContext;
var urlHelper = _urlHelperFactory.GetUrlHelper(context);
var redirectUrl = urlHelper.Page("/UserManagement/ChangePassword");
var currentUrl = context.HttpContext.Request.Path;
if (redirectUrl != currentUrl)
{
var user = await _userManager.GetUserAsync(context.HttpContext.User);
if (user?.ChangePassword ?? false)
{
//context.Result = new RedirectResult(redirectUrl);
context.Result = new RedirectToActionResult("ChangePassword", "UserManagement", new { ReturnUrl = HttpContext.Request.Path });
}
}
await next();
}
And in startup.ConfigureServices
services.AddScoped<ChangePasswordFilter>();
services.AddMvc(o =>
{
o.Filters.Add(typeof(ChangePasswordFilter));
});
This example is taken from this answer
Force user change password when loading any webpage
Any help appreciated.
Thanks.

The link you referenced is for a question on asp.net core 2.0. There are many differences between asp.net core 2.0 and asp.net core 5.0.
However, the error message is quite clear. You are invoking the ResourceExecutionDelegate after setting the ResourceExecutingContext Result property to a non-null value.
I'm not too familiar with ResourceFilters but what happens if you replace await next(); with return;?

I was having the same issue, while trying to implement this same code.
I did add return; after context.Result, and my code is working fine.
My code:
context.Result = new RedirectResult(redirectUrl);
return;
Hope you find this helpful.

Related

how to add a token head to a request using HttpClient from IHttpClientFactory in Blazor

I am trying to use JWT in my API, and configuration is completed, can use postman tool to access data from it. However when I use Blazor as front end to access it , the request doesn't have token, so always give a 401 code.
Below is my Blazor code.
program.cs
builder.Services.AddHttpClient<IOptionService, OptionService> ("OptionAPI", (sp, cl) => {
cl.BaseAddress = new Uri("https://localhost:7172");
});
builder.Services.AddScoped(
sp => sp.GetService<IHttpClientFactory>().CreateClient("OptionAPI"));
OptionService.cs
public class OptionService : IOptionService {
private readonly HttpClient _httpClient;
public OptionService(HttpClient httpClient) {
_httpClient = httpClient;
}
public async Task<IEnumerable<OptionOutputDto>> GetOptionsAsync(Guid quizId, Guid questionId) {
_httpClient.DefaultRequestHeaders.Authorization
= new AuthenticationHeaderValue("Bearer", "token");
return await JsonSerializer.DeserializeAsync<IEnumerable<OptionOutputDto>>(
await _httpClient.GetStreamAsync($"api/quizzes/{quizId}/{questionId}/options"),
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});
}
I tired use " new AuthenticationHeaderValue("Bearer", "token");" to attach token in header, but its not working, still give 401 code.
And I also tried use
private readonly IHttpClientFactory _httpClient;
public OptionService(IHttpClientFactory httpClient) {
_httpClient = httpClient;
}
public async Task<IEnumerable<OptionOutputDto>> GetOptionsAsync(Guid quizId, Guid questionId) {
var newHttpClient = _httpClient.CreateClient();
newHttpClient.DefaultRequestHeaders.Authorization
= new AuthenticationHeaderValue("Bearer", "token");
return await JsonSerializer.DeserializeAsync<IEnumerable<OptionOutputDto>>(
await newHttpClient.GetStreamAsync($"api/quizzes/{quizId}/{questionId}/options"),
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});
}
it's also not working, give me an error,
Unhandled exception rendering component: A suitable constructor for type 'Services.OptionService' could not be located. Ensure the type is concrete and all parameters of a public constructor are either registered as services or passed as arguments. Also ensure no extraneous arguments are provided.
System.InvalidOperationException: A suitable constructor for type .....
Can anyone has a simple way to attach token in request header?
Thanks in advance.
I think the good option is :
builder.Services.AddHttpClient<IOptionService, OptionService> ("OptionAPI", (sp, cl) => {
cl.BaseAddress = new Uri("https://localhost:7172");
});
Could you check if the token is present in header or not?
Your error is most likely related to how the OptionService is being registered in dependency injection. It either needs an empty constructor adding - and/or - you need to ensure that the constructor has all of its dependencies registered correctly in the ServicesCollection too.
The exception is quite explicit:
Ensure the type is concrete and all parameters of a public constructor
are either registered as services or passed as arguments. Also ensure
no extraneous arguments are provided
I gave a similar answer here. Basically you need to include the BaseAddressAuthorizationMessageHandler when defining your httpclients. If you're using a typed httpclient, you can inject the IAccessTokenProvider and get the token from there. Kinda like this:
public class MyHttpClient(IAccessTokenProvider tokenProvider, HttpClient httpClient)
{
_tokenProvider = tokenProvider;
_httpClient = httpClient;
}
private async Task RequestAuthToken()
{
var requestToken = await _tokenProvider.RequestAccessToken();
requestToken.TryGetToken(out var token);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value);
}
public async Task<IEnumerable<ReplyDto>> SendHttpRequest()
{
await RequestAuthToken();
return await JsonSerializer.DeserializeAsync<IEnumerable<ReplyDto>>(
await _httpClient.GetStreamAsync("api/getendpoint"),
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true
});
}

How to know the user edited?

I am using Blazor server-side with ASP.NET Core 5 and EF Core 5. I would like that when a record is updated than the ModifiedBy and CreatedBy are generated automatically.
I have overriden SaveChangeAsync as follows:
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ApplyInterfaces();
return base.SaveChangesAsync(cancellationToken);
}
private void ApplyInterfaces()
{
var userId = _httpContextAccessor?.HttpContext?.User?.Identity?.Name;
var currentUsername = !string.IsNullOrEmpty(userId)? userId: "Anonymous";
foreach (ICreatedBy entity in ChangeTracker.Entries().Where(x => x.Entity is ICreatedBy).Where(x => x.State == EntityState.Added).Select(x=> x.Entity))
{
entity.CreatedBy = currentUsername;
entity.CreatedDate = DateTime.Now;
}
foreach (IModifiedBy entity in ChangeTracker.Entries().Where(x=> x.State == EntityState.Modified).Where(x => x.Entity is IModifiedBy).Select(x => x.Entity))
{
entity.ModifiedBy = currentUsername;
entity.ModifiedDate = DateTime.Now;
}
}
_httpContextAccessor is injected to the DBContext. I added the services
services.AddHttpContextAccessor();
Everything works fine on IIS Express. But when I publish to IIS _httpContextAccessor.HttpContext is null.
Where did I miss something?
You should not use IHttpContextAccessor in Blazor apps (source).
It's not clear from your code sample if this code is in a Blazor component/page or in a database access library. If it's in the database layer you should modify SaveChangesAsync to add a string username parameter, which you can then pass to ApplyInterfaces.
Getting the user should be the responsibility of the Blazor page/component, using either AuthenticationStateProvider or an AuthorizeView component.
#page "/example"
#inject AuthenticationStateProvider auth;
<button #OnClick="Save">Save</button>
#code
{
async Task Save()
{
var state = await auth.GetAuthenticationStateAsync();
var username = state.User.Identity?.Name ?? "[anon]";
await someObject.SaveChangesAsync(username);
}
}

How do I use Audit.NET Entity Framework Data Provider to save Audit.NET WebAPI audit logs?

I am having difficulty understanding the documentation for the Audit.NET Entity Framework Data Provider, to save Audit.NET WebAPI audit logs to my database.
This is how I have my Audit configuration set, just to test. I have a breakpoint inside the AuditEntityAction on entity.ChangeType = ev.EventType, but this never gets hit when I call an audited action on my controller.
Audit.Core.Configuration.Setup()
.UseEntityFramework(x =>
x.AuditTypeMapper(t => typeof(AuditLog))
.AuditEntityAction<AuditLog>((ev, entry, entity) =>
{
entity.ChangeType = ev.EventType;
entity.ObjectType = entry.EntityType.Name;
entity.PrimaryKey = "test";
entity.TableName = "test";
entity.UserId = entry.CustomFields[UserIdField].ToString();
})
.IgnoreMatchedProperties()
);
On my controller action, I have the decorator:
[AuditApi(EventTypeName = "Organisation:Create", IncludeRequestBody = true, IncludeResponseBody = true)]
Is this correct? I am not very clear on this, and I would appreciate some pointers.
The Entity Framework Data Provider is part of the library Audit.EntityFramework and was designed to exclusively store the audits that are generated by an audited Entity Framework DbContext.
So it will not work for WebApi events of any other kind of event.
Here you can see how the audit event is discarded if it's not an AuditEventEntityFramework
So you should create your own Custom Data Provider or maybe use the SQL Data Provider.
You can use Audit.NetWebApi package to get the WebApiAudit logs
public static void UseAudit(this IApplicationBuilder app, IHttpContextAccessor contextAccessor)
{
Audit.Core.Configuration.AddCustomAction(ActionType.OnScopeCreated, scope =>
{
var entityTrack = scope.Event.GetEntityFrameworkEvent();
var requestTrack = scope.Event.GetWebApiAuditAction();
if (entityTrack!=null)
{
foreach (var item in entityTrack.Entries)
{
scope.Event.CustomFields[Table] = item.Table;
scope.Event.CustomFields[Action] = item.Action;
}
}
else if(requestTrack!=null)
{
scope.Event.CustomFields[Action] = $"{requestTrack.ActionName}:{requestTrack.ActionName}";
scope.Event.CustomFields[RequestBody] = requestTrack.RequestBody.Value.ToString();
scope.Event.CustomFields[ResponseBody] = requestTrack.ResponseBody?.Value?.ToString()?? string.Empty;
scope.Event.CustomFields[Exception] = requestTrack.Exception?? string.Empty;
}
});
}
And then put this function in Startup.cs ConfigureApp
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IHttpContextAccessor contextAccessor)
{
app.UseAudit(contextAccessor);
}
Constants used:
private const string Table = "Table";
private const string Action = "Action";
private const string RequestBody = "RequestBody";
private const string ResponseBody = "ResponseBody";
private const string Exception = "Exception";

Facebook login manual flow with error

After I dealt with this error a verified many fori and all mentioned that the solution for this error would be having in Facebook settings the “Valid OAuth Redirect URIs” set to “https://www.facebook.com/connect/login_success.htm”, what it is, so that is not the issue. Furthermore, all solutions found are too old and use obsolete components.
Said that using Xamarin Forms I’m trying to do the manual flow to login into Facebook as described in “https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow”.
The code is:
using Authentication.ViewModels;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Authentication
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class FacebookProfilePage : ContentPage
{
private string ClientId = "910688099117930";
public FacebookProfilePage()
{
InitializeComponent();
var apiRequest =
"https://www.facebook.com/dialog/oauth?client_id="
+ ClientId
+ "&display=popup&response_type=token&redirect_uri=http://www.facebook.com/connect/login_success.html"
+ "&state=state123abc";
var webView = new WebView
{
Source = apiRequest,
HeightRequest = 1
};
webView.Navigated += WebViewOnNavigated;
Content = webView;
}
private async void WebViewOnNavigated(object sender, WebNavigatedEventArgs e)
{
var accessToken = ExtractAccessTokenFromUrl(e.Url);
if (accessToken != "")
{
var vm = BindingContext as FacebookViewModel;
await vm.SetFacebookUserProfileAsync(accessToken);
Content = MainStackLayout;
}
}
private string ExtractAccessTokenFromUrl(string url)
{
if (url.Contains("access_token") && url.Contains("&expires_in="))
{
var at = url.Replace("https://www.facebook.com/connect/login_success.html#access_token=", "");
if (Device.OS == TargetPlatform.WinPhone || Device.OS == TargetPlatform.Windows)
{
at = url.Replace("http://www.facebook.com/connect/login_success.html#access_token=", "");
}
var accessToken = at.Remove(at.IndexOf("&expires_in="));
return accessToken;
}
return string.Empty;
}
}
}
Please, notice that this is a open code, found in “https://github.com/HoussemDellai/Facebook-Login-Xamarin-Forms”, which historically has proven itself to work fine.
The URL that I send to login is: “https://www.facebook.com/v3.0/dialog/oauth?client_id=910688099117930&response_type=token&redirect_uri=http://www.facebook.com/connect/login_success.html”. I always get the error message: “Not Logged In: You are not logged in. Please login and try again.”.
Can anyone tell me what’s wrong here?
And before any comment, yes, I’ll delete this App Id as soon as the problem is solved and change it for a new one 😊

How does REST authentication work for client-side apps?

I'm trying to design my first public API, and I'm trying to learn how REST works with authentication, especially in the context of completely client-side apps using js-frameworks, e.g., angularJS.
Say you have a client which is a browser application (i.e., HTML, JS, CSS only) served as static files from something like nginx using a javascript framework to consume a REST service from, e.g. something that requires a secret access key that's used to create a signature for each request to the service, something like Amazon S3.
In terms of authentication in this scenario, where you don't have a server-side application, how would the secret access key be handled, i.e., how do you get it, where do you store it, etc.? It would seem like a horrible security situation to serve the key for each request (even if it only happens once to bootstrap the application).
And even if you do have a light server-side application--how do you securely inform the client (which still calls the authenticated 3rd party API itself) what the signature should be for every request it could possibly make? I'm very confused by how this is supposed to be designed from either end.
I've done a few AngularJS apps and the way that I've found is to use an HttpModule like this one:
using System;
using System.Net.Http.Headers;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Web;
namespace YourSolution.WebApp.Modules
{
public class BasicAuthenticationHttpModule : IHttpModule
{
public BasicAuthenticationHttpModule()
{
}
public void Init(HttpApplication context)
{
context.AuthenticateRequest += OnApplicationAuthenticateRequest;
context.EndRequest += OnApplicationEndRequest;
}
private static void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
private static bool CheckPassword(
string username, string password)
{
return username == password;
}
private static void AuthenticateUser(string credentials)
{
try
{
var encoding = Encoding.GetEncoding(
"iso-8859-1");
credentials = encoding.GetString(
Convert.FromBase64String(credentials));
var separator = credentials.IndexOf(':');
var name = credentials.Substring(0, separator);
var password = credentials.Substring(separator + 1);
var validated = CheckPassword(name, password);
if (!validated) return;
var identity = new GenericIdentity(name);
SetPrincipal(new GenericPrincipal(identity, null));
}
catch (FormatException)
{
}
}
private static void OnApplicationAuthenticateRequest(
object sender, EventArgs e)
{
var request = HttpContext.Current.Request;
var authHeader = request.Headers["Authorization"];
if (authHeader == null) return;
var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);
if (authHeaderVal.Scheme.Equals(
"basic",
StringComparison.OrdinalIgnoreCase)
&& authHeaderVal.Parameter != null)
{
AuthenticateUser(authHeaderVal.Parameter);
}
}
private static void OnApplicationEndRequest(
object sender, EventArgs e)
{
var response = HttpContext.Current.Response;
if (response.StatusCode == 401)
{
//response.Headers.Add(
// "WWW-Authenticate",
// string.Format("Basic realm=\"{0}\"", Realm));
}
}
public void Dispose()
{
}
}
}
The most important part is inside CheckPassword method, there is where you should validate the credentials.
Another point is this line response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", Realm)); if you don't comment this line, the classic login requested form will show up, and if you do comment this line you have to catch the 401 error in your requests.
If you want to know about realm: What is the “realm” in basic authentication.
Plus, you will need to register the module in your web.config file:
<system.webServer>
<modules>
<add
name="BasicAuthenticationHttpModule"
type="YourSolution.WebApp.Modules.BasicAuthenticationHttpModule" />
</modules>
</system.webServer>
Then I've added these two methods to deal with the authentication token:
// u: username; p: password
CreateBasicAuthenticationToken = function (u, p) {
var t = u + ':' + p;
var hat = btoa(t);
window.sessionStorage.setItem('basicauthtoken', 'basic ' + hat);
};
DestroyBasicAuthenticationToken = function () {
window.sessionStorage.removeItem('basicauthtoken');
};
The btoa method: The btoa() method of window object is used to convert a given string to a encoded data (using base-64 encoding) string.. Taken from: http://www.w3resource.com/javascript/client-object-property-method/window-btoa.php.
And last I've added the authtoken to the request header using the beforeSend:
$.ajax({
type: 'GET',
url: 'your url',
beforeSend: function (xhr) {
window.sessionStorage.getItem('basicauthtoken');
}
}).done(function (data, textStatus, xhr) {
//...
});
Please do note using jQuery outside an angular directive is not recommended, AngularJS best practices dictates jQuery code must be always placed inside a directive.
Hope it helps.