How to serve multiple web sites by the one ADFS server? - c#

I have two servers: one of them serves UI (it is called webUI) and another works with data (it is called webAPI).
I try to implement an authentication across the ADFS server. It has Relying Party Trusts for both servers: [urn=webui,identifier=address/webui],[urn=webapi,identifier=address/webapi].
I adjused the HttpConfiguration for webUI and user can be authenticated and use website, which the webUI serves (it's good).
var wsFedMetAdd = ConfigurationManager.AppSettings["wsFedMetAdd"];
if (string.IsNullOrWhiteSpace(wsFedMetAdd))
throw new ConfigurationErrorsException(Properties.Resources.InvalidMetadataAddress);
var wsFedWtrealm = ConfigurationManager.AppSettings["wsFedWtrealm"];
if (string.IsNullOrWhiteSpace(wsFedWtrealm))
throw new ConfigurationErrorsException(Properties.Resources.InvalidWtrealm);
appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
});
var options = new WsFederationAuthenticationOptions
{
MetadataAddress = wsFedMetAdd,
Wtrealm = wsFedWtrealm,
SignInAsAuthenticationType = "Federation"
};
appBuilder.UseWsFederationAuthentication(options);
config.Filters.Add(new AuthorizeAttribute() { Roles = "Admin" });
Once client gets RequestSecurityTokenResponse (SAML Token). Also responses from ADFS set cookies for further requests (MSISAuth, MSISAuthenticated and so on).
The webAPI has the same implemention of HttpConfiguration (only one difference - wsFedWtrealm is urn:webapi instead urn:webui). Then I try send a request to the webAPI from client and the ADFS Server asks to authenticate one more.
I can't understand what should I do to use the same credentials for webAPI which I entered for webUI. Or maybe I should use SAML Token?
UPDATE
Wow. It is worked without SAML token, just using cookies.
When the user tries to be authenticated for webUI, diverse cookies are set on client (.AspNet.Federation, MSISAuth, MSISAuthenticated...). Then I substitute the webUI link with the webAPI link in the address bar and then webAPI doesn't ask to enter login and password. Hence data is displayed in browser. Authentication is picked up for webUI and for webAPI too.
But now problem is I get the error when javascript tries to send a request to webAPI:
XMLHttpRequest cannot load
https://my_address/adfs/ls/?wtrealm=urn%3awebapi&wctx=_ No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'https://my_address:9001' is therefore not allowed
access.

What version of ADFS?
You are mixing two protocols - Web API generally uses OAuth.
Use OpenID Connect for the UI and then that will naturally flow into the WebAPI as per this : Securing a Web API with ADFS on WS2012 R2 Got Even Easier.
Or for a somewhat more convoluted approach - what protocol to use with ADFS when security webapi for non-browser clients

This post help me to solve my problem.
I added to code of index.html new element iframe. Attribute src is the link to my webAPI.

Related

How to Authenticate two subdomain by one login in IdentityServer?

I have an IDP server implemented by Duende IdentityServer assume which is hosted on idp.com and there are two separate ReactJS applications hosted on app.mysite.com and profile.mysite.com and they are using JWT token for authentication and authorization process. now when I login into app.mysite.com through idp.com profile.mysite.com is un unauthenticated and needs another login. I use the same client configuration for both of these sites. I know there are some methods such as using an IFRAME inside client code to share the JWT token between these two app but I am looking for a built-in approach inside the Identity server to solve this issue?
First of all, if you have 2 CLIENTS, you should configure 2 separate configurations for both of them.
Afer separation of clients you should rely on cookie set on idp.com after first authentication. (Good to know - How to setup cookie authentication basic cookie authentication: https://learn.microsoft.com/pl-pl/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0)
Anyway, if you configured IdentityServer properly, it handles cookie authentication "out-of-the-box" - so probably the only thing you have to do is to Signin the user.
AuthenticationProperties props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(LoginOptions.RememberMeLoginDuration)
};
var issuer = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.Username
};
await HttpContext.SignInAsync(issuer, props);
When the youser want to login to second application, after start of the flow (eg. code flow) and redirect to the idp.com, idp.com knows that the user is already signed-in (cookie) and should immediately generate token and redirect back to the return url.
If you need you can adjust custom behaviours using IProfileService.

IdentityServer4 - Using External Authentication

I am trying to implement an Authorization server using IdentityServer4, using the Hybrid Flow.
let's say url is : auth.company.com
To authenticate users, the company uses a simple .NET MVC login/password form.
url : client.company.com/login.html
My question is : how can i plug the authentication system into the IdentityServer4 ?
I've tried adding this :
Startup.cs / ConfigureServices()
services.AddIdentityServer(SetupIdentityServer)
private static void SetupIdentityServer(IdentityServerOptions options)
{
options.UserInteraction.LoginUrl = #"client.company.com/login.html";
options.UserInteraction.LoginReturnUrlParameter = "referrer";
}
But it resulted in too many redirections error between auth server and authentication server
Thank you
I just replied to another question very similar to this so this is a shameless copy and paste of that:
This will not work as the identity server needs to issue its own cookie once authentication has taken place. This cookie is what allows the authorise endpoint to know who is signed in.
The intention of this model is that authentication takes place on the IDP or it’s negotiated with an external provider via a suitable protocol. Therefore the appropriate approach in this case is to move the login UI into your identity server application. It’s entirely up to you exactly how that is done and where it gets it’s data from but it must be your identityserver4 that issues the cookie.

Why redirections on my site take me to azure.websites.net instead my domain?

I have configured my web app to time out on idle by setting the following in the Startup.auth.cs file:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
ExpireTimeSpan = TimeSpan.FromHours(1),
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
SlidingExpiration = true,
....
}
My web app is behind a Virtual Network--->Application Gateway. The AG forwards the requests to the web app. I have also got rules that prevent direct access (i.e myapp.azurewebsites.com) to the web app.
Now when the session times out, I get redirected to:
https://myapp.azurewebsites.net/Account/Login?ReturnUrl=%2Fcustomerarea
which is a blue screen with error 403(correct error), instead of my own domain like:
https://example.com/Account/Login?ReturnUrl=%2Fcustomerarea
Anyone knows why I would get this behavior? thanks.
Edit: more info...it looks like any redirect causes the above problem. So if I enter a URL https://example.com/customerarea which requires the user to login, then the redirected URL to the login page, has the azurewebsites.net in its address.
So the above answer is correct. I am adding more information on how that fixes the problem in an MVC 5 web app. According to Microsoft's Application Gateway FAQ:
Application Gateway also inserts X-Original-Host header that contains the original Host header with which the request arrived. This header is useful in scenarios like Azure Website integration, where the incoming host header is modified before traffic is routed to the backend.
So to fix, I added the following code at the top of my Configuration (IAppBuilder app) method in the start.cs file:
app.Use(async (context, next) =>
{
if (context.Request.Headers.GetValues("X-Original-Host") != null)
{
var originalHost = context.Request.Headers.GetValues("X-Original-Host").FirstOrDefault();
context.Request.Headers.Set("Host", originalHost);
}
await next.Invoke();
});
Any redirects without explicit host portions in the ASP.NET ecosystem will go to the host portion provided by the current HttpContext.Request.
Your application gateway will make the final request to your application (like a proxy) and addresses it as your .azurewebsites.net domain → meaning your ASP.NET app doesn't know about the original request to the gateway.
What you will need to do is to set the incoming request hostname to the original hostname from the request that went to your Application Gateway.
This documentation page (It's ASP.NET Core, but the same principle holds true for asp.net-mvc-5) should allow you to get an insight on how to overwrite your incoming HttpContext.Request hostname to the original. There are of course multiple ways.
Forwarded Headers (Which would then need to be set by your Application Gateway)
Config entry (Having a config entry with a "hardcoded" hostname)
Both approaches will just require extra middleware to override the current request according to the information gathered by either of these approaches.

Ws-Federation Identity Information Not Available in Web API?

I'm using Ws-Fed Authentication OWIN middleware to authenticate an ASP.NET MVC app with Web API endpoints using ADFS. I'm able to sign in using ADFS successfully, and on my MVC controllers, HttpContext.User.Identity.IsAuthenticated is true - I can see the claims information for the signed in user as well.
However for WebAPI endpoints, User.Identity.IsAuthenticated is false. The claims information for the signed is user is also unavailable. Is there any way that I expose the fact that the user is authenticated for both MVC and WebAPI controllers?
Here is how I am configuring my authentication middleware in the OWIN Startup class:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
ExpireTimeSpan = TimeSpan.FromMinutes(sessionDuration),
SlidingExpiration = true //expiration extended after each request
});
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = metadata,
Notifications = new WsFederationAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/?loginfailed=loginfailed");
return Task.FromResult(0);
}
}
});
Try adding a "Name Id" claim to ADFS:
http://darb.io/blog/2014/06/30/WebAPI-and-ADFS-as-external-login-provider/
So I discovered the answer to this question by examining the OWIN cookie authentication middleware source code on CodePlex. Cookies created using the middleware by an MVC controller are created differently from cookies created Web API. MVC cookies are a reference to user information stored in session, and since Web API is completely stateless (no session), cookies created in MVC can not be used in Web API.
In addition, it is bad practice to use cookie authentication in Web API anyways; bearer token authentication is a preferable option.
In my case where I needed to use Ws-Federation authentication, the solution was to:
Add bearer token authentication middleware to my app
Create a Web API endpoint (ideally cryptically named) that will securely receive Ws-Federation claims, perform validation to ensure the request really came from your MVC controller, use them to generate a bearer token, and respond with the generated bearer token
Upon authenticating in MVC, serialize the claims, and marshal them over to Web API using the endpoint created earlier
Add the bearer token to a hidden field in the SPA
Many, many thanks to #Juan for providing me with feedback and links to point me in the right direction.

Cross-Domain OWIN Authentication for Multi-Tenanted ASP.NET MVC Application

I am using OWIN Authentication for a Multi-Tenant ASP.NET MVC application.
The application and authentication sits on one server in a single application but can be accessed via many domains and subdomains. For instance:
www.domain.com
site1.domain.com
site2.domain.com
site3.domain.com
www.differentdomain.com
site4.differentdomain.com
site5.differentdomain.com
site6.differentdomain.com
I would like to allow a user to login on any of these domains and have their authentication cookie work regardless of which domain is used to access the application.
This is how I have my authentication setup:
public void ConfigureAuthentication(IAppBuilder Application)
{
Application.CreatePerOwinContext<RepositoryManager>((x, y) => new RepositoryManager(new SiteDatabase(), x, y));
Application.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieName = "sso.domain.com",
CookieDomain = ".domain.com",
LoginPath = new PathString("/login"),
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<UserManager, User, int>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateClaimsAsync(manager),
getUserIdCallback: (claim) => int.Parse(claim.GetUserId()))
}
});
Application.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
I have also explicitly set a Machine Key for my application in the root web.config of my application:
<configuration>
<system.web>
<machineKey decryption="AES" decryptionKey="<Redacted>" validation="<Redacted>" validationKey="<Redacted>" />
</system.web>
</configuration>
Update
This setup works as expected when I navigate between domain.com and site1.domain.com, but now it is not letting me login to differentdomain.com.
I understand that cookies are tied to a single domain. But what is the easiest way of persisting a login across multiple domains? Is there a way for me to read a cookie from a different domain, decrypt it, and recreate a new cookie for the differentdomain.com?
Since you need something simple, consider this. In your particular setup, where you really have just one app accessible by multiple domain names, you can make simple "single sign on". First you have to choose single domain name which is responsible for initial authentication. Let's say that is auth.domain.com (remember it's just domain name - all your domains still point to single application). Then:
Suppose user is on domain1.com and you found he is not logged-in (no cookie). You direct him to auth.domain.com login page.
Suppose you are logged-in there already. You see that request came from domain1.com (via Referrer header, or you can pass domain explicitly). You verify that is your trusted domain (important), and generate auth token like this:
var token = FormsAuthentication.Encrypt(
new FormsAuthenticationTicket(1, "username", DateTime.Now, DateTime.Now.AddHours(8), true, "some relevant data"));
If you do not use forms authentication - just protect some data with machine key:
var myTicket = new MyTicket()
{
Username = "username",
Issued = DateTime.Now,
Expires = DateTime.Now.AddHours(8),
TicketExpires = DateTime.Now.AddMinutes(1)
};
using (var ms = new MemoryStream()) {
new BinaryFormatter().Serialize(ms, myTicket);
var token = Convert.ToBase64String(MachineKey.Protect(ms.ToArray(), "auth"));
}
So basically you generate your token in the same way asp.net does. Since your sites are all in the same app - no need to bother about different machine keys.
You redirect user back to domain1.com, passing encrypted token in query string. See here for example about security implications of this. Of course I suppose you use https, otherwise no setup (be it "single sign on" or not) is secure anyway. This is in some ways similar to asp.net "cookieless" authentication.
On domain1.com you see that token and verify:
var ticket = FormsAuthentication.Decrypt(token);
var userName = ticket.Name;
var expires = ticket.Expiration;
Or with:
var unprotected = MachineKey.Unprotect(Convert.FromBase64String(token), "auth");
using (var ms = new MemoryStream(unprotected)) {
var ticket = (MyTicket) new BinaryFormatter().Deserialize(ms);
var user = ticket.Username;
}
You create cookie on domain1.com using information you received in token and redirect user back to the location he came from initially.
So there is a bunch of redirects but at least user have to type his password just once.
Update to answer your questions.
Yes if you find that user is authenticated on domain1.com you redirect to auth.domain.com. But after auth.domain.com redirects back with token - you create a cookie at domain1.com as usual and user becomes logged-in a domain1.com. So this redirect happens just once per user (just as with usual log in).
You can make request to auth.domain.com with javascript (XmlHttpRequest, or just jquery.get\post methods). But note you have to configure CORS to allow that (see here for example). What is CORS in short? When siteB is requested via javascript from siteA (another domain) - browser will first ask siteB if it trusts siteA to make such requests. It does so with adding special headers to request and it wants to see some special headers in response. Those headers you need to add to allow domain1.com to request auth.domain.com via javascript. When this is done - make such request from domain1.com javascript to auth.domain.com and if logged in - auth.domain.com will return you token as described above. Then make a query (again with javascript) to domain1.com with that token so that domain1.com can set a cookie in response. Now you are logged in at domain1.com with cookie and can continue.
Why we need all this at all, even if we have one application just reachable from different domains? Because browser does not know that and treats them completely different. In addition to that - http protocol is stateless and every request is not related to any other, so our server also needs confirmation that request A and B made by the same user, hence those tokens.
Yes, HttpServerUtility.UrlTokenEncode is perfectly fine to use here, even better than just Convert.ToBase64String, because you need to url encode it anyway (you pass it in query string). But if you will not pass token in query string (for example you would use javascript way above - you won't need to url encode it, so don't use HttpServerUtility.UrlTokenEncode in that case.
You are right on how cookie works, but that it not how OWIN works.
Don't override the cookie domain of the Auth Server(auth.domain.com).
You may override the cookie domain of the individual sites to "site1.domain.com" and "site2.domain.com".
In your SSO page, let's say someone lands on site1.domain.com and since is unauthenticated is taken to your auth server. The auth server takes the login credentials and sends a code to site1.domain.com on the registered URI(eg: /oauthcallback). This endpoint on site1.domain.com will get an access token from the code and SignIn(automatically write the cookie). So 2 cookies are written one on auth.domain.com and second on site1.domain.com
Now, same user visits site2.domain.com and finds a cookie of logged in user on "auth.domain.com". This means that the user is logged in and a new cookie is created with same claims on "site2.domain.com"
User is now logged into both site.
You don't manually write the cookie. Use OwinContext.Signin and the cookie will be saved / created.
To answer the question on your update, there is no way of sharing cookies across different domains.
You could possibly use some query strings parameters and some server side logic to handle this particular case, but this could raise some security concerns.
Se this suggestion: https://stackoverflow.com/a/315141/4567456
Update
Following your comment, here are the details:
https://blog.stackoverflow.com/2010/09/global-network-auto-login/
https://meta.stackexchange.com/questions/64260/how-does-sos-new-auto-login-feature-work
http://openid.net/specs/openid-connect-session-1_0.html
Bonus:
The mechanism in use today is a bit different, and simpler, than what is discribed in the first two links above.
If you look at the network requests when you login on StackOverflow, you will see that it logs you in individually to each site on the network.
https://stackexchange.com/users/login/universal.gif?authToken=....
https://serverfault.com/users/login/universal.gif?authToken=...
https://askubuntu.com/users/login/universal.gif?authToken=...
etc, etc...
William,
I understand that cookies are tied to a single domain.
Yes and there is no way you can manipulate it on the client side. The browsers never send a cookie of one domain to another.
But what is the easiest way of persisting a login across multiple domains?
External Identity Provider or Security Token Service (STS) is the easiest way to achieve this. In this setup all the domains site1.com. site2.com etc will trust the STS as the identity provider. In this federated solution, the user authenticates with the STS and the federated identity is used across all the domains. Here is a great resource on this topic from an expert.
Is there a way for me to read a cookie from a different domain, decrypt it, and recreate a new cookie for the differentdomain.com?
With some tweaks you may achieve this federated solution with your current setup. FYI, this is not recommended or an in-use approach, but an idea to help you achieve the goal.
Lets say you have multiple domains 1, 2, 3 pointing to a single application. I will create another domain STS pointing to the same application but deals only with cookie creation and validation. Create a custom middleware that utilizes the asp.net cookie authentication middleware under the wrap. This gets executed only if the requests are for STS domain. This can be achieved with a simple if condition on the domain/ host or by using the Map on IAppBuilder interface.
Lets look at the flow:
a. The user tries to access a protected resource using domain 1
b. Since he is not authenticated, he will be redirected to domain STS, with a query parameter for domain1 (for STS to identify which domain he is accessing the resource from) and the url for the protected resource on domain1
c. As the request is for STS domain, the custom middleware kicks in and authenticates the user. And sends two cookies one for STS and the second one for whatever the domain (in this case 1) he is trying.
d. Now the user will be redirected to the protected resource on domain1
e. If he tries to access protected resource on domain 2, he is not autheticated hence will be redirected to STS.
f. Since he had an authentication cookie for STS that will be attached with this request to STS by the browser. The STS middleware can validate the cookie and can authenticate the user. If authenticate, issues another cookie for domain 2 and redirects him to the protected resource on domain2.
If you closely look at the flow it is similar to what we do when we have an external STS, but in our case the STS is our application. I hope this makes sense.
If I had to do this task, I would use an external STS sitting on the same host (IIS). IdentityServer, an opensource implementation of OpenID Connect standard, is what I would use as STS. It is extremely flexible in terms of usage and can be co-hosted with our application (which I think is great deal in your case). Here are links Identity server, Video
I hope that this is helpful. Please let me know if you have any questions.
Thank you,
Soma.

Categories