How to properly log in a user from Owin Middleware - asp.net-mvc

I'm making my own system to authenticate jwt tokens in certain scenarios.
When I have properly validated the token, I have
var userIdentity = await user.CreateIdentityAsync(DefaultAuthenticationTypes.ExternalBearer);
owinContext.Authentication.User = new System.Security.Claims.ClaimsPrincipal(userIdentity);
owinContext.Authentication.SignIn(userIdentity);
System.Web.HttpContext.Current.User = owinContext.Authentication.User;
await next()
but that doesn't seem to fix authentication which still fails at - I believe - the Asp.Net Mvc level. Because I know it uses HttpContext I try adding this before calling next()
HttpContext.Current.User = new GenericPrincipal(userIdentity, new string[0]);
This gets me further along but I still seem to be getting an an authorization error it would seem (by searching source for the message that I get and where its used) to be coming from the Web Api [Authorize] attribute.
I'm hitting a wall as far as tracing through the .net source code. The only way I should be getting this message is if IsAuthorized returns false. But there are no roles nor users specified (it's just plain [Authorize]) and before heading off to the next() I can stop the debugger and check that yes there is a user identity, and yes it IsAuthorized.
I've overridden the AuthorizeAttribute so as to place breakpoints and can see that by the time it is called however, my actionContext is associated with a completely different identity with IsAuthorized == false. Which in turn makes me wonder if I'm signing in the user identity wrong
So... am I doing this correctly? What should I be doing?

I have never undertstood why but in my case, i have need to valid the ticket after signing in:
var userIdentity = await user.CreateIdentityAsync(DefaultAuthenticationTypes.ExternalBearer);
ctx.Authentication.SignIn(userIdentity);
AuthenticationTicket ticket = new AuthenticationTicket(userIdentity, null);
ctx.Validated(ticket);
Edit
I'm not really in the same context. In my case, I have a custom authentication provider inheriting of Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationProvider :
public class CustomBearerAuthenticationProvider:OAuthBearerAuthenticationProvider
{
public CustomBearerAuthenticationProvider() : base()
{
this.OnValidateIdentity = (context) => Task.Run(() =>
{
var identity = this.CreateApplicationIdentity(user);
context.OwinContext.Authentication.SignIn(identity);
AuthenticationTicket ticket = new AuthenticationTicket(identity, null);
context.Validated(ticket);
});
}
}
context is of type : Microsoft.Owin.Security.OAuth.OAuthValidateIdentityContext

Related

Problem with Claims in ASP.NET Core (user still have some claim after updating database)

I'm learning asp.net core and I'm stuck. I'll try to explain somehow.
Problem is with user claim.
When I log in into website, user have, let's say Create and Delete Claim and all working.
But when THAT user changes that HE cannot Delete something, after updating database HE still can.
In database Delete is gone (which is good).
Authorization is checked by authorization attribute:
[Authorize(Policy="DeletePolicy")]
I found something what may help.
ClaimsPrincipal User
property still have both claims (Create and Delete), but when I check DB:
await userManager.GetClaimsAsync("user_id")
I get only Create Claim which is good.
My question is: what precisely is that ClaimsPrincipal User property and why is not updated automatically?
Do I need to update User's Claims manually?
Claims are embedded in authentication cookie. It's like a snapshot of user's claims at a moment when user was signed in. Instead of putting all claims into cookie you can create ExtraClaimsMiddleware and put it after authentication middleware:
public class ExtraClaimsMiddleware
{
private readonly RequestDelegate _next;
public ExtraClaimsMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context,
YourDb db)
{
//get additional claims for the current user from database, cache it if you want
var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
var extraClaims = await db.GetExtraClaimsAsync(userId);
foreach (var claim in extraClaims)
{
context.User.Identities.First().AddClaim(new Claim(claim.ClaimType, claim.ClaimValue));
}
await _next(context);
}
}
public static class ExtraClaimsMiddlewareExtensions
{
public static IApplicationBuilder UseExtraClaims(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ExtraClaimsMiddleware>();
}
}
Then in Startup.cs:
app.UseAuthentication();
app.UseExtraClaims();
I am not sure if I understand your problem correctly but I guess it can be solved with the SignInManager Class.
After you deleted the claim from the database you should be able to use the “RefreshSignInAsync”-Method to update the user´s cookie.

User Principal in Entity Framework Core DbContext

I am upgrading a project from .NET 4.6 to .NET Core. It is an ASP.NET MVC website with a WebAPI that uses EntityFramework. When the a (MVC or WebAPI) Controller fires up the DbContext, there is code that needs to identity the user as a ClaimsIdentity to inspect their claims. In previous .NET, this was most reliably available on Thread.CurrentPrincipal like this:
ClaimsIdentity identity = System.Threading.Thread.CurrentPrincipal.Identity as ClaimsIdentity;
IIRC, this was the safest way to do it since you could be coming from different contexts - WebAPI or ASP.NET MVC.
In the .NET core solution, I have tried to Dependency Inject an IHttpContextAccessor into the constructor, but the User on HttpContext is not authorized and has no claims
ClaimsIdentity identity = httpContext.HttpContext.User.Identity;
// identity.IsAuthenticated == false. identity.Claims is empty.
Security is wired up in Startup.cs:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).
AddCookie(options =>
{
options.LoginPath = "/Login";
options.Cookie.HttpOnly = true;
}).
AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
var key = Configuration["Tokens:Key"];
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
};
});
The user logins on a /Login MVC view page, which logs in via Cookies and also generates a Bearer token in another request that is saved on the client. After all this the user is redirected to the homepage.
Cookie Login:
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = bIsPersistent });
Token Generation (called from ajax, saved to localstorage before redirection)
var secretKey = Configuration["Tokens:Key"];
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
var creds = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256 );
var expires = DateTime.Now.AddHours(8);
var token = new JwtSecurityToken(
_config["Tokens:Issuer"],
_config["Tokens:Issuer"],
oAuthIdentity.Claims,
expires: expires,
signingCredentials: creds
);
ret = Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
After landing on the homepage, an ajax call is made to the WebApi with the bearer token (I pulled the bearer token out of the http request and verified the signature on jwt.io), and the webapi causes the DbContext to be instantiated, and this is where the identity is not valid.
It's as if the Identity is not properly marshalled over to the DbContext -
How to I get the correct User or Identity in the DbContext?
Additionally, at the point I need it is in the DbContext construction, which I don't have alot of control over with the Dependency Injection. But I need to get this info basically from a default constructor or lazy load it somehow.
With your setup, you have two authentications setup. So, in your ConfigureServices function in Startup class, you need to use something like the following:
services.AddAuthentication().AddCookie().AddJwtBearer();
Don't forget to specify a default authentication. For instance, if you want the authentication to be cookies by default, you can use this:
services.AddAuthentication("Cookies").AddCookie().AddJwtBearer();
Or to keep the code safer,
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie().AddJwtBearer();
In your startup class, in the Configure function, don't forget to add
app.UseAuthentication();
When authenticating within a controller, you will need to use the scheme name along with the [Authorize] if you are not using the default scheme.
[Authorize(AuthenticationSchemes = "")]

How to implement OAuth2 for a single tool, without using it as my application's authorization solution

I currently have a MVC site, in .NET Core, backed by a public API. My users must log in (there are no [Anonymous] controllers), and authentication is already successfully being done using the DotNetCore.Authentication provider. All that is well and good.
What I'm now trying to do (by user request) is implement functionality for a user to read and view their Outlook 365 calendar on a page within my site. It doesn't seem too hard on the surface... all I have to do is have them authenticate through microsoftonline with my registered app, and then -- once they have given approval -- redirect back to my app to view their calendar events that I am now able to pull (probably using Graph).
In principle that seems really easy and straightforward. My confusion comes from not being able to implement authentication for a single controller, and not for the entire site. All of the OAuth2 (or OpenID, or OWIN, or whatever your flavor) examples I can find online -- of which there are countless dozens -- all want to use the authorization to control the User.Identity for the whole site. I don't want to change my sitewide authentication protocol; I don't want to add anything to Startup.cs; I don't want anything to scope outside of the one single controller.
tldr; Is there a way to just call https://login.microsoftonline.com/common/oauth2/v2.0/authorize (or facebook, or google, or whatever), and get back a code or token that I can use for that user on that area of the site, and not have it take over the authentication that is already in place for the rest of the site?
For anybody else who is looking for this answer, I've figured out (after much trial and error) how to authenticate for a single user just for a short time, without using middleware that authenticates for the entire application.
public async Task<IActionResult> OfficeRedirectMethod()
{
Uri loginRedirectUri = new Uri(Url.Action(nameof(OfficeAuthorize), "MyApp", null, Request.Scheme));
var azureADAuthority = #"https://login.microsoftonline.com/common";
// Generate the parameterized URL for Azure login.
var authContext = GetProviderContext();
Uri authUri = await authContext.GetAuthorizationRequestUrlAsync(_scopes, loginRedirectUri.ToString(), null, null, null, azureADAuthority);
// Redirect the browser to the login page, then come back to the Authorize method below.
return Redirect(authUri.ToString());
}
public async Task<IActionResult> OfficeAuthorize()
{
var code = Request.Query["code"].ToString();
try
{
// Trade the code for a token.
var authContext = GetProviderContext();
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(code, _scopes);
// do whatever with the authResult, here
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.ToString());
}
return View();
}
public ConfidentialClientApplication GetContext()
{
var clientId = "OfficeClientId;
var clientSecret = "OfficeClientSecret";
var loginRedirectUri = new Uri(#"MyRedirectUri");
TokenCache tokenCache = new MSALSessionCache().GetMsalCacheInstance();
return new ConfidentialClientApplication(
clientId,
loginRedirectUri.ToString(),
new ClientCredential(clientSecret),
tokenCache,
null);
}
I don't know if that will ever be helpful to anybody but me; I just know that it's a problem that doesn't seem to be easily solved by a quick search.

What's the purpose of this SignInManager call when the controller has the [Authorize] attribute?

Having set up a default ASP.Net MVC 5 application, I fail to understand why the below snippet has a call to SignInManager.
This is in the ManageController, which has the [Authorize] attribute.
//
// POST: /Manage/RemoveLogin
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> RemoveLogin(string loginProvider, string providerKey)
{
ManageMessageId? message;
var result = await UserManager.Get().RemoveLoginAsync(User.Identity.GetUserId<int>(), new UserLoginInfo(loginProvider, providerKey));
if (result.Succeeded)
{
var user = await UserManager.Get().FindByIdAsync(User.Identity.GetUserId<int>());
if (user != null)
{
await SignInManager.Get().SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
message = ManageMessageId.RemoveLoginSuccess;
}
else
{
message = ManageMessageId.Error;
}
return RedirectToAction("ManageLogins", new { Message = message });
}
I am wondering if, whenver I retrieve the authenticated user, I should repeat this step, i.e. check if the user is null and if not, await SignInAsync.
Edit: check if the user is null and if it is, await SignInAsync
Now, I've created a new controller, that I've given the [Authorize] attribute, and in the Index() function of the controller, I do:
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId<int>());
If I load that page in two tabs, sign out in one of them and then refresh the page, I'm redirected to the login screen. I've attached a debugger and have been unable to cause a case where the SignInManager is hit.
In what scenario would the user be not-null?
They do entirely different things. The Authorize attribute checks that the user is authenticated and authorized to access the action (based on role permissions and such). It doesn't authenticate the user. That's what the call to SignInManager is for. It actually authenticates the user, so that the user can then pass checks made by the Authorize attribute.
As for when the user might be null, effectively, if the action is protected by Authorize it probably never will be. It would have to take some strange confluence of events where the user was found in the database in order to sign them in, but then somehow was removed by the time you tried to retrieve it. In other words: not bloody likely. Still, it's good practice to always perform a null-check like this when a value could potentially be null, which user could.

Manually validating a password reset token in ASP.NET Identity

I would like to manually validate a password reset token in ASP.NET Identity 2.0. I'm trying to create my own version of UserManager.ResetPasswordAsync(string userId, string token, string newPassword) that takes and IdentityUser instead of userId like this:
UserManager.ResetPasswordAsync(IdentityUser user, string token, string newPassword)
Not sure if I am doing this right, but here I am attempting to validate the code that was emailed to the user in an earlier step. I have not modified the code/token that sends the email to the user and generates the code. I am assuming this is the correct method to call, but the purpose argument is incorrect. (I tried passing "ASP.NET Identity" but no dice.)
if (await userManager.UserTokenProvider.ValidateAsync(purpose: "?", token: code, manager: userManager, user: user))
{
return IdentityResult.Success;
}
else
{
return new IdentityResult("Invalid code.");
}
If someone could fill me in on the details of how it works out of the box, or point me at Microsoft's source code for UserManager.ResetPasswordAsync(IdentityUser user, string token, string newPassword) that would be most appreciated!
I overcame my problem by setting the purpose to "ResetPassword".
Below is a snippet of the final result in case someone wants to do something similar. It is a method in my ApplicationUserManager class. Realize, though, that some of the exception handling that Microsoft implements is missing or not localized because certain private variables, methods, and resources used in their code are inaccessible. It's unfortunate they did not make that stuff protected so that I could have gotten at it. The missing ThrowIfDisposed method call in particular is interesting (and bazaar) to me. Apparently they are anticipating method calls after an instance has been disposed in order to provide a friendlier error message and avoid the unexpected.
public async Task<IdentityResult> ResetPasswordAsync(IdentityUser user,
string token, string newPassword)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
// Make sure the token is valid and the stamp matches.
if (!await UserTokenProvider.ValidateAsync("ResetPassword", token,
this, user))
{
return IdentityResult.Failed("Invalid token.");
}
// Make sure the new password is valid.
var result = await PasswordValidator.ValidateAsync(newPassword)
.ConfigureAwait(false);
if (!result.Succeeded)
{
return result;
}
// Update the password hash and invalidate the current security stamp.
user.PasswordHash = PasswordHasher.HashPassword(newPassword);
user.SecurityStamp = Guid.NewGuid().ToString();
// Save the user and return the outcome.
return await UpdateAsync(user).ConfigureAwait(false);
}
It appears that the code for Microsoft.AspNet.Identity has not been Open Sourced according to the Codeplex repository located at:
https://aspnetidentity.codeplex.com/SourceControl/latest#Readme.markdown
At present, the ASP.NET Identity framework code is not public and
therefore will not be published on this site. However, we are planning
to change that, and as soon as we are able, the code will be published
in this repository.
However I did find this which might be the source for the UserManager based on the debug symbols:
UserManager Source Code
I also found these posts which might help:
Implementing custom password policy using ASP.NET Identity
UserManager Class Documentation
IUserTokenProvider Interface Documentation

Resources