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.
Related
I am developing a Standard .NET Web Api 2 with Angular 7 and I need to connect Azure Active Directory.
I have added this code:
public static void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = configurationManager.AadTenant,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = configurationManager.AadAudience,
},
});
}
Both my Tenant and Audience are correct. Everything works fine and the token is valid and exists in the request.
The problem is that IsAuthenticated is always false and when I looked inside the claims in the identity they were empty
protected override bool IsAuthorized(HttpActionContext actionContext)
{
return base.IsAuthorized(actionContext); // Always false
}
I don't know where the problem is. I have tried many links but none of them worked for me. Anybody knows why? Thanks
To protect your service, you can use IsAuthorize filter implementation something like below:
private static string trustedCallerClientId = ConfigurationManager.AppSettings["ida:TrustedCallerClientId"];
protected override bool IsAuthorized(HttpActionContext actionContext)
{
bool isAuthenticated = false;
try
{
string currentCallerClientId = ClaimsPrincipal.Current.FindFirst("appid").Value;
isAuthenticated = currentCallerClientId == trustedCallerClientId;
}
catch (Exception ex)
{
new CustomLogger().LogError(ex, "Invalid User");
isAuthenticated = false;
}
return isAuthenticated;
}
The principal is not taken from the current thread, but from the actionContext. So, what you must set is the principal in the request context of the action context:
actionContext.RequestContext.Principal = yourPrincipal;
I am assuming your action context.requestcontext does not have the right data, that''s why even though your request are succeeding but your attribute is always false.
Reference:
https://www.c-sharpcorner.com/article/azure-active-directory-authentication/
Hope it helps.
I've reviewed similar posts with solutions that do not work for me.
I have an MVC 5 site hosted in IIS 7.x that serves a web ui - https://www.example.com. Callers can also access api (Webapi 2.2) endpoints to perform certain functions - https://www.example.com/api/x. Some pages/apis are secured while others are not. The mvc/web ui security is managed by owin middleware configured with UseCookieAuthentication and UseWsFederationAuthentication.
The secured pages in the webui are automatically redirected to an ADFS login screen when the user does not have already have a valid SAML token - as desired.
The secured web apis require a separate JWT token passed in the Auth header.
The Webapi is hosted in the same app pool as MVC. The Webapi does NOT have controllers, instead the webapiconfig has routes that leverage a DelegatingHandler to route/pass through the api calls. The Delegating handler is the one that checks to see if the JWT is included in the Auth header and if so allows it to continue to a different internal webapi that validates the JWT. If the JWT is not present then the DelegatingHandler returns a 401.
The 401 return used to work as it shortcircuited a continuation of the request and therefore bypassed any owin pipeline stuff. However, now when the shortcircuit fires the 401 is not returned. Instead the request continues and gets passes onto the Owin auth which then redirects (302) to the ADFS login. I have no idea why. If I change the response status code to something other than 401 then Owin Auth ignores it.
Please see the code below:
Global.asax.cs
public class Global : HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "Apis",
routeTemplate: "api/{*path}",
handler: HttpClientFactory.CreatePipeline
(
innerHandler: new HttpClientHandler(),
handlers: new DelegatingHandler[] { new ApiHandler() }
),
defaults: new { path = RouteParameter.Optional },
constraints: null
);
}
}
ApiHandler.cs
internal class ApiHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
try
{
// get jwt from header
var jwt = GetJWTFromHeader(request.Headers);
if (jwt == null)
{
response.ReasonPhrase = "Token required";
return await Task.FromResult<HttpResponseMessage>(response);
}
else if (!IsValidJWT(jwt))
{
response.ReasonPhrase = "Invalid token";
return await Task.FromResult<HttpResponseMessage>(response);
}
response = await base.SendAsync(request, cancellationToken);
}
catch (Exception ex)
{
// log error
response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
// return result
return response;
}
}
Startup.Auth.cs
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertificate;
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(
new CookieAuthenticationOptions()
{
SlidingExpiration = false
}
);
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = ADFS_REALM,
MetadataAddress = ADFS_METADATA,
UseTokenLifetime = true,
TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = true
},
Notifications = new WsFederationAuthenticationNotifications
{
RedirectToIdentityProvider = async r =>
{
// do stuff
},
SecurityTokenValidated = async s =>
{
// if we get here, then UI user has valid saml token
// do stuff
}
}
}
});
}
I appreciate any help. Please let me know if more details are needed!
Looks like you can use:
https://msdn.microsoft.com/en-us/library/system.web.http.owinhttpconfigurationextensions.suppressdefaulthostauthentication(v=vs.118).aspx
config.SuppressDefaultHostAuthentication();
Thanks to Finallz I was able to refine my search and come across an answer - found here. In my case, I don't need any special authentication config since I'm manually inspected the JWT in the apihandler. However, by simply including a map to my api path, it naturally overrides the Owin security:
app.Map("/api", inner =>
{
// nothing to do here since we don't have any concrete controllers to manage special authorization for
// we're using apihandlers to pass api traffic through to next stop
});
Unsure how IMobileServiceSyncTable security works - say I have a table, and it stores data for multiple users.
Following this Azure App Services tutorial, it looks like I can query - from a mobile app - for pretty much any record, for any user, that I want.
Client-side (e.g., Xamarin):
await todoTable.PullAsync("todoItems" + userid,
syncTable.Where(u => u.UserId = userid));
Is there a way (server-side) to automatically scope records to the current authenticated user? Or is that done for you automatically if you decorate your table controllers with the [Authorize] attribute?
Server-side:
[Authorize]
public class TodoItemController : TableController<TodoItem>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MyAppContext context = new MyAppContext();
DomainManager = new EntityDomainManager<TodoItem>(context, Request);
}
// GET tables/TodoItem
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
// GET tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<TodoItem> GetTodoItem(string id)
{
return Lookup(id);
}
}
Check out this blog post (assuming ASP.NET): http://shellmonger.com/2016/05/09/30-days-of-zumo-v2-azure-mobile-apps-day-18-asp-net-authentication/ - it adjusts the table controller to do exactly what you want.
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();
}
}
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...
}
}