GetExternalLoginInfoAsync always returns null when using Okta for authentication - owin

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

Related

Cannot Logout from IdentityServer 4 through Azure SAML logout request

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.

401 unauthorize exception for multitenant web api

Need help in authenticating the token request from any client application to WEB API. We have registered our web API has multi-tenant application in Azure AAD. My client application which has been registered in different tenant was able to get Access token from AAD.while making Http request to our endpoint with passing the access token part of request header, we are receiving 401 unauthorized exception. I found reason while browsing for multi tenant scenario is to disable ValidateIssuer and have custom handler.
• Is there any custom handler on implementing for WindowsAzureActiveDirectoryBearerAuthentication. I see people are using OpenIDConnect. But for WEB API, we are using WindowsAzureActiveDirectoryBearerAuthentication i.e Is there any equivalent Event for validation of access token in UseWindowsAzureActiveDirectoryBearerAuthentication and tell user is authenticated ?.
• Is there any better standard of validation of access token and tell user is valid user ?.
• Can we get the claims of user by passing bearer token to WEBAPI Authorize filter ?. or will httprequest object claims gets user information like given name, tenant name, object ID (esp. localhost debugging scenario also.), If we can get those information, we can have our own logic of validation.
Please let us know whether this approach is best practice for authenticating a user.
You could implement a custom issuer validator and assign it to the IssuerValidator property. This is useful when you can't specify a predefined list of issuers in configuration and need some runtime logic to determine if you trust the issuer presented in the token:
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
IssuerValidator = (issuer, token, tvp) =>
{
if (db.Issuers.FirstOrDefault(b => (b.Issuer == issuer)) == null)
return issuer;
else
throw new SecurityTokenInvalidIssuerException("Invalid issuer");
}
}
You could decode the access token to get basic user information like family_name/given_name , but you can only get that by using user identity to acquire the access token .

Azure AD OpenIDConnect + ASP.NET Core - Authenticate and Extra Permissions/Token?

I am using the following bits against my Azure AD to authenticate with ASP.NET Core.
https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-webapp-openidconnect-aspnetcore/
https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect-aspnetcore
I have the basic login/auth working after creating an Azure AD app. User can login/logout.
My question is given this, what's the best way when a user Auth's to log to a DB? I thought about making the redirect URL to an endpoint, saving, then just redirecting back to "Home" but is that ideal?
Also, is it possible to retrieve a bearer token via this approach? Or does this require another type of call or extending "scope"? So that for example I could retrieve the authenticated users Manager.
https://graph.microsoft.com/v1.0/me/manager
My question is given this, what's the best way when a user Auth's to log to a DB? I thought about making the redirect URL to an endpoint, saving, then just redirecting back to "Home" but is that ideal?
This way only able to log those who already sign-in your app successfully. It is not able to log those users who are attempt to sign-in your app but enter the wrong password.
Azure AD already provide lots of report to gain visibility into the integrity and security of your organization’s directory.( refer here)
And if you are using the Azure AD Premium, you can review the sign-in activities via the Azure new portal below:
And if you want to store the sign-in activity in your web app, you can write the custom code after the token is verified. Here is the code for your reference:
// Configure the OWIN pipeline to use OpenID Connect auth.
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = Configuration["AzureAD:ClientId"],
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAd:Tenant"]),
ResponseType = OpenIdConnectResponseType.IdToken,
PostLogoutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"],
Events = new OpenIdConnectEvents
{
OnRemoteFailure = OnAuthenticationFailed,
OnTokenValidated = context => {
//write the custom code to store users login-in
return Task.FromResult(0); }
},
});
Also, is it possible to retrieve a bearer token via this approach?
Yes. We can get the token after receive the authorization code. You can refer the code sample here to acquire the token from asp.net core app.

Web API 2 OWIN Bearer Token purpose of cookie?

I am trying to understand the new OWIN Bearer Token authentication process in the Single Page App template in MVC 5. Please correct me if I'm wrong, for the OAuth password client authentication flow, Bearer Token authentication works by checking the http authorization request header for the Bearer access token code to see if a request is authenticated, it doesn't rely on cookie to check if a particular request is authenticated.
According to this post:
OWIN Bearer Token Authentication with Web API Sample
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
using (IdentityManager identityManager = _identityManagerFactory.CreateStoreManager())
{
if (!await identityManager.Passwords.CheckPasswordAsync(context.UserName, context.Password))
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
string userId = await identityManager.Logins.GetUserIdForLocalLoginAsync(context.UserName);
IEnumerable<Claim> claims = await GetClaimsAsync(identityManager, userId);
ClaimsIdentity oAuthIdentity = CreateIdentity(identityManager, claims,
context.Options.AuthenticationType);
ClaimsIdentity cookiesIdentity = CreateIdentity(identityManager, claims,
_cookieOptions.AuthenticationType);
AuthenticationProperties properties = await CreatePropertiesAsync(identityManager, userId);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
}
The GrantReourceOwnerCredentials function not only compose the ticket with this line: context.Validated(ticket); but it also compose a cookie identity and set it to the cookie with this line: context.Request.Context.Authentication.SignIn(cookiesIdentity);
So my questions are, what is the exact purpose of the cookie in this function? Shouldn't the AuthenticationTicket be good enough for authentication purpose?
In the SPA template there are actually two separate authentication mechanisms enabled- cookie authentication and token authentication. This enables authentication of both MVC and Web API controller actions, but requires some additional setup.
If you look in the WebApiConfig.Register method you'll see this line of code:
config.SuppressDefaultHostAuthentication();
That tells Web API to ignore cookie authentication, which avoids a host of problems which are explained in the link you posted in your question:
"...the SPA template enables application cookie middleware as active mode as well in order to enable other scenarios like MVC authentication. So Web API will still be authenticated if the request has session cookie but without a bearer token. That’s probably not what you want as you would be venerable to CSRF attacks for your APIs. Another negative impact is that if request is unauthorized, both middleware components will apply challenges to it. The cookie middleware will alter the 401 response to a 302 to redirect to the login page. That is also not what you want in a Web API request."
So now with the call to config.SuppressDefaultHostAuthentication() Web API calls that require authorization will ignore the cookie that is automatically sent along with the request and look for an Authorization header that begins with "Bearer". MVC controllers will continue to use cookie authentication and are ignorant of the token authentication mechanism as it's not a very good fit for web page authentication to begin with.
The existence of the cookie also left me puzzled, since it clearly is not necessary in a bearer token authentication scenario... In this post the author dissects the individual accounts template, and has the following to say about the cookie:
The method also sets an application cookie. I don’t see a good reason for that.
My guess is that the authors of the template wanted to show examples of different kinds of authentication logic, and in this particular case they wanted to show how the authentication information could be stored in both the bearer token authentication JSON payload, as well as in a standard authentication cookie.
The fact that the JSON authentication payload is set to also include an additional (unnecessary) unencrypted property (the user id), in addition to the encrypted ticket, seems to support this theory:
var properties = CreateProperties(user.UserName);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
It seems that the authors of the template wanted to provide some useful examples, rather than the bare minimum needed to achieve bearer token authentication. This is also mentioned in the linked post above.
The cookie has one important purpose. Its value contains the bearer token which can be extracted by client-side javascript on your pages. This means that if the user hits F5 or refreshes the page, the cookie will typically persist. Your client-side javascript can then grab the bearer token from the cookie when the page reloads.

Azure ACS, WIF 3.5, Asp.Net 3.5 Custom Membership Provider and IsAuthenticated

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.

Resources