Azure AD Redirect URL Using Application Gateway and ASE - single-sign-on

We have an ASP Core 2.0 App working nicely with Azure AD on a public network. Our Test environment is running in an Azure ASE. The user starts with a public address that passes through the Azure Application Gateway and gets routed to 1 of 2 App servers in the ASE. The application is registered in Azure AD with response URL's that specify the public address.
The problem is when the user redirects to login, the request address presented to Azure AD is an internal address from one of the 2 servers. Then the response URL's don't match and we get an error at login.
The question is how to present the public address to Azure AD so the response URL's match and the token is posted back to the app using the same? The app gateway, I'm told, is configured to populate x-forwarded-for header which has the original address. I don't see where in the web application this can be controlled.
startup.cs
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options =>
{
Configuration.Bind("AzureAd", options);
AzureAdOptions.Settings = options;
})
.AddCookie();
AccountController.cs
public IActionResult SignIn()
{
var redirectUrl = _azureAdOptions.WebBaseUrl;
return Challenge(
new AuthenticationProperties { RedirectUri = redirectUrl },
OpenIdConnectDefaults.AuthenticationScheme);
}
I would think this is a common configuration - passing public to private servers with SSO integrated.
[Edit]
Based on the link provided in the comments, which was very helpful, we tried several things including explicitly setting UseforwardedHeaders in startup.cs even though this is supposed to be enabled by default. Nothing we did changed the URL bolded in the URL below.
https://login.microsoftonline.com/2ff13e34-f33f-498b-982a-7cb336e12bc6/oauth2/authorize?client_id=998c48ae-bbcf-4724-b6f4-6517e41d180a&redirect_uri=**http%3A%2F%2Flocalhost%3A2345%2Fsignin-oidc**&resource=https%3A%2F%2Fgraph.windows.net&response_type=id_token%20code&scope=openid%20profile&response_mode=form_post......
However, and maybe this is a clue, if we comment out the [Authorize] on the home controller and login after the user clicks a button to login, it works. Why?
Note: IDs/GUIDs above have been scrambled to protect the innocent

I came across this post explaining how Application Gateway did not implement the standard x-forwarded-host headers. I'm hoping this gets fixed so the code below would not be required. The solution that worked in our configuration was to force both the public domain and scheme (HTTPS) on every request because the app gateway wasn't (and apparently couldn't be) configured to pass SSL to the backend servers.
Added to startup.cs
app.Use((ctx, next) =>
{
ctx.Request.Host = new HostString(options.Value.CustomDomain;
ctx.Request.Scheme = "https";
return next();
});
Now when the application redirects for any secure resource -- [Authorize] controller methods, or code that explicitly calls Challenge(x, y, z) the public domain on HTTPS is used as the origin host and scheme. Thanks to #juunas for pointing in the right direction.

Related

Best way to set up SSI with SAML and Azure AD

Based on this article, I am under the impression you can use the SAML standard to authenticate users on your web application using Azure AD:
https://learn.microsoft.com/en-us/azure/active-directory/develop/single-sign-on-saml-protocol
However it is not entire clear on all of the small steps required to do this. So I had a go and did the following to figure out for myself the first step which is to get the URL that the Service Provider needs to redirect the browser to authenticate using the IdP (i.e. Azure AD):
I set up a separate AD in Azure, on the free tier.
Within that AD I created an Application.
There was no option in the Application to set up SSI
I then use the following code to generate a URL for the SAML browser request:
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
namespace AzureSAMLExperiment
{
class Program
{
// Call to https://login.microsoftonline.com/common/FederationMetadata/2007-06/FederationMetadata.xml returns the SingleSignOnService element below:
public const string SingleSignOnServiceUrl = "https://login.microsoftonline.com/common/saml2";
public const string SingleSignOnQueryString = "?SAMLRequest={0}";
static void Main(string[] args)
{
// See https://learn.microsoft.com/en-us/azure/active-directory/develop/single-sign-on-saml-protocol
var SAMLRequestXML = $#"<samlp:AuthnRequest
xmlns=""urn:oasis:names:tc:SAML:2.0:assertion""
ID=""id6c1c178c166d486687be4aaf5e482730""
Version=""2.0"" IssueInstant=""{DateTime.UtcNow.ToString("o")}""
xmlns:samlp=""urn:oasis:names:tc:SAML:2.0:protocol"">
<Issuer xmlns=""urn:oasis:names:tc:SAML:2.0:assertion"">ISSUER</Issuer>
</samlp:AuthnRequest>";
var url = $"{SingleSignOnServiceUrl}?SAMLRequest={DeflateEncode(SAMLRequestXML)}";
}
private static string DeflateEncode(string val)
{
var memoryStream = new MemoryStream();
using (var writer = new StreamWriter(new DeflateStream(memoryStream, CompressionMode.Compress, true), new UTF8Encoding(false)))
{
writer.Write(val);
writer.Close();
return Convert.ToBase64String(memoryStream.GetBuffer(), 0, (int)memoryStream.Length, Base64FormattingOptions.None);
}
}
}
}
And put the resultant URL in the browser.
I tried a few different values for the ISSUER including
http://localhost
Our company domain name
https://sts.windows.net/{tennant guid}
{tennant guid}
{application guid}
But none of that worked.
As you can see I am using the common URL not the tenant-specific. I am not sure which one is best to use.
In every case I got the following response when visiting the url:
So any directions on what I have done wrong?
Do I need a premium level AD or is free OK?
Should I use a tenant or common end point?
Is there something wrong with the XML or the encoding I have used?
Azure AD provides the active directory service for free. To connect to the active directory via SAML protocol, you need to switch to a paid plan. Once you are on a paid plan and configured the SAML setup, Azure will be acting like a SAML IdP (identity provider). At this point, I would recommend testing the Authentication flow using an external IAM service configured as SAML SP (Service Provider) instead of crafting a solution on your own. E.g., you may try Auth0 as SAML SP for this purpose.
Architecture would be something like this with the above setup;
Your App <= OAuth => Auth0 <= SAML => Azure AD
If you don't want to pay Azure for the SAML support, you could federate users to Azure AD with the WsFed protocol. This is also supported with Auth0.
Your App <= OAuth => Auth0 <= WsFed => Azure AD
This link might be useful with some links for .Net if you need to support SAML protocol within your App and also provides some more links to have a broader view of possibilities.
Disclaimer: I work for Auth0.
Yes you need Azure AD Premium.
Here's an example using a custom SAML connection.
Then Azure AD / Enterprise applications / SSO.

Pass Windows Credentials to a RESTful Web API using Windows.Web.Http.HttpClient

I know this question was asked time and again, here and here. The answers given are pretty much the same, but in my case I still miss something and I cannot figure out exactly what it is. I have a RESTful Web API deployed and that is configured to accept only domain-authenticated calls. So on my client side, in my UWP application, I used the HttpClient class from the Windows.Web.Http namespace. The resources found online all show that I need to do two things:
Enable Enterprise Authentication in the package manifest of my UWP app. I did that. Here is a screen shot of the capabilities selected for my app:
Set the "AllowUI" flag to be false, so that the user is not prompted to enter its credentials. I did that too. Here is a code snippet of what I am doing:
Uri uri = new Uri(_myUriRoute);
var filter = new HttpBaseProtocolFilter { AllowUI = false };
var httpClient = new HttpClient(filter);
HttpResponseMessage response = await httpClient.GetAsync(uri);
With this code in place, I don't get prompted for the credentials, but the response.IsSuccessStatusCode flag comes back as false and the error that I get is 401 - Unauthorized.
Before you ask, yes, the server-side endpoint is properly configured and works fine. If I try this:
Uri uri = new Uri(_myUriRoute);
var filter = new HttpBaseProtocolFilter();
var httpClient = new HttpClient(filter);
HttpResponseMessage response = await httpClient.GetAsync(uri);
I am asked for my credentials and when I enter them correctly, I get a proper HTTP 200 code in response. I also tried this:
Uri uri = new Uri(_myUriRoute);
var filter = new HttpBaseProtocolFilter
{
AllowUI = false,
ServerCredential = new PasswordCredential(_myUriRoute, _myUserName, _myPassword)
};
var httpClient = new HttpClient(filter);
HttpResponseMessage response = await httpClient.GetAsync(uri);
and again, I get a nice HTTP 200.
So what am I missing? I don't want to be prompted and I don't want to store credentials either. I want to have Windows pass automatically the credentials of the current user of the app.
Two things worth mentioning. The above-described behavior happens in my development environment (Visual Studio 2017) while I try debugging/running my app using the "Local Machine" option. Also, the first thing that happens when I start the app is I am prompted to grant permissions to the app to access the pictures folder and account info:
This happens despite the fact that I have selected "User Account Information" among the Capabilities set for the application, as can be seen in the above screen shot of the Capabilities tab, in the application's package manifest.
Any idea of what is missing? Any idea of what else should be tried?
Any suggestion will be highly appreciated.
Cheers,
Eddie
PS: I posted the same question on the MSDN Forums as well
PS2: The Web API is running in IIS Express, started from Visual Studio 2017, in a different instance. I configured IIS Express to expose my Web API using the IP address of my development machine instead of the "localhost". In its web.config file, I have the following setting:
<system.web>
<authentication mode="Windows"/>
</system.web>
I post this, just in case the issue is on the Server side, which I think it isn't.

Identityserver3 - Select Identityprovider a client should use

I now have identityserver3 setup, i have 3 identityproviders configured:
- Local
- Google
- ADFS
I have multiple clients using Oidc-Client-JS (https://github.com/IdentityModel/oidc-client-js).
Now i would like to specify which identityprovider a client should use to login. so lets say:
Client A lets the user choose which provider to use
Client B logs in with local
Client C logs in with google
Client D logs in with ADFS
The situation of Client A is the default behavior and i have that working. My question is how do i set up clients B,C and D?
Check the following function in your start up see what you called your Identityprovider in my case "Google".
public static void ConfigureIdentityProviders(IAppBuilder app, string signInAsType)
{
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
{
AuthenticationType = "Google",
In your client set the acr_value for idp to what ever you have set.
let userManagerSettings: Oidc.UserManagerSettings = {
acr_values: "idp:Google",
Now the client will automatically redirect to the correct identityprovider
According to the IdentityServer3 documentation, you need to configure the IdentityProviderRestrictions for each clients. In case of only one identity provider is configured, the IdSrv3 will automatically redirect.

IdentityServer3: How to assign ClientSecret to MVC Client?

I have configured IdentityServer3 with EF and AspNetIdentity. I have 3 MVC client applications. All users and clients are configured in SQL DB. I was able to get it working and users can now log in.
Now I'm trying to add some security around Client & Users and I have few questions related
1> Does MVC client only works with Implicit Flow? I have MVC client and below is it's OWIN startup.
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44314/identity",
Scope = "openid",
ClientId = "LocalHostMvcClient",
RedirectUri = "http://localhost:34937/",
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies"
});
}
}
On IdentityServer, when I change the flow of LocalHostMvcClient to any flow (Other than 'Implicit') then client get error The client application is not known or is not authorized.. So it looks like for MVC client
it only works with Implicit flow.
Is this true?
2> Is ClientSecret not relevant for Implicit flow?
I want to use ClientSecret, but looks like it is not relevant for Implicit flow. Based on documentation ClientSecret is relevant to only flows that require secret. If answer to question 1 is true then does that mean i cannot use ClientSecret with Implicit Flow?
3> If i have multiple clients and users. Can we assign a user to a particular client?
For example, if i have 3 clients, www.client1.com, www.client2.com,www.client3.com and 2 users User1,User2. I want User1 to be able to login only for www.client1.com
Is this possible?
ASP.NET MVC can use any OpenID Connect flow. The error you are receiving is due to the client application requesting something it is not allowed to or otherwise being misconfigured in some way. Enable logging in Identity Server and it'll soon tell you why.
Client Secret is not used in Implicit, as implicit relies on the requesting url, not any sort of explicit authorization. That's why it's useful for client-side languages.
This is authorization logic and should be handled within the client application. For example when they login they would be shown an 'unauthorized' page. Identity Server is for authentication only.

how to redirect/map to externalregistration page from AuthenticateExternalAsync to angular page

I am using external providers to login to my web app. (for example Google). In my custom userservice I get to AuthenticateExternalAsync and from there I want (if need to) redirect to Angular page.
public override Task AuthenticateExternalAsync(ExternalAuthenticationContext context)
{
...
...
context.AuthenticateResult = new AuthenticateResult("~/externalregistration", user.Subject, name, identityProvider: user.Provider);
return Task.FromResult(0);
}
i have html page
at https://localhost:44300/Content/app/externalregistration.html
How do I map externalregistration to this page?
At the moment I get an error
https://localhost:44300/identity/externalregistration#
HTTP Error 404.0 - Not Found
thank you
Mark
The page for the partial login has to be with IdentityServer - see that it's looking for it at /identity/ and not /Content/app/.
If from your user service you issue a partial login, then that web page is entirely up to you to serve up from the server. If that partial login page needs to know the identity of the user, then it needs to be hosted in the same path as IdentityServer so the partial login cookie can be read on the server. If you then want that page to be a SPA, then you'd have to have some server side code issue something into the browser for your SPA to know the identity of the user. If you want that page to be a SPA and make Ajax calls back to the server, you need to include some XSRF protection.
All in all, custom partial pages are easiest implemented as standard server-rendered MVC pages.