So I set this above my Controller:
[Authorize(Roles="Administrator")]
The problem is whether they are not logged in, or don't have the right role, it redirects them to the login page. Is there a way to have it handle authorization and authenticate differently?
I might not understand you clearly, but authentication and authorization are always coming together.. One says which mechanism use to authenticate user (forms, windows etc.), and second which roles or users are allowed to see the content...
As far as authentication method is set in your web config it is fixed, and only think you can use to protect your controller methods is to put those attributes.
Also if you want to use it diffrently, f.e. redirect to diffrent page you can use following code:
public class RedirectAuthorizeAttribute : AuthorizeAttribute
{
public string RedirectUrl { get; set; }
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectResult(RedirectUrl);
}
}
and then put it onto your controller method like that:
[RedirectAuthorize(Roles = "MyRole", RedirectUrl = "SomeUrl")]
public ActionResult SomeAction()
{
...
}
Related
A page that asks the already signed in user to confirm their password one more time for security purposes on certain actions. Once confirmed it will go back to whatever request(action)they made in the first place. Should I use an user API for this? How can I achieve something like this?
Public IActionResult IndexMethod()
{
//process request only if user was verified using that verification page.
//It can take in parameters such as tokens if needed
}
In my opinion, if you want to confirm their password one more time for security purposes on certain actions. I suggest you could try to use action filter instead of directly going to the action and you could store the previous url into session.
More details, you could refer to below test demo:
1.Enable session:
Add below codes into Startup.cs's ConfigureServices method:
services.AddSession();
Add below codes into Configure method:
app.UseSession();
2.Create a filter:
public class ConfirmActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
base.OnActionExecuted(context);
}
public override void OnActionExecuting(ActionExecutingContext context)
{
//We will store the user is comfirmed into session and check it at the filter
if (String.IsNullOrEmpty(context.HttpContext.Session.GetString("checked")))
{
//store the path into session route .
context.HttpContext.Session.SetString("route", context.HttpContext.Request.Path);
//redirect to the confrim controller action
context.Result = new RedirectToActionResult("Index", "Confirm", context.HttpContext.Request.RouteValues);
}
}
}
3.Add confirm controller:
public class ConfirmController : Controller
{
public IActionResult Index()
{
//You could get the path
HttpContext.Session.SetString("checked","true");
return View();
}
public IActionResult Checked() {
// redirect to the path user has accessed.
var re = HttpContext.Session.GetString("route");
return new RedirectResult(re);
}
}
filter usage:
[ConfirmActionFilter]
public class HomeController : Controller
Result:
If the user access firstly, you will find it will go to the confirm method.
As recommend I would have register the authorize callback url/redirect_url at IdP, which it works.
But what if a client using MVC app tries to access a page with an unauthorized state, will be redirect to idsrv login page.
The redirect_url is always (Home page entry point) as configured.
To change this behavior I would have to register all possible routes at IdP.
That can not a be solution!
On idsrv Login method I have tried:
Login(string returnUrl)
checking the value from returnUrl it gives /connect/authorize/callback?client_id=...
Shouldn't returnUrl have the url of the previous page? Like in a normal mvc app has..
I have tried to get Referer store it on session and then redirect..
if (!string.IsNullOrEmpty(Request.Headers["Referer"].ToString()))
{
this.httpContextAccessor.HttpContext.Session.SetString("Referer", Request.Headers["Referer"].ToString());
}
But that doesn't work Referer comes null...
I have checked what's coming on context from interation services
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
context.RedirectUri
And returns /signin-oidc/ this is the automated way for returning (Home page entry point).
Any chance to get the previous url, so that the user can be redirect?
So what can I do else?
I'm using Hybrid flow to manage the following clients : mvc-app, classic-asp, web api
Here's an example of implementation allowing you to achieve what you want. Keep in mind that there's other ways of doing it.
All the code goes on your client, the server never knows anything about the end url.
First, you want to create a custom attribute that will be decorating all your actions/controllers that you want to protect:
using System;
using System.Web.Mvc;
namespace MyApp
{
internal class MyCustomAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.Result is HttpUnauthorizedResult)
{
filterContext.RequestContext.HttpContext.Session["oidc-returnUrl"] = filterContext.RequestContext.HttpContext.Request.UrlReferrer?.PathAndQuery;
}
}
}
}
And then you are going to create a login route/action that will handle all your authorize requests:
using System.Web.Mvc;
namespace MyApp
{
public class AccountController : Controller
{
[MyCustomAuthorize]
public ActionResult Login()
{
returnUrl = Session["oidc-returnUrl"]?.ToString();
// clean up
Session["oidc-returnUrl"] = null;
return Redirect(returnUrl ?? "/");
}
}
}
The login path can be changed in your startup code:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
LoginPath = "/my-login"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
// setting up your client
});
}
}
I have created a custom attribute that I would like to decorate my api controller from within my ASPNETCORE angular application. I am able to set up my authentication as required and log into the application from the login. Then I decorate my api method with my custom attribute.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false)]
public class ManageAuditAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public List<Claim> Claims { get; set; }
public ManageAuditAttribute(String feature)
{
Feature = feature;
}
public bool IsAuthorized()
{
// TODO check there is a claim for the given feature
}
private String Feature { get; }
public void OnAuthorization(AuthorizationFilterContext context)
{
var accessor = (IHttpContextAccessor)context.HttpContext.RequestServices.GetService(typeof(IHttpContextAccessor));
var name = context.HttpContext.User.FindFirst(ClaimTypes.Email); //NULL
var user = context.HttpContext.User; // NULL
var userName = Thread.CurrentPrincipal.Identity.Name; // NULL
}
}
Before saying that claims are not used this way I would add that I need to fit this into a legacy system that has a list of allowed features. I am adding those features to the user as claims and checking the claim exists for each user. The value for the actual claim is the name of the application that the user needs the claim for.
I could easily add these as a custom list to my custom identity user which might be more fitting however, I still need to access my user.
I can get the user but the name is always null and my claims list is empty as well. It is as if I am not logged in at all. Even after logging in.
For retrieving name, you should pass ClaimTypes.Name instead of ClaimTypes.Email like
var user = context.HttpContext.User.FindFirst(ClaimTypes.Name);
Or, you could retrieve by HttpContext.User.Identity.Name like
var userName = context.HttpContext.User.Identity.Name;
I have solved you problem by adding:
app.UseAuthentication();
in Startup.cs.
I am working on an MVC site that has some pages that need authentication and others that don't. This is determined using the Authorize and AllowAnonymous attributes in a pretty standard way. If they try to access something restricted they get redirected to the login page.
I'm now wanting to add the functionality to automatically log them in using an encrypted token passed in the querystring (the link will be in emails sent out). So the workflow I want now is that if a request goes to a page that is restricted and there is a login token in the querystring I want it to use that token to log in. If it logs in successfully then I want it to run the original page requested with the new logged in context. If it fails to log in then it will redirect to a custom error page.
My question is where would I need to insert this logic into the site?
I have seen some suggestions on subclassing the Authorize attribute and overriding some of the methods but I'm not 100% sure how to go about this (eg what I would override and what I'd do in those overridden methods.
I've also had a look at putting the logic at a controller level but I am led to understand that the authorize attribute would redirect it away from the controller before any code in the controller itself was run.
It would be better to write a custom authorization attribute that will entirely replace the default functionality and check for the query string parameter and if present, decrypt it and authenticate the user. If you are using FormsAuthentication that would be to call the FormsAuthentication.SetAuthCookie method. Something along the lines of:
public class TokenAuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
string token = filterContext.HttpContext.Request["token"];
IPrincipal user = this.GetUserFromToken(token);
if (user == null)
{
this.HandleUnAuthorizedRequest(filterContext);
}
else
{
FormsAuthentication.SetAuthCookie(user.Identity.Name, false);
filterContext.HttpContext.User = user;
}
}
private IPrincipal GetUserFromToken(string token)
{
// Here you could put your custom logic to decrypt the token and
// extract the associated user from it
throw new NotImplementedException();
}
private void HandleUnAuthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new ViewResult
{
ViewName = "~/Views/Shared/CustomError.cshtml",
};
}
}
and then you could decorate your action with this attribute:
[TokenAuthorize]
public ActionResult ProcessEmail(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Home");
}
Here is my code:
[HttpGet, Authorize(Roles = "Admin")]
public ActionResult ActivityLog()
{
'code to do stuff
return View(model);
}
It's pretty simple - if you are in the "Admin" role you can get into this action. However I have a custom ActionFilter that populates my IPrinciple with all the custom claims (I cant use ADFS to send the claims because I have ONE ADFS for multiple sites so my claims have to be for that specific site).
public class CustomFilter: ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
'go get custom claims
}
}
}
I tie the custom filter into the application from the Global.asax file
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomFilter());
}
The problem is since the Authorize attribute runs before my custom filter I don't have the
"Admin" role and i get a 401 - Unauthorized Access error. How do I still keep the filter AND use the "Roles" tag in the Authorize attribute?
In regards to
"The problem is since the Authorize attribute runs before my custom
filter I don't have the "Admin" role"
You can create a another Authorize attribute which access the claims first, and then your standard Authorization which sets up the Admin.
The way you do this is to register and specify the Order property
filters.Add(new AuthorizeAttribute(), 1);
filters.Add(new CustomAuthorizeAttribute(), 2);
See more information on Filter Ordering
AuthorizeAttribute is no longer supported in MVC 4. You should now use the new AllowAnonymous attribute.
http://blogs.msdn.com/b/rickandy/archive/2012/03/23/securing-your-asp-net-mvc-4-app-and-the-new-allowanonymous-attribute.aspx
ASP.NET MVC 4 includes the new AllowAnonymous attribute, you no longer need to write that code. Setting the AuthorizeAttribute globally in global.asax and then whitelisting (That is, explicitly decorating the method with the AllowAnonymous attribute) the methods you want to opt out of authorization is considered a best practice in securing your action methods.
If you attempt to use an action filter and override AuthorizeCore, you'll get a compile time error "There is no suitable method for override".
Here is another method of performing attribute authorization in MVC 4:
public class AuthAttribute : AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
HandleUnauthorizedRequest(actionContext);
}
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var response = actionContext.Request.CreateResponse(System.Net.HttpStatusCode.Redirect);
response.Headers.Add("Location", "http://www.google.com");
actionContext.Response = response;
}
}