I am working on a multi-tenant solution with Azure AD with web apps and a web api. The web app uses OpenIdConnect to retrieve a bearer token (which is cached in Azure Redis Cache), which is used in Angular to get JSON from the web api. User impersonation is used between the web app and web api (set up in Azure AD applications).
Problem:
This works fine for about an hour, then the Identity suddenly disappears on the web api side. If I refresh the web app, I see that the page is redirected to the Microsoft login page, but no action is required since the user is just redirected back to the web app and everything works again. As far as I can see, the web app uses the same bearer token when it fails and after the refresh (same expire time) when it works again. AuthenticationContext.AcquireTokenSilent works in both scenarios.
I have tried to increase a lot of different timeouts, but nothing has helped. I have also disabled all but bearer token authentication on the web api. I do not understand why the identity disapears and why it helps to refresh the client. Any ideas? :)
Additional info
This is how the RequestContext.Principal.Identity looks for about an hour after login or a refresh (on the web api):
And this is after about an hour, which causes authentication to fail:
Some of the code changes I have tried out:
In web api HttpConfiguration:
config.SuppressDefaultHostAuthentication();
config.Filters.Add(
new HostAuthenticationFilter(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions().AuthenticationType));
This changed the unauthenticated principal from WindowsPrincipal to ClaimsPrincipal, but it still fails after an hour.
WindowsAzureActiveDirectoryBearerAuthenticationOptions BackChannelTimeout set to 5 days. Still fails
In the web app web.config:
sessionState timeout="525600" for RedisSessionStateProvider. Still fails
In the web app owin auth process, increased timespan and added sliding expiration. Still fails:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieSecure = CookieSecureOption.Always,
ExpireTimeSpan = TimeSpan.FromDays(5),
SlidingExpiration = true,
CookieHttpOnly = true
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = Constants.CommonAuthority,
UseTokenLifetime = false
Update:
To flesh out some details: We have a hybrid MVC Angular web application. Many MVC menu items which each lead to an Angular "single page application" for that menu item. MVC is used for routing, authentication and authorization. In addition, additional claims is retrieved and appended to the current principal server side. The menu items are MVC controllers that are protected with the Authorized and ClaimsPrincipalPermission attributes. Since the web page will run in Azure, we changed the default sessionProvider to Microsoft.Web.Redis.RedisSessionStateProvider. Only the MVC server side talks to this redis session cache. The bearer token (not refresh token) is shared with Angular through an Authorized protected MVC controller, which is then stored in the browser session storage (similar to adal.js use of localstorage?) Angular gets JSON content from a CORS enabled API that lives in a separate domain from the MVC app. The API and MVC app also belong to two different Azure AD applications.
you seem to be crossing flows here. If you are making calls from JavaScript, you should obtain the token in the client - something like http://www.cloudidentity.com/blog/2014/10/28/adal-javascript-and-angularjs-deep-dive/.
Redirect based authentication flows in which the outcome is a cookie are not well suited for scenarios in which you call API via JavaScript. Furthermore, if I understood correctly you are obtaining a token as a private client and then sharing it out of band (redis cache) with a public client running inside a user agent. That's a no-no from the security perspective.
That said: if you are really really set in keeping up with your current route, I suggest taking a look at http://www.cloudidentity.com/blog/2014/04/28/use-owin-azure-ad-to-secure-both-mvc-ux-and-web-api-in-the-same-project/ for achieving full separation between your web UX and web API routes.
Related
I'm running asp.net core 3.1 and IdentityServer4 following the quick start and guides.
I'm a bit lost when implementing IS end points and api endpoints in the same project. I want the server to host IS authentication/login urls, apis and mvc client. I want to issue bearer tokens which will be used by the IS host/mvc client and client credential clients.
I have gone through the quick starts and gotten the MVC client and the console client to authenticate with my identity server host, but when I try to use my identity server host to authenticate and then go to one of the view controllers, I just get a circular workflow back through authentication.
I believe it's because my account controller login post method isn't issuing the bearer token. I'm thinking there's a call I need make to identity server during login to get it to set the bearer cookie. (It's also possible I'm just missing something on my view controller to tell it it's part of the group to allow access?)
I have this in which allows the client credentials from the console app to work:
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = Constants.HostUrl;
options.RequireHttpsMetadata = false;
options.Audience = "api";
});
But if I remove it, then the login workflow through the host identity server works and I can access my view and api controllers.
Is there a quick start that I'm missing that shows the identity server also being the mvc client?
Thanks
when I try to use my identity server host to authenticate to itself, I just get a circular workflow back through authentication.
Well, what does it mean for itself?
Do you try to ask for token for the service that is used to host the REST API ?
It looks like you have some kind of client (like background service whatever) and is trying to get token for that client.
Is it so?
Seems that you can use the client credentials flow However this requires clientid and secret to be stored somewhere. Here is some documentation.
You can use Identity server's Windows Authentication feature also
Currently I am working in a Solution with next projects:
IdentityServer
Web Application
WebApi Aplication
Until now Identity Server its authenticating Web Application pretty well, however, once a user has been successfully authenticated on (2) I need this user could authenticated on (3) with the same credentials. Since I am calling Services from (3) with JavaScript (from browser).
I tried to to with oidc-client and manually by setting "Authorization" Bearer token without any success.
So you have any idea how to deal with this?
So we are currently looking to build out a site that utilizes a Web API for all of our authentication and communication with the database. What we are unsure of at the moment is how to persist users in our MVC application by authenticating through the Web API.
Where does this happen on the MVC side of things and how should we be authenticating with the Web API?
EDIT: Another thing I am wondering is if we can make a call to the API to get an Identity user after they are authenticated and instantiate the identity user on the MVC side of things and just store them in a session variable to persist on our application. Would this be doable and any idea what it would look like?
MVC utilizes the session for authentication. A cookie is sent to the user, and the web browser sends that cookie back with each request to enable to the server to restore the session and recognize the user as authenticated.
Web Api is REST-based and stateless. There's no concept of a session, cookies, etc. Each Web Api request must be authenticated in the request, usually by passing an Authorization header with a bearer token or similar.
If the MVC application utilizes the Web Api to authenticate, then the Web Api should return an authentication token to the MVC application. The MVC application then, should "log in" the user by setting that normal authentication cookie and save the token so that it can authenticate future Web Api requests with that. In other words, the MVC application still handles authorization as it normally does. The only difference is that the response of the Web Api determines whether or not it considers the username/password combo to be correct, rather than a database query made directly.
I have ASP.NET MVC 4 / Web API hybrid application. The authentication is being handled by an existing application. In looking at securing these types of applications, most articles point to using Forms Authentication along with the [Authorize] attribute on the MVC and API controllers/actions you want to protect. I would like to use the [Authorize] attribute as it will handle both MVC routes and API routes but not sure how to do that without having an actual form and using the built-in membership provider.
Should I go with a simple approach like described here? Or should I create a Custom Membership provider that handles the logic?
For clarity, the workflow would be as follows:
The user logs in through the existing authentication portal.
If authenticated, they are redirected to my application along with some additional data like username and email (so no passwords need to be transferred)
My application sets an authentication cookie that allows the user to continue using the application.
Any help would be greatly appreciated.
Option 1
If your existing login portal is using Forms Authentication, then you can share the encryption keys between the applications so that they can read the authentication cookie created in the login portal:
How can I share .net (C#) based authenticated session between web forms and MVC2 applications?
Option 2
If you're not using forms authentication in the login portal, you could keep your existing process, but add on a step that manually creates the authentication cookie. There are lots of variations on this, but this question show a higher level way in the question, and a lower level method in the answers:
How can I manually create a authentication cookie instead of the default method?
This also requires the encryption configuration be the same between the applications.
Option 3
Otherwise you are either left with using an SSO protocol for the handoff. You can keep the existing authentication process for your login portal, but will need to add additional code to coordinate the SSO handoff to the other application. SSO came about because other than encrypted cookie methods used in #1 & 2 above, there is little other secure options for doing a browser redirect and communicating authentication.
Creating your own cookie based method is risky and may open up security holes that you don't foresee.
"Should I go with a simple approach like described here?"
The important thing about that example is it's not clear where username is coming from in SetAuthCookie(username, .... That question implies that the user will login to the additional application and that app will query the web service to determine if that login is valid. In this case it's not single sign on with a dedicated login portal, but instead each app collects login information and asks the web API if it is valid. In your case, you do not want to collect the login information in each portal, but instead detect that they've already logged in to the deicated login portal.
So the problem is how does the login protal tell you in a secure fashion what username is when you call SetAuthCookie(username, .... That's exactly what SSO is for. Using a SSO handoff, one site can tell the other in a secure fashion that "I'm sending Bob123 to you, and you can be sure it is really Bob123 and not someone else.
Options #1 and #2 get around this by having the login portal set the cookie instead, and by sharing keys across the apps, the other apps can securely read that cookie.
Note you can't do this with just any cookie. The forms authentication cookie is built in a certain way to prevent forgery of the cookie and other tampering.
SSO becomes your only option if you are going across domains because cookies written in one domain cannot be read in another(the browser only submits cookies for the current comain).
There are workarounds for forms authentication with multiple subdomains sharing a root domain:
Proper creation of a cross-domain forms authentication cookie
There are various hacks for redirecting to another site with encrypted information and letting that site write the forms authentication cookie, but most of them are just horrible hacks that are just as complicated as SSO.
You seem to try to reinvent a Single Sign-On protocol. Instead, there are existing SSO protocols like OAuth2 or WS-Federation you should definitely learn about.
In general, SSO protocols work similarily to what you expect your "workflow" to behave. The exact flow can differ but this is always the Identity Provider that authenticates/authrorizes users and the IdP somehow passes this information to the application that makes use of it (for example, the application issues a custom cookie to establish user authentication).
The Authorize attribute is not intended to be used with Forms Authentication only. Any authentication module that sets the principal for the request lifetime can replace Forms. For example, the Session Authentication Module is often used nowadays as it fixes some particular issues of the Forms module (e.g. the inability to persist long user data).
If you need a good free book on SSO, take a look here:
http://msdn.microsoft.com/en-us/library/ff423674.aspx
You can use Owin to handle this. Here's a code snippet I am using to authentication using Facebook, it also uses cookie:
using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;
namespace ASPNetMVC53rdPartyAuth
{
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
// Use a cookie to temporarily store information about a user logging in with a
// third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// third party login providers:
// You have to register this app at https://developers.facebook.com/ and get the
//appId and appSecret.
// Facebook requires SSL, so that need to be enanbled. Project url can be found
// under project properties and can be localhost.
app.UseFacebookAuthentication(
appId: "xxxxxxxxxxxxxxxx",
appSecret: "xxxxxxxxxxxxxxxx");
);
}
}
}
I'm using the Client Application Services for Client (WPF) authentication with ASP.Net membership, which is working just fine. However, on the server I have additional MVC queries that I must authenticate when calling them from the client. When looking at the available Membership.ValidateUser call, I fail to see how this helps me in any way, as I need to validate every single call.
I assume sticking [Authorize] on each MVC call is the first step.
Can I get a security token, or extract a cookie for the CookieContainer, or am I simply misunderstanding something here?
I am assuming you ASP.Net MVC site, WCF services are hosted in same virtual application and ASP.Net compatibility mode is on.
If you are using WPF to authenticate your user by calling a web service do the following
On Server side
Implement code to do authentication
Once authentication is successful
create a form authentication cookie
and add it to response cookie
collection.
On the client side
The client class that you are using
to connect to authentication service
should contain a static CookieContainer class instance.
Once authentication is
successful add the cookie received to
this cookie container and pass it along every subsequent request.
Hence forth all request to ASP.Net MVC application or any service would contain the cookie and the user would get authorize automatically. Check this blog post for sample
Hope this helps.