I'm stuck on how to solve following problem.
I'll start with describing what my app looks like in a general context.
[ ASP MVC (Angular App) ]
Uses Owin cookie
[ WEB API 2 ]
Uses Oauth Token Bearer
This scenario is happening:
User visits app and authenticates with a login form which lies in ASP MVC app and generates a cookie.
Now I've decided to use AngularJs to add a couple features which made me use $resources and Web API 2. However, those features are only available if user is authorized.
To the problem: Now I must use a token for each request to the Web Api 2 to access different methods within controllers. This means I must login the user again but this time through AngularJs. Using /token route.
How would I do this?
Should I take the cookie, check credentials in it and send it as a authentication request?
Can I do something within the form authentication, in the same method, in the Asp MVC app?
Please help me, this gave me a lot of overhead. Walking from a simple app to this in 30min. Can't even get my head around all stuff in the authentication.
Regards!
My WebAPI supports both token and cookie auth.
During startup I register the authentication like this:
private void ConfigureAuth(IAppBuilder app)
{
//Token
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
});
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = ctx =>
{
// this is to ensure that a 401 response is sent if the
// user is not authenticated, rather than redirecting to
// a logon page.
}
},
CookieDomain = ".example.com" //might not need to set this
});
}
Related
I have an MVC client app (APP1) protected by Identity Server. I use a backchannel mechanism to log out of clients as soon as user logs out of idsrv. Pretty similar to this one: https://github.com/IdentityServer/IdentityServer4/blob/main/samples/Clients/src/MvcHybridBackChannel/Startup.cs
Now the thing is: I need to add a support for external provider, Azure AD. And in turn it also should support automatic log out: when a user logs out of Azure AD, he should be logged out of idsrv and from the client apps.
My first idea was to implement the same approach: Azure App registration supports the ability to call client's endpoint upon log out. But I'm struggling with the part when I need to set up custom CookieAuthenticationEvents. The code which I have in a client app APP1:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies", options =>
{
options.EventsType = typeof(CookieEventHandler);
}
But the same code doesn't work in idsrv. But this I mean that my cookie is not being validated by my custom CookieEventHandler. Can anyone point me in a right direction?
One idea is to let Azure AD call the Logout method on the AccountController to sign out the user. (or if you make your own "logout" endpoint).
The logout method is protected by a ValidateAntiForgeryToken, so you might need to get around that by creating your "own" logout endpoint.
But basically what you need to do on a high level is to let AzureAD trigger a call to the
HttpContext.SignOutAsync(); method.
The short version:
I have an MVC5 website app deployed as an Azure cloud service web role. Using Owin for a login flow. The Owin Facebook integration works fine when testing the site on localhost, but on the production server GetExternalLoginInfoAsync() is returning null in the callback from signin-facebook.
Some details:
Have all the latest Owin Nuget packages (4.0.1 other than Identity.Owin 2.2.2 and Owin (startup components) 1.0).
The app uses basic Owin cookie authentication (traditional logins work fine).
We are not using the default Owin SigninManager, or UserManager as per the MVC WebApplication template. (Failure occurs well before reaching any of that code, in theory!)
Testing using Chrome on Windows 10.
It works perfectly on localhost (both debug and release), directly running the web project from visual studio (no cloud service involvement).
Am using the same Facebook test user for both localhost and production.
On the Facebook end, everything looks normal and the app is added to the user's list of apps, with all the permissions. It's not a facebook rejection.
Went so far as to remove Application Insights (as that is something different in production), but it did not affect the problem.
Looking at DevTools in Chrome:
Cache control looks normal for everything, all no-cache.
The .AspNet.Correlation.Facebook cookie is set and is being returned (same value) to the signin-facebook endpoint on completion from Facebook. Note that there is no other Owin related cookie.
Re. Deployment:
The Azure cloud service consists of the single web role.
Azure cloud service osFamily="5" osVersion="*"
Deployment is done directly from Visual Studio to the staging slot (right click publish).
I typically perform an interactive login to the staging slot, and when things are warmed up I use the Azure portal to swap the slots. Testing of the Facebook login is only on the production slot (not staging).
Here is a code fragment showing the processing (ChallengeResult is per the current WebApplication template code). The Controller is marked as [OutputCache(Location = OutputCacheLocation.None)] and is not marked for [Authorize]:
// POST: /Membership/ExternalSignupDispatch (They clicked to login with Facebook, Google, etc.)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ExternalSignupDispatch(string provider, string community = Communities.Dating)
{
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalSignupCallback1", "Membership", new { community }));
}
// GET: /Membership/ExternalSignupCallback
public async Task<ActionResult> ExternalSignupCallback1(string community = Communities.Dating)
{
var info = await AuthenticationManager.GetExternalLoginInfoAsync();
if (info == null) // Unsuccessful login
{
The silent failure of Owin/Facebook on the production server is maddening. If only there were an error enumeration... or an exception... but alas
Any ideas are greatly appreciated!
So. It turns out that Owin will currently fail in mysterious manners if there is no pre-existing ASP.NET_SessionID cookie present. Without it, the signin-facebook function does not remove the .Aspnet.Correlation.Facebook cookie, and does not set the .Aspnet.ExternalCookie cookie. The non-presence of the Session ID cookie somehow prevents the required cookie processing from taking place. All this sets the stage for intermittent silent failures, depending on the client's cookie status.
The workaround is to store a fake Session variable when generating the form with the Facebook login, forcing creation of the SessionID cookie prior to any Owin logins.
Note we are using the SystemWebCookieManager (in the hopes of avoiding such cookie issues). It appears that things are still evolving.
For reference, here is the cookie setup in our ConfigureAuth function:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieManager = new SystemWebCookieManager(),
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
ExpireTimeSpan = TimeSpan.FromMinutes(Params.LoginExpiryMinutes),
SlidingExpiration = true,
LoginPath = new PathString("/Login"),
Provider = new CookieAuthenticationProvider // Used to allow returning 401 Unauthorized status to API calls, instead of 302 redirect
{
OnApplyRedirect = ctx =>
{
if (!IsAjaxRequest(ctx.Request))
{
ctx.Response.Redirect(ctx.RedirectUri);
}
}
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
I think I may have phrased the question wrong. What I have currently is an MVC Web Application that (by default) uses bearer tokens. This is all well and good but if I want to communicate with any of these APIs outside of the Web Application, I would like to use a REST client. However, I can not find a way to generate a bearer token/call the Login method and get a bearer token back to be used as an authorization header in subsequent requests.
What I hope to have:
POST /Account/Login
Returns: token
POST /Product/Create
Token Header
Body Request
Returns: success or failure
ETC. All without losing the existing functionality of the website.
Thanks for any help!
The /Token endpoint already provides all the functionality you need in order to use [Authorize] on your WebAPI methods. The general process to make this work would be something like the following:
Client establishes a POST request to http://somesite.com/Token. The Content-Type header should contain x-www-form-urlencoded. The payload body should include grant_type=password&username="username"&password="password". The grant_type value indicates that we are presenting a password in exchange for an access token.
The server response will either be a HTTP 403 or an HTTP 200. In the case of HTTP 200, the response body will include access_token, token_type (bearer), and expires_in.
The client optionally stores this access_token for future access, then establishes a new request to the protected server resource, including a header Authorization, which will be Bearer access_token. This format is important, it must start with Bearer and a space, then the access_token value.
Note that this does not take into account issues of Cross Origin Requests (CORS), or HTTPS. Proper security should be enacted whenever a username or password is sent, as in step 1 here.
This is configured by default on any new MVC5 project with Identity. You will find a Startup class similar to the following:
public partial class Startup
{
// Enable the application to use OAuthAuthorization. You can then secure your Web APIs
static Startup()
{
PublicClientId = "web";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
AuthorizeEndpointPath = new PathString("/Account/Authorize"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
The TokenEndpointPath represents your path for token requests, the AuthorizeEndpointPath represents the path used when External Logins (Facebook, Twitter, Google, etc.) are used. See this Microsoft article for more info on the default template.
A much more detailed step by step of this process and complete client application written in Angular.js can be found on a blog by Taiseer Joudeh.
I'm have an IIS hosted MVC 5 app that uses Asp.Net Identity and OWIN for authentication via .AspNet.ApplicationCookie. From one of its views, I make calls to long-running methods on a self-hosted SignalR hub (running on the same server) via a SignalR JS client. These calls all work as expected. I now wish to decorate my hub with [Authorize(Roles = "Administrator")]. This has proved problematic. Setting a breakpoint in a hub method reveals that the Context.User is null, even though the .AspNet.ApplicationCookie is clearly in the Context.RequestCookies.
Here is the bootstrap for the hub (self-hosted in a windows service):
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
map.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie
});
var hubConfiguration = new HubConfiguration();
map.RunSignalR(hubConfiguration);
});
Here is the auth config for the web app (hosted in IIS):
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(UserAccountContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
Question 1: Is the use of [Authorize] possible in the scenario described above? If so, how?
Question 2: Would it be better to just merge the self-hosted hub into the IIS hosted app? If so, are there any issues with long-running hub methods under IIS?
Update 1
I've tried adding TicketDataFormat = new TicketDataFormat(new MachineKeyDataProtector("ASP.NET Identity")) to the CookieAuthenticationOptions on my hub config, but that didn't help. Sure seems like this should be easier than it is.
I ended up moving my self-hosted hub into my ASP.Net application and it worked just fine. This seemed easier and more maintainable than implementing the workaround in this SO question, OWIN Self-Host CookieAuthentication & Legacy .NET 4.0 Application / FormsAuthenticationTicket
I have an ASP.NET MVC5 application I am working on right now that I am integrating OWIN authentication into. I currently have my app configured in the startup class as so
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, // "ApplicationCookie",
LoginPath = new PathString("/Producer/LogIn/")
});
This is working great. But I have a few areas in my code where I need to know the login path. I am not looking to directly call a method to log out the user and redirect them, I just need the value of the PathString.
I could hard code it of course as its a value that wouldn't change often, (if ever) but I hate to do that and I want a solution that I can reuse in other projects cleanly. I would prefer to be able to access this value programmatically.
TIA.