OWIN Authentication in MVC 5 Loops while User.IsAuthenticated = false - authentication

I'm trying to implement authentication and authorization in an MVC 5.1 app. The authentication takes place via Facebook that is custom implemented. (I can post that code if needed.) Once FB authenticates and sends back the code and the Authenticate method of the auth service is called to sign the user into the application. There is no auth code in the application itself (thus not using Identity or other membership services).
public async Task<ActionResult> Connect(string code)
{
if (code == null)
{
return RedirectToAction("Index", "Home");
}
else
{
// get access token
var accessToken = await nApplication.FacebookClient.AccessTokenAsync(code);
// get user info from facebook
var meResult = await nApplication.FacebookClient.MeResultAsync(accessToken);
nApplication.NRepository.SaveChanges();
nAuthorization.Authenticate(member);
return RedirectToAction("Index");
}
}
nAuthorization.Authenticate(member); creates a list of claims and executes OWIN SignIn,
claims.Add(new Claim(ClaimTypes.Name, member.Name));
claims.Add(new Claim(ClaimTypes.Role, "Member"));
var claimIdentity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
owinContext.Authentication.SignIn(new AuthenticationProperties { IsPersistent = true }, claimIdentity);
I'm using the Authorize attribute from Mvc namespace. But at this point /Profile/Authenticate/ which is my Owin LoginPath get's called again and again to redirect the user to FB and return to the Connect method above.
[Authorize(Roles = "Member")]
public async Task<ActionResult> Index(int? id)
I've checked the User property in the controller and it is not authenticated. I could set that to a new ClaimsPrincipal but I'd like the auth code to be independent of the HttpContext. And it doesn't seem to be right solution.
My Startup class contains:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
LoginPath = new PathString("/Profile/Authenticate/"),
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieSecure = CookieSecureOption.Always,
ReturnUrlParameter = "next"
});
Maybe I am missing something completely fundamental? Any pointers would help, I've looked through articles such as the following but to no avail:
http://brockallen.com/2013/10/24/a-primer-on-owin-cookie-authentication-middleware-for-the-asp-net-developer/
http://www.khalidabuhakmeh.com/asp-net-mvc-5-authentication-breakdown-part-deux

I think this will solve your problem...it worked for me:
Add an empty method in your global.asax.cs file:
protected void Session_Start()
{
}
for some reason, the asp.net session cookie does not get set at the proper time without this. The Thinktecture devs think this might be happening if your webapp uses http and your identity provider uses https but I have not verified that yet.

Related

ASP.NET Core 3.1 Microsoft Graph Access Token lost when application is restarted

Using ASP.NET Core 3.1 and Microsoft Graph API, when the application is restarted the Access Token is lost. The user is still logged in, but is unable to perform any Graph API calls.
I have tried the recommendations mentioned here Managing incremental consent and conditional access but was unable to refresh the token, and resume automatically.
Here is my Controller:
readonly ITokenAcquisition tokenAcquisition;
private GraphServiceClient graphServiceClient;
public HomeController(GraphServiceClient graphServiceClient, ITokenAcquisition tokenAcquisition)
{
this.graphServiceClient = graphServiceClient;
this.tokenAcquisition = tokenAcquisition;
}
[HttpGet]
[AuthorizeForScopes(Scopes = new string[] {"user.read"})]
public async Task<IActionResult> A()
{
User user;
try
{
var scopes = new string[] { "user.read" };
var accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
user = await graphServiceClient.Me.Request().GetAsync();
}
catch (MicrosoftIdentityWebChallengeUserException ex)
{
// token is invalid....
// the throw causes a redirect to the User Login Page
throw ex.MsalUiRequiredException;
}
user = await graphServiceClient.Me.Request().GetAsync();
Serilog.Log.Debug("{#User}", user);
return View();
}
In the code above, when the application is restarted the access token is
lost, and re-throwing the exception causes a redirect to the Login-Page.
If I then click the Sign-in with Microsoft button, the user is already signed-in,
there is no need to enter the credentials. If I then access the controller calling the Graph API, the API calls succeed.
How can I refresh the tokens before calling the API?
Also how can I debug this? If I set a breakpoint at throw ex.MsalUiRequiredException; It is of no use, I cannot see,
where the redirect get's it's value from.
The Annotation [AuthorizeForScopes(Scopes = new string[] {"user.read"})]
Is responsible for dealing with the redirect.
For debugging, use the AuthorizeForScopes source file.
In this specific case the AuthenticationScheme was not set to a value,
and was null.
Annotate the Action method to enforce a specific Scheme.
e.g. AuthenticationScheme=OpenIdConnectDefaults.AuthenticationScheme
[HttpGet]
[AuthorizeForScopes(Scopes = new string[] {"user.read"}, AuthenticationScheme=OpenIdConnectDefaults.AuthenticationScheme)]
public async Task<IActionResult> A()
{
// snipped
}
This resulted into the desired redirect path:
https://login.microsoftonline.com/{}/oauth2/v2.0/authorize?client_id={}

AspNetCore alternative for System.Web.Security.FormsAuthenticationTicket

We're using System.Web.Security.FormsAuthenticationTicket to create anonymous cookies for users that aren't logged in. Is there an equivalent in AspNetCore?
I'm well aware that ASP.NET Core cannot support forms authentication. The new way of doing things is cookies. So how to create a cookie that does the equivalent in the new situation?
Asp.net core cannot support form authentication. I recommend you use cookie-base authentication. This link can help you build it.
If you want to skip a method that requires authorized access. You can add attribute [AllowAnonymous].
[AllowAnonymous]
public IActionResult Privacy()
{
return View();
}
Or you can refer to this link.
Configure cookie in Startup.cs.
services.AddAuthentication("auth")
.AddCookie("auth",config=>
{
config.Cookie.Name = "cookie.name";
config.LoginPath = "/home/login";
});
Generate token in this action. You can fill the claim by receiving form data.
[HttpPost]
public IActionResult login()
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name,"myName"),
new Claim(ClaimTypes.Role,"myRole")
};
var claimIdentity = new ClaimsIdentity(claims,"id card");
var claimPrinciple = new ClaimsPrincipal(claimIdentity);
var authenticationProperty = new AuthenticationProperties
{
IsPersistent = true
};
HttpContext.SignInAsync(claimPrinciple,authenticationProperty);
return View();
}

How to simply authenticate a request in middleware?

I am developing a Web API in ASP.NET Core 2.2. I want to authenticate every request by any user at any time based on token stored in Authorization header of HTTP Request so the user can call controllers and actions which are annotated with [AuthorizeAttribute]. This is my middleware:
public class TokenBasedAuthenticationMiddleware
{
private RequestDelegate nextDelegate;
public TokenBasedAuthenticationMiddleware(RequestDelegate next) => nextDelegate = next;
public async Task Invoke(HttpContext httpContext, IRegistrationsRepository registrationsRepository)
{
if (registrationsRepository.IsAuthorized(httpContext.Request.Headers["Authorization"]))
{
//Code To Authenticate this request?
}
await nextDelegate.Invoke(httpContext);
}
}
How can I simply authenticate the request (i.e set HttpContext.User.Identity.IsAuthenticated to true) without going into any complexity?
You can try something like :
var claims = new[] { new Claim("name", "YourName"), new Claim(ClaimTypes.Role, "Admin") };
var identity = new ClaimsIdentity(claims, "JWT");
httpContext.User = new ClaimsPrincipal(identity);
await nextDelegate.Invoke(httpContext);
But you can directly use the AddJwtBearer extension if using JWT bearer token authentication . The JwtBearer middleware looks for tokens (JSON Web Tokens or JWTs) in the HTTP Authorization header of incoming requests. If a valid token is found, the request is authorized. You then add the [Authorize] attribute on your controllers or routes you want protected:
Related code samples :
https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide
https://garywoodfine.com/asp-net-core-2-2-jwt-authentication-tutorial/

What claim in ClaimsPrincipal should I fill to make Roles Authorization work in .Net Core 2.0?

I have this method to sign in a user using .Net Core Cookie Authentication.
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginVM loginModel)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, loginModel.UserName)
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
// TODO: Validate against DB Staff/User.
// If valid, sign in.
await HttpContext.SignInAsync(new ClaimsPrincipal(claimsIdentity));
return Redirect(loginModel?.ReturnUrl ?? "/");
// Else return View();
}
I am filling up ClaimTypes.Name with the user name being posted up to the login View Model.
Is there like a ClaimTypes.Roles value to fill up?
I need to be able to use "User.IsInRole(...)".
This would be a collection of roles of course for a user. Not just one role.
Does anyone know how to do this?
Add like this:
claims.Add(new Claim(ClaimTypes.Role, p.Role.RoleCd));

Re-challenge authenticated users in ASP.NET Core

I'm running into some issues with the authentication pipeline in ASP.NET Core. My scenario is that I want to issue a challenge to a user who is already authenticated using OpenID Connect and Azure AD. There are multiple scenarios where you'd want to do that, for example when requesting additional scopes in a AAD v2 endpoint scenario.
This works like a charm in ASP.NET MVC, but in ASP.NET Core MVC the user is being redirected to the Access Denied-page as configured in the cookie authentication middleware. (When the user is not logged in, issuing a challenge works as expected.)
After a couple of hours searching the web and trying different parameters for my middleware options, I'm beginning to suspect that either I'm missing something obvious, or this behavior is by design and I need to solve my requirement some other way. Anyone any ideas on this?
EDIT: the relevant parts of my Startup.cs look like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(
SharedOptions => SharedOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// <snip...>
app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme });
var options = new OpenIdConnectOptions
{
AuthenticationScheme = OpenIdConnectDefaults.AuthenticationScheme,
ClientId = ClientId,
Authority = Authority,
CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"],
ResponseType = OpenIdConnectResponseType.CodeIdToken,
PostLogoutRedirectUri = "https://localhost:44374/",
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false
}
};
options.Scope.Add("email");
options.Scope.Add("offline_access");
app.UseOpenIdConnectAuthentication(options);
}
And the Action looks like this:
public void RefreshSession()
{
HttpContext.Authentication.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" });
}
I found a hint and the solution here: https://github.com/aspnet/Security/issues/912.
ChallengeBehavior.Unauthorized is the "key".
This post gives the current (november 2016 - ASPNet 1.0.1) workaround: https://joonasw.net/view/azure-ad-b2c-with-aspnet-core
You'll need a new ActionResult to be able to call the AuthauticationManager.ChallengeAsync with the ChallengeBehavior.Unauthorized behavior.
Once the issue https://github.com/aspnet/Mvc/issues/5187 will be sucessfully closed, this should be integrated.
I tested it and it worked perfectly well (my goal was simply to extend Google scopes on a per user basis).
Try to sign out:
public void RefreshSession()
{
HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
HttpContext.Authentication.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
HttpContext.Authentication.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" });
}