Infinite re-direct loop after AAD Authentication when redirect is specified - asp.net-mvc-5

If I specify a redirect URI in my OpenIdConnectAuthenticationOptions like so
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
Then I get an infinite re-direct loop. This only happens though when i put it on and standalone IIS Server (our test server). If i remove all the Replay url's in AAD and leave it only setup for the test server, and remove the "RedirectUri = redirectUri," from the above my problem goes away.
I have a fiddler log here : https://drive.google.com/file/d/0B5Ap95E_wdyAa0RLLWloZ0dCaGM/view?usp=sharing
It appears that when request from AAD comes back to my app, before the token is grabbed and used, the Middle Ware is just bouncing it right back with a 302. Also what may be important, I have the [Authorize] attribute over the mvc controller that the routing and return uri directs to. If I remove it i do not get this issue.
[UPDATE]
I tried moving the application to my localhost install of IIS rather than using iisexpress so that i could setup as a SubApplication like it is on my iis server. On my localhost it does the same infinite loop. I added in some telemetry custom events on an override of the [Authorize] attribute and have been able to discover that when page is re-directed back to the application after authentication httpContext.user.identity.IsAuthenticated = false. So somehow the OWIN middle ware is not setting this to true?
Thanks for any help!

I was able to find a solution to my problem. Originally i was specifying my reply url's to point to the root of the site. My rout config looks like this:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Welcome", action = "Index", id = UrlParameter.Optional }
);
If I append "Welcome" to the end of my reply url it works. For some reason if i leave the reply url to the root of the site and have the default route picked up it just goes into an infinite loop.
I found out also that this only applies to a sub application of a site. I tried moving my application to be a standalone site in iis so rather than and I didnt have to add the controller name in the reply url.
Example:
Original reply url:
mysite.mydomain.com/CustomApp
New Reply url:
mysite.mydomain.com/CustomApp/Welcome
Hope someone else can find this useful!
UPDATE
I found out that the root of the problem was still caused from this mvc5 bug: katanaproject.codeplex.com/workitem/197. I thought it had been fixed but it has not, so i will continue to use the well known Kentor Owin Cookie Saver: github.com/Sustainsys/owin-cookie-saver

Solved by using Never option for CookieSecureOption
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieSecure = CookieSecureOption.Never
})

Related

Azure AD : how to redirect to sign in page by code manually

I followed this official sample to integrate Azure AD authentication into my ASP.NET Core application. Everything is working well, and I can call the MS Graph API successfully.
The problem is that after I signed in, I kept the browser opening and then I shutdown my application (simulate the server crashed), then I run the application again, then refreshing the page, I got an exception as shown in this screenshot:
I debugged the code, and I found that the request went into my controller (I had [Authorize] on my controller so it should redirect to sign in page when app found the request doesn't be authenticated) and the exception appeared when run
var me = await _graphClient.Me.Request().GetAsync();
This means the app thinks the request is authenticated.
[Authorize]
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly GraphServiceClient _graphClient;
public HomeController(GraphServiceClient graphClient, ILogger<HomeController> logger)
{
_logger = logger;
_graphClient = graphClient;
}
public async Task<IActionResult> IndexAsync()
{
var me = await _graphClient.Me.Request().GetAsync();
ViewBag.Myname = me.DisplayName;
return View();
}
}
The reason for why the app not redirected to sign in page is that the browser stored cookie, after removing the cookie manually, the exception wouldn't appear and app would redirect to sign in page.
I think I need a global filter to handle the exception, by removing the cookie (I'm not sure if it can remove the cookie) and then redirect to sign in page manually, but I failed to find any document to describe how to redirect to sign in page manually by code.
Any document or code sample is appreciated.
By the way, should we put effort on this kind of scenario?
• As per my understanding on your query regarding the redirection to sign in page manually through code, I would suggest you to please go through the below code which suggests the same mechanism as you are expecting, i.e., delete the cookie from browser memory for the session signed in and redirect to the sign in page again for login to a new session, but the below code is for ‘laravel’: -
public function logout(Request $request)
{
$cookie = \Cookie::forget('first_time');
$this->guard()->logout();
$request->session()->invalidate();
return $this->loggedOut($request) ?: redirect('/')-
>withCookie($cookie);
Also, you can use ‘\Cookie::queue(\Cookie::forget('first_time'));’ to avoid creating the cookie variable and redirecting with it.
Kindly find the link below for more details: -
https://laracasts.com/discuss/channels/laravel/cookie-is-not-deleting-using-cookieforget-after-logout
• Similarly, I would suggest you follow the cookie-based identity authentication method using the ASP .Net Core sample code as below: -
using Microsoft.AspNetCore.Authentication.Cookies;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication
(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.SlidingExpiration = true;
options.AccessDeniedPath = "/Forbidden/";
});
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapDefaultControllerRoute();
app.Run();
For more information, kindly find the documentation link below which explains the cookie authentication configuration in detail: -
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0

Azures Arr Affinity response cookie

Once you consume and set Azure ARRAffinity response cookie and send it back to Azure, are you supposed to get it back with next response ?
I just completed bit of code what brings Azure response cookie all the way to browser, sets it as a session cookie and then I pass it back to Azure in request as a cookie. To my surprise I am not getting this cookie back, I see it only the first time. However I have a feeling this might be expected behaviour - I could find anything in the documentation. When I try to change the cookie to some made up value, the correct cookie is returned with the next response.
public class RestRequestWithAffinity : RestRequest
{
public RestRequestWithAffinity(string resource, IRequestWithAffinity request)
: base(resource)
{
if (!string.IsNullOrEmpty(request.AffinityValue))
{
AddCookie("ARRAffinity", request.AffinityValue);
}
}
}
var request = new RestRequestWithAffinity(url, feedRequest)
{
Method = Method.GET
};
// cookie doesn't come back when already in request
IRestResponse response = await _client.ExecuteTaskAsync(request);
Yes, you supposed to get it back with next response. You can take a look on the following link:
http://azure.microsoft.com/blog/2013/11/18/disabling-arrs-instance-affinity-in-windows-azure-web-sites/
if you create the cookie, than choose a different name and everything will be fine! ARRAffinity is a reserved name by the IIS ARR Module. And that's why you may see this misbehavior.
Also pay attention that if you use the public Microsoft provided domains (i.e. yourdomain.cloudapp.net or yourdomain.azurewebsites.net) you cannot set the cookie at top domain level - i.e. you cannot set cookie for the cloudapp.net domain or for the azurewebsites.net domain. You shall always use the full domain, including any subdomains to set the cookie - i.e. yourdomain.azurewebsites.net.
Take a read here for more information about that issue: https://publicsuffix.org/learn/

WebClient with credentials still not downloading file

I am trying to download files from a website with username/password. You need to pay for a registered account in order to download files - which we have done. I am attempting to pass in the username/password and download a file as follows:
if (docUrl != null)
{
if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
this.WebClientInstance.Credentials = new NetworkCredential(username, password);
fileData = this.WebClientInstance.DownloadData(docUrl);
this.WebClientInstance.Dispose();
isDataDownloaded = true;
}
WebClientInstance is a System.Net.WebClient. I debugged and verified that it is hitting the line to set credentials. Instead of downloading the PDF, I end up with an HTML page that prompts me to log in to get access to the file. I have verified that the username/password is correct. I use the same credentials to scrape the website with WatiN.
Is there something else that I'm supposed to be doing here?
UPDATE
Okay, I've done some sniffing around and found some useful info on this issue. I still haven't gotten it to work, but I think I'm closer. First, you need to create a cookie aware WebClient that extends the WebClient class, as follows:
public class CookiesAwareWebClient : WebClient
{
public CookieContainer CookieContainer { get; private set; }
public CookiesAwareWebClient()
{
this.CookieContainer = new CookieContainer();
}
protected override WebRequest GetWebRequest(Uri address)
{
var webRequest = base.GetWebRequest(address);
if (webRequest is HttpWebRequest)
(webRequest as HttpWebRequest).CookieContainer = this.CookieContainer;
return webRequest;
}
}
Next is to use the WebClient.UploadValues() method to upload the login info to the target website. The full process of authenticating and downloading the target resource is as follows:
using (var webClient = new CookiesAwareWebClient())
{
var postData = new NameValueCollection()
{
{ "userId", username },
{ "password", password }
};
webClient.UploadValues(docUrl, postData);
fileData = webClient.DownloadData(docUrl);
}
I was wrong about the site using forms auth. It is a JSP website and uses a JSESSIONID. I have verified that I am getting a cookie back with what appears to be a valid 32-byte JSESSIONID value.
However, when I call WebClient.DownloadData() it is still only returning the redirected login page. I've tried to fix this by setting the AllowAutoRedirect property on the HttpWebRequest to false, but then it returns 0 bytes.
Is there something else that I need to do so it won't redirect and will take me to the resource once I have authenticated?
(Answered in a question edit. Converted to a community wiki answer. See Question with no answers, but issue solved in the comments (or extended in chat) )
The OP wrote:
Solved. So the problem was between my ears. I was passing in the URL for the secure resource to the .UploadValues() method, knowing that it would redirect to the login page. However, I really needed to pass in the URL from the login form (where it goes upon submitting) - not the login page itself. Once I did that, it worked correctly. I think I'm going to go find a career in food service now.
LINKS
There were already a few questions posted on SO that addressed this issue. I just didn't know what I was looking for at first so I didn't see those... Anywhere here are a couple good resources that I came across when working on this issue:
how to maintaine cookies in between two Url's in asp.net
Trying to get authentication cookie(s) using HttpWebRequest

Why does CredentialCache.DefaultCredential contain empty strings for domain, username, and password

Does anyone have any ideas as to why CredentialCache.DefaultCredential would return an ICredential instance with empty strings for domain, username, and password? I'm running a WCF service on IIS 7.5. It works fine on one server but never works on another. I have verified that the IIS application has Windows Authentication enabled....
Here is how it's being used:
string url = string.Format("{0}/departments/finance/_vti_bin/listdata.svc", _IntranetAddress);
var financeDataContext = new FinanceDataContext(new Uri(url))
{
Credentials = CredentialCache.DefaultCredentials
};
I am not sure how it is working in one of your servers? I hope you already read this
http://msdn.microsoft.com/en-us/library/system.net.credentialcache.defaultcredentials.aspx
but it clearly says "The ICredentials instance returned by DefaultCredentials cannot be used to view the user name, password, or domain of the current security context."
The NetworkCredential returned from CredentialCache.DefaultCredential is just a placeholder. If you look at it using the Debugger, you'll see that it's of type SystemNetworkCredential. Internal API check for this type to see if integrated authentication should be used or not. There are other ways to get the current username (like WindowsIdentity.GetCurrent()).
EDIT:
To specify impersonation for a WCF operation, add this attribute to the method implementing a contract:
[OperationBehavior(Impersonation = ImpersonationOption.Required)]
public void SomeMethod()
{
// do something here
}

SocialAuthManager object ('manager') becomes NULL after redirection in case of SocialAuth libraries with JSF application?

I am using SocialAuth libraries in my JSF application for providing 'login with google/facebook'. As shown below it requires me to stores the SocialAuthManager object ('manager') in the session and then redirect to 'google/facebook' URL
//Create an instance of SocialAuthManager and set config
SocialAuthManager manager = new SocialAuthManager();
manager.setSocialAuthConfig(config);
// URL of YOUR application which will be called after authentication
String successUrl= "http://opensource.brickred.com/socialauthdemo/socialAuthSuccessAction.do";
// get Provider URL to which you should redirect for authentication.
// id can have values "facebook", "twitter", "yahoo" etc. or the OpenID URL
String url = manager.getAuthenticationUrl(id, successUrl);
// Store in session
session.setAttribute("authManager", manager);
Then get the 'manager' from session on succssfull/failure redirection from facebook/redirect as shown below:
// get the social auth manager from session
SocialAuthManager manager = (SocialAuthManager)session.getAttribute("authManager");
// call connect method of manager which returns the provider object.
// Pass request parameter map while calling connect method.
AuthProvider provider = manager.connect(SocialAuthUtil.getRequestParametersMap(request));
// get profile
Profile p = provider.getUserProfile();
The problem is if I am already logged in to facebook or google in a one of the 'tab' of the browser then this works perfectly OK. But if I am not logged in already then session becomes NULL and consequently 'manager' as well.
In other words if redirection from 'my application to facebook to my application' happens then it fails. If I am already logged in to facebook then redirection does not happens and it works.
Can someone help?
NOTE: this works perfectly well in case of IE but does not work in case of Chrome & Mozila
the reason for this behavior is that you are calling the redirected page from different domain so when page redirection happens your session data is lost.
please have a look at this link
http://31stdimension.blogspot.in/2012/04/how-to-connect-facebook-using-jsfjava.html

Resources