I have a project where Windows Authentication and Forms login are required. I came across OWIN Mixed Authentication which seems to meet my requirements.
Before implementing into my own project I tried running the sample solution from the source link.
I debugged the solution using IIS Express and when I entered my credentials into the windows authentication dialog my correct credentials where found in the logonUserIdentity variable.
But when I set up a local IIS site add set the following feature delegation property as stated in the readme file:
Authentication - Windows to Read/Write
When I entered my credentials into the windows authentication dialog NT AUTHORITY\IUSR is coming through in the logonUserIdentity variable instead of the username I entered in the dialog.
I feel this happening because AllowAnonymous is enabled on the IIS site but its needed to stop a login loop that occurs because of the CookieAuthentication within the Startup.Auth class.
How should I be setting up my IIS site so that the windows credential dialog passes through the entered credentials and not NT AUTHORITY\IUSR.
I debugged the solution using IIS Express and when I entered my credentials into the windows authentication dialog my correct credentials where found in the logonUserIdentity variable.
As far as I know, the IIS express use current computer login account as the Anonymous login account. So you will find the logonUserIdentity is right. You could try to login the application with different domain account. You will find it still use current computer login account not changed to the login user account.
Since the mix auth allow multiple ways to login,you should always enable anonymous login to let the person who doesn't have the domain account.
The mix own auth use asp.net identity external login to achieve login with windows.The asp.net identity external login will firstly go to the mixauth provider to check the windows auth result.
If success, it will go back to the account controller's ExternalLoginCallback method with the windows info and use this info the identity will generate an identity user.
In my opinion, if you want to get the current login in user, I suggest you could try to use session to store the windows login in user's account in ExternalLoginCallback method.
More details, you could refer to below codes:
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
Session["LoginonUsername"] = loginInfo.DefaultUserName;
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// Sign in the user with this external login provider if the user already has a login
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
}
Result:
My IIS site binding was set to http://projectname
When I changed the binding on the IIS site to http://localhost or http://pcname it was allowing me to pass through the correct windows credentials.
Related
Context
I have a Service Provider (SP) based on IdentityServer 4 and Sustainsys.Saml2.AspNetCore2 that is configured to use Azure as an IdP (SAML2).
I also have a SPA with an api that connects to my SP (with oidp) to identify my user. The api then creates a JWT for my user to use.
I can login my user correctly.
Question
My issue comes with the logout. I want to use the logout url parameter of Azure to notify my SP about the logout. I manage to see the SAML Logout Request as a string when I configure an endpoint of mine but I can't exploit it and parsing it manually does't seem right.
Is there an existing endpoint that would come with my dependencies that I missed?
The goal here is to revoke all my user's application sessions (the apps to which my user is connected throug my SP).
Configuration
Idp configuration in the SP (called in Startup.cs).
The Saml2AuthModel comes from a config file.
public static AuthenticationBuilder AddSaml2Auth(this AuthenticationBuilder builder, Saml2AuthModel saml2AuthModel)
{
builder.AddSaml2(saml2AuthModel.Scheme, saml2AuthModel.DisplayName ?? saml2AuthModel.Scheme, options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.SPOptions.EntityId = new EntityId(saml2AuthModel.ServiceProviderEntityId);
options.SPOptions.ModulePath = "/" + saml2AuthModel.ModulePath ?? saml2AuthModel.Scheme ?? options.SPOptions.ModulePath;
var idp = new IdentityProvider(
new EntityId(saml2AuthModel.IdentityProviderEntityId),
options.SPOptions
);
idp.MetadataLocation = saml2AuthModel.IdentityProviderMetadataLocation;
options.IdentityProviders.Add(idp);
});
return builder;
}
The Sustainsys.Saml2 library has support for single logout. To enable it, you need to set up a service signing key. The reason is that logout requests and responses should be signed. So the library doesn't expose the logout endpoints if it has no signing keys available.
I have an ASP.NET MVC 5 application that uses ASP.NET Identity 2/OWIN that has it's own login using the following:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString(AppConfiguration.LoginPath),
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, int>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (id) => (Int32.Parse(id.GetUserId())))
}
});
In Addition to our own authentication set up above in the startup, we'd like to also introduce authentication using an external app that uses Identity Server 4 (basically just so a user in that app can sso into ours), using open id connect, setting that up after the above code like:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = AppConfiguration.ExternalServerAuthority,
ClientId = "xxxxxxxxxx",
ClientSecret = "secret",
RedirectUri = "http://localhost:1045/signin-oidc",
ResponseType = "id_token",
RequireHttpsMetadata = false,
PostLogoutRedirectUri = "http://localhost:1045/signout-callback-oidc",
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async context =>
{
var appAuthManager = DependencyResolver.Current.GetService<IApplicationAuthenticationManager>();
var userManager = DependencyResolver.Current.GetService<IApplicationUserManager>();
var email = context.AuthenticationTicket.Identity.FindFirst("preferred_username");
var user = userManager.FindByName(email.Value, null);
if (user == null)
{
return;
}
await appAuthManager.SignInAsync(user, false, false);
}
}
});
We need to be able to do a SignInAsync when we get the token from the external app because we need the user to be signed in as the actual user in our own app. Part of our problem is also, if we set this up, then whenever a user is not logged in, they are always sent to the other external app to log in if trying to access a resource/page that they must be logged in for - it no longer sends them to our existing login page (which would give them the option of logging in there like they normally would or clicking a link to take them to the other app to log in if they have a user account for that app too). We don't want this because not all our users will be using/have access to this other app, it's really only for some users, mostly to conveniently navigate into our app from the other app without having to separately sign in to ours. So we can't have all unauthorized requests sent to this other app to log in. How can we achieve that? Is there a better way to set that up here?
Edit for more info:
To explain more clearly the pieces here and what needs to happen. There are actually three applications at play. There is our app, an MVC 5 app that has it's own login page, uses owin/asp.net identity for user authentication and to store/manage its users. There is now another app (for another company that wants to work with us), which is a SPA app that authenticates against a separate IdentityServer4 server run by the same company. This SPA app, wants to put a link in it that sends a user to our MVC 5 app without the user having to actually log in to our MVC 5 app - so, they want to SSO into our MVC 5 app (by use setting up our app to use oidc connect to authenticate the user against the SPA's identity server). So when they get to our app, we need to actually log them in as our user....but we have to also make sure that all our users are not sent to this external app to log in when they are not currently logged in to our app because not all our users will have access to this external app. I hope that clears this up.
I'm currently trying to get Okta to work with our MVC based application. Unfortunatly I am not the original designer or author of original code. However after some digging I have found that my predecessors work was based on the sustainsys example app "SampleOwinApplication" and this certainly seems to provided the functionality that we require. So I have based my query on this sample that can be obtained from https://github.com/Sustainsys/Saml2
This works with the sustainsys saml stub but now a genuine authentication provider (in this case Okta)
If I configure the application to use the sustainsys stub authentication provider and using Chrome with a plugin to view SAML tokens. I can see the SAML token come back and is presented to the call back as expected:
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
and when it runs loginInfo gets filed in and all works as expetced.
However when I change to configuration to use my Okta app, I get redirected to log in to Okta (as expecected) and I can see successful authentication and a SAML token comes back to my application (as seen in the Chrome plugin). However the above consumer for the callback ends up with a null value in loginInfo.
Further digging into the issue shows that in the Statup.Auth.cs there is the following code:
// 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))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
and then the Saml2 authentication is added
app.UseSaml2Authentication(CreateSaml2Options());
So it looks like cookie authentication is being used rather than saml2. If I check for cookies from the sustainsys site I can see them added to the browser and authentication works as expected. However, if I use Okta for authentication, no such cookies get set and the authentication fails.
Removing all the cookie authentication references results in:
A default value for SignInAsAuthenticationType was not found in IAppBuilder Properties. This can happen if your authentication middleware are added in the wrong order, or if one is missing.
So clearly this is required, shifting the call to app.UseSaml2Authentication(CreateSaml2Options()); before app.UseCookieAuthentication in the vain hope of it changing the priority and therefore picking up the SAML token fails and whilst the code runs authentication and the call to AuthenticationManager.GetExternalLoginInfoAsync, always results in a null value being returned regardless of the authentication provider.
So I either need to completely remove the cookie authentication so it is forced to use the saml packet, get Okta to set the necessary cookies or be able to parse the saml 2 token independently rather than relying on AuthenticationManager.GetExternalLoginInfoAsync to do the work.
Any clues/advice is appreciated
See the working configuration I am currently using successfully with Okta for a service provider initiated login:
AuthenticateResult.Succeeded is false with Okta and Sustainsys.SAML2
Unfortunately, it is still not working with an identity provider initiated login.
See: IdP Initiated Login with Sustainsys.SAML2 - AuthenticateResult Has No Information
At the moment, I use the following code to prompt a redirect to the Azure login portal to enter a valid username and password at the Azure login screen:
public void SignIn()
{
// Redirect to the Azure sign in page.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = RedirectUrl }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
How could i change the above code so that instead of redirecting to Azure first and entering a username and password over there, I can enter a username and password directly from my website and pass the credentials along in the background to Azure, get an authentication token back and finally redirect to the return url specified?
That is not supported. The only place where a user should enter azure ad credentials is the azure ad credentials gathering page. Everything else is disallowed, including hosting that page in an iframe. That's for security reasons.
HTH
V.
Decided not to use the ClaimsPrincipal from the Azure AD and do the following instead and manage the session for current user manually on my website:
private static string GetUserID(string userName, string password)
{
// Acquire the token for this user attempting to access the API.
var authenticationContext = new AuthenticationContext(Authority, TokenCache.DefaultShared);
var result = authenticationContext.AcquireToken(ApiResource, ClientId, new UserCredential(userName, password));
return result.UserInfo.UniqueId;
}
Following the steps in this guide Using Azure ACS I have a working Azure ACS service configured & authenticating via Facebook, redirecting back to a website running on my development server.
On authentication success Azure ACS redirects back to my local development website and the IsAuthenticated flag is true, however I want to set the IsAuthenticated flag to true only if the email from the claim also exists in my local database, via a check/call to a custom MembershipProvider. If the email from the claim does not exist I want to redirect the client to a register page. Once registered and authenticated I would like to set the IsAuthenticated flag to true.
Currently once authenticated with Facebook and AzureACS, a user can request a secure page such as ViewAccountBalance.aspx, even though the account does not exist since out of the box IsAuthenticated flag to true. Interested to hear what others have done and what the best practice is.
You'll need to make a clear difference between authentication and authorization. Since the user logged in through Facebook it means he's authenticated (you know who he is and where he comes from).
Now, if you want to restrict parts of the application based on a specific condition you're actually talking about authorization. You might consider combining roles with a simple HttpModule. Example: your HttpModule could verify which page the user is browsing. If the user accesses a page that requires an active profile, you could use the following code:
public class RequiresProfileHttpModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.AuthorizeRequest += new EventHandler(OnAuthorize);
}
private void OnAuthorize(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
if (app.Request.Url.ToString().Contains("bla") && !app.Context.User.IsInRole("UsersWithProfile"))
app.Response.Redirect("http://myapp/register.aspx");
}
}
The only thing you'll need to take care of is to update the principal to make sure it has the role UsersWithProfile if the user filled in his email address.
This is just one of many possible solutions. If you're using ASP.NET MVC you could achieve the same result with global ActionFilters. Or, you could also try to work with the IClaimsPrincipal (add a claim if the user has a profile).
Sandrino is correct. You can use role based authorization (or more generally, claim based authorization). By default, ACS simply returns the claims issued by the identity providers to your relying party. For Facebook, it will return an email claim. However, you can configure ACS to create additional rules. For example, you can map particular users to a role whose value is administrator. Then ACS will also return this role claim to your relying party. Then you can use Sandrino’s suggestion to use role based authorization. You can also refer to http://msdn.microsoft.com/en-us/library/windowsazure/gg185915.aspx for more information.