Authenticate Attribute for MVC: ExecuteServiceStackFiltersAttribute: SessionFeature not present in time to set AuthSession? - servicestack

I'm trying to create a simple Credentials Auth using OrmLiteAuthRepository(Postgres) and Memcached as caching layer on Mono 3.2.x / Ubuntu 12.04 in an MVC Application - I am using ServiceStack libraries version 4.0x
I am using a custom session object, adapted from ServiceStack's SocialBootstrap example
What works perfectly:
Getting the session inside a controller action, such as:
var currentSession = base.SessionAs<MyCustomUserSession>();
However, I don't want to check / validate the session and what may or may not be inside it in the action code, I would like to use an attribute, and this leads me to:
What does not work: Using the Authenticate attribute above the action name:
My problem (null AuthSession) shows up when trying to utilize the [Authenticate] attribute on an MVC action.
[Authenticate]
public ActionResult Index()
{
return View();
}
I have managed to narrow it down to the fact that ExecuteServiceStackFiltersAttribute executes this code, but it appears the AuthSession has not yet been made available by the SessionFeature - so the AuthSession will always be null at this point:
var authAttrs = GetActionAndControllerAttributes<AuthenticateAttribute>(filterContext);
if (authAttrs.Count > 0 && ( ssController.AuthSession==null || !ssController.AuthSession.IsAuthenticated))
{
filterContext.Result = ssController.AuthenticationErrorResult;
return;
}
If, for example I override the AuthenticationErrorResult and try to throw an exception if I manually initialize the session from the SessionFeature, it will throw the "there is life in the session" exception (of course, when I logged in with a valid user):
public override ActionResult AuthenticationErrorResult
{
get
{
if (AuthSession == null)
{
// the Authenticate filter is triggered by ExecuteServiceStackFilters attribute
// which seems to always have AuthSession null
var session = SessionFeature.GetOrCreateSession<MyCustomUserSession>(AuthService.Cache);
if (session == null || (session != null && session.IsAuthenticated == false))
{
throw new Exception("Hmmm...dead as a dodo");
}
else
{
throw new Exception("there is life in the session:" + session.UserName);
}
}
var returnUrl = HttpContext.Request.Url.PathAndQuery;
return new RedirectResult(LoginRedirectUrl.Fmt(HttpUtility.UrlEncode(returnUrl)));
}
}
Aside from creating my custom attributes / filters, is there a solution I should try (properties to set) with the incumbent ServiceStack codebase? If I'm missing something, please let me know.
My regards for a great project in any case.

My problem (null AuthSession) shows up when trying to utilize the [Authenticate] attribute on an MVC action.
Are you getting an Exception or are you just getting redirected to the 'Login' page? If you are not getting an Exception and just be redirected because you're not authenticated, the below may work. Also, are you implementing your own Custom Authentication Provider? If so, could you post a sample of it?
I don't think you have it in your code samples but I think your MVC Controller code is probably something like...
public class SomeController : ServiceStackController
{
[Authenticate]
public ActionResult Index()
{
return View();
}
}
Can you try adding your custom MyCustomUserSession to the Type of the ServiceStackController making it...
public class SomeController : ServiceStackController<MyCustomUserSession>
{
[Authenticate]
public ActionResult Index()
{
return View();
}
}

Related

Web API 2 Basic Authentication and allow actions not marked [Authorize]

I have been looking at Basic Authentication in Web Api2 and don’t seem to find an explanation for something I am confused about.
I created a web api application project with individual authentication in Visual studio 2017.
I have the default code
public class ValuesController : ApiController
{
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
public string Get(int id)
{
return "value";
}
}
I call these actions via postman, browser etc all good.
If I add the [Authorize] attribute to one of the methods I get 401 unauthorized response as expected.
So far so good.
I then add basic authentication by creating a class derived from AuthorizationFilterAttribute
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var authHeader = actionContext.Request.Headers.Authorization;
if (authHeader != null)
{
var authenticationToken = actionContext.Request.Headers.Authorization.Parameter;
var decodedAuthenticationToken = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationToken));
var usernamePasswordArray = decodedAuthenticationToken.Split(':');
var userName = usernamePasswordArray[0];
var password = usernamePasswordArray[1];
var isValid = userName == "ade" && password == "password";
if (isValid)
{
var principal = new GenericPrincipal(new GenericIdentity(userName), null);
HttpContext.Current.User = principal;
return;
}
}
}
HandleUnathorized(actionContext);
}
private static void HandleUnathorized(HttpActionContext actionContext)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", "Basic Scheme='Data' location = 'http://localhost:");
}
I register the filter in WebApiConfig.cs
config.Filters.Add(new BasicAuthenticationAttribute());
I use postman to call the action marked with [Authorize] and send with header Authorization: Basic YWRlOnBhc3N3b3Jk
The request is authorized and I get my action response. All good.
Now I call the action that is not marked with [Authorize] without a Authorization header from postman expecting to get a response but the OnAuthorization is called and obviously returns HandleUnathorized(actionContext); I only expected the OnAuthorization method to be called where an action is marked with [Authorize]
So now I am thinking what is the point of the [Authorize] attribute because OnAuthorization is called regardless so what is the point of marking actions [Authorize] attribute?
Secondly, I added the following method to my class
private static bool SkipAuthorization(HttpActionContext actionContext)
{
Contract.Assert(actionContext != null);
return actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any()
|| actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any();
}
I call this method at the beginning of OnAuthorization
if (SkipAuthorization(actionContext)) return;
If I mark my actions with [AllowAnonymous] it works.
If there is no [Authorize] attribute on the controller or specific actions then surely the OnAuthorization should also be skipped?
I just don't see the point of using [Authorize], I am clearly missing something here, am I doing something wrong or do I need to mark the actions with [AllowAnonymous] to exclude them.
If you are using [Authorize] attribute and windows authentication, then authorization will done automatically, you don't need to do any special configuration, but any special case if you need to override [Authorize] class then your class is like below,
Instead of inheriting AuthorizationFilterAttribute, you can
inherit AuthorizeAttribute
public class BasicAuthenticationAttribute : AuthorizeAttribute
{
//your override methods
}
Instead of using [Authorize] attribute, use your derived class name. In your case use [BasicAuthenticationAttribute], not [Authorize]
Thanks Fran you set me off on the right path.
I commented out the following line
config.Filters.Add(new BasicAuthenticationAttribute());
I used the following attributes in controller
public class ValuesController : ApiController
{
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[Authorize]
[BasicAuthentication]
public string Get(int id)
{
return "value";
}
}
If I called the action get() I got a response, OnAuthorisation was not called.
If I call get(int id) I get 401 Unauthorised and OnAuthorisation is not called.
I removed the [Authorize] from get(int id) action
[BasicAuthentication]
public string Get(int id)
{
return "value";
}
and it all worked, OnAuthorisation was called as expected.

SwaggerRequestExample attribute does not work in ASP.NET MVC 5 (.NET Framework 4.5.2)

I am toying with Swashbuckle.Examples package (3.10.0) in an ASP.NET MVC project. However, I cannot make request examples appear within the UI.
Configuration (SwaggerConfig.cs)
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c => {
c.SingleApiVersion("v1", "TestApp.Web");
c.IncludeXmlComments(string.Format(#"{0}\bin\TestApp.Web.xml", System.AppDomain.CurrentDomain.BaseDirectory));
c.OperationFilter<ExamplesOperationFilter>();
c.OperationFilter<DescriptionOperationFilter>();
c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
})
.EnableSwaggerUi(c => { });
}
Request example classes
public class EchoRequestExample : IExamplesProvider
{
public object GetExamples()
{
return new EchoInput { Value = 7 } ;
}
}
public class EchoInput
{
public int Value { get; set; }
}
Action
[HttpGet]
[Route("Echo")]
[CustomApiAuthorize]
[SwaggerRequestExample(typeof(EchoInput), typeof(EchoRequestExample))]
[ResponseType(typeof(EchoServiceModel))]
public HttpResponseMessage Echo([FromUri] EchoInput model)
{
var ret = new EchoServiceModel
{
Username = RequestContext.Principal.Identity.Name,
Value = value
};
return Request.CreateResponse(HttpStatusCode.OK, ret);
}
Swagger UI shows xml comments and output metadata (model and an example containing default values), but shows no request example. I attached to process and EchoRequestExample.GetExamples is not hit.
Question: How to make SwaggerRequestExample attribute work in ASP.NET MVC 5?
Note: Windows identity is used for authorization.
I received an answer from library owner here:
Swagger request examples can only set on [HttpPost] actions
It is not clear if this is a design choice or just a limitation, as I find [HttpGet] examples also relevant.
I know the feeling, lot's of overhead just for an example, I struggle with this for a while, so I created my own fork of swashbuckle, and after unsuccessful attempts to merge my ideas I ended up detaching and renaming my project and pushed to nuget, here it is: Swagger-Net
An example like that will be:
[SwaggerExample("id", "123456")]
public IHttpActionResult GetById(int id)
{
Here the full code for that: Swagger_Test/Controllers/IHttpActionResultController.cs#L26
Wondering how that looks like on the Swagger-UI, here it is:
http://swagger-net-test.azurewebsites.net/swagger/ui/index?filter=IHttpActionResult#/IHttpActionResult/IHttpActionResult_GetById

Custom Authentication mechanism in ASP.Net Core

I need to authenticate my users using an external API from the login page. If the authentication from the external API succeed then I store at the session a AuthToken.
To check if the request is valid I have created the following Authorization Handler
public class ExtApiStoreRequirement : IAuthorizationRequirement
{
}
public class ExtApiAuthorizationHandler : AuthorizationHandler<ExtApiStoreRequirement>
{
IHttpContextAccessor _accessor;
public ExtApiAuthorizationHandler(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ExtApiStoreRequirement requirement)
{
var authState = GET_AUTH_FROM_SESSION(_accessor.HttpContext.Session);
if (authState!=null)
{
_accessor.HttpContext.Response.Redirect("/Account/Login");
//context.Fail(); <-- I removed that because it was responding an empty page
context.Succeed(requirement);
}
else
context.Succeed(requirement);
return Task.CompletedTask;
}
}
And I have registered this handler at my startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("ExtApi",
policy => policy.Requirements.Add(new ExtApiStoreRequirement()));
});
This approach is working but I don't feel confident because I have to call context.Succeed(requirement); for the redirection to work. If I call context.Fail() then no redirection takes place and all I see is an empty page.
Is there any security issue with this approach or I will be safe using it?
Your implementation is for authorization not authentication. I think instead of creating an authorization policy, writing custom authentication middleware would be right way for your case.
First see how to implement custom authentication Simple token based authentication/authorization in asp.net core for Mongodb datastore
To implement above way for your case HandleAuthenticateAsync should be something like below:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
AuthenticateResult result = null;
var principal = GetPrincipalFromSession();
if(principal != null)
{
result = AuthenticateResult.Success(new AuthenticationTicket(principal,
new AuthenticationProperties(), Options.AuthenticationScheme));
}
else
{
result = AuthenticateResult.Skip();
}
return result;
}
Update based on comment:
protected override async Task<bool> HandleUnauthorizedAsync(ChallengeContext context)
{
Response.Redirect(Options.LoginPath);// you need to define LoginPath
return true;
}
Also you should store principal in session when user signs in.

Attribute Routing with BaseClass implementation: Correcltly listed but still fails

I've set attribute routing on a controller class which inherits a base class where I handle I18N culture set/selection logic (as described in article ASP.NET MVC 5 Internationalization) but that process fails, although route seemed to be set correctly.
[RoutePrefix("{culture}")]
public class HomeController : BaseController
{
public ActionResult Index()
{
return View();
}
[Route("Hakkimda")]
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
When I try to get to link I see grey screen of death on browser with this on address bar:
http://localhost:53530/tr-tr/Hakkimda?MS_DirectRouteMatches=System.Collections.Generic.List%601%5BSystem.Web.Routing.RouteData%5D
I believe the problem is the way base controller implements I18N logic which is based on BeginExecuteCore overloading.
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
string cultureName = RouteData.Values["culture"] as string;
// Attempt to read the culture cookie from Request
if (cultureName == null)
cultureName = Request.UserLanguages != null && Request.UserLanguages.Length > 0 ? Request.UserLanguages[0] : null; // obtain it from HTTP header AcceptLanguages
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
if (RouteData.Values["culture"] as string != cultureName) {
// Force a valid culture in the URL
RouteData.Values["culture"] = cultureName.ToLowerInvariant(); // lower case too
// Redirect user
Response.RedirectToRoute(RouteData.Values);
}
// Modify current thread's cultures
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
return base.BeginExecuteCore(callback, state);
}
Probably execution precedence of BeginExecuteCore and routing have some mismatch but my knowledge on both don't suffice to solve it.
I've seen this article(What’s New in ASP.NET MVC 5.2 : Attribute routing improvements) but example provided there was a bit different and because it's new there aren't other examples on the net.
mr-anton's answer will stop you getting rubbish in the address bar but It'll also stop the language changing.
I had this issue after a change from MVC5 to MVC5.2
This answer says it is a Microsoft issue
The workaround is to see if the route data is in a nested route key
var routeData = RouteData;
if (routeData.Values.ContainsKey("MS_DirectRouteMatches"))
{
routeData = ((IEnumerable<System.Web.Routing.RouteData>)routeData.Values["MS_DirectRouteMatches"]).First();
}
string cultureName = routeData.Values["culture"] as string;
And then it just works.
remove this code
if (RouteData.Values["culture"] as string != cultureName) {
// Force a valid culture in the URL
RouteData.Values["culture"] = cultureName.ToLowerInvariant(); // lower case too
// Redirect user
Response.RedirectToRoute(RouteData.Values);
}

How to get HttpContext in servicestack.net

I am unable to switch to all of service stack's new providers, authentication, etc. So, I am running a hybrid scenario and that works great.
To get the current user in service, I do this:
private IPrincipal CurrentUser()
{
var context = HttpContext.Current;
if (context != null)
{
var user = context.User;
if (user != null)
{
if (!user.Identity.IsAuthenticated)
return null;
return user;
}
}
return null;
}
Is there an alternative/better way to get the current http context directly from a service? I would prefer to not have to use the HttpContext.Current if I do not have to?
This is alternative way... It goes through ServiceStack to get the OriginalRequest which will be an ASP.NET request (though could be HttpListener HttpRequest if not used within ASP.NET application). Not sure if it's better but you no longer have HttpContext.Current within your Service code.
public class MyService : Service
{
public object Get(MyRequest request)
{
var originalRequest = this.Request.OriginalRequest as System.Web.HttpRequest;
var user = originalRequest.RequestContext.HttpContext.User;
// more code...
}
}

Resources