I have a very basic Single Sign On app built on VS 2015 using MVC and Web Forms. It is supposed to be a simple proof of concept and is based on some code found here and here which are essentially the same things. I've finally gotten it all converted to use .Net 4.5 but when running it on my local server it throws a 404 with no debug information.
The 404 itself wasn't initially a surprise as I was supposed to be able to change the url to one of the secure pages (for instance /WebSecApp1) which would redirect me back to the signon page but no matter what I put as the url I get the 404.
I've also tried changing the urls in the code so that they contain the port numbers for the localhost but that doesn't work either.
It was suggested to me that the RouteConfig.cs could be the culprit but I don't see how that could be since I'm calling a single page with no parameters.
I know this is kind of lite on details but does anyone have any suggestions?
Yes this looks like a routing issue as you also thought it to be. Routing is essential for web api too .Pls see https://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection. Does your api request look like this
GET http://localhost:34701/api/products/1?version=1.5&details=1
You do have to mention the port in the request.
While the routing that Arathy mentioned above was partially to blame, the real problem turned out to be relatively simple. In my case simply selecting Properties->Web for each of offending pages and setting "Override application root URL" to checked fixed the whole problem.
I have an MVC project that works fine when in VS.
But when I publish it and use it in IIS, a search page always gives this result:
No web page was found for the web address:
My controller has these two options:
[HttpPost]
[Route("[controller]")]
public IActionResult Search(SearchModel model)
&
public IActionResult Index(SearchModel model = null)
The page will load normally but once I perform the search in IIS it just wont work, any idea why this would be when VS handles it just fine.
Just incase this incredibly rare issue occurs for someone else!
I have finally managed to correct the fault by slowly removing code from the error page until it finally loaded.
The problem was making a call to a method in a class that made use of HtmlAgilityPack and Fizzler.
For some reason MVC cannot handle it, and not a single error of relevance was offered anywhere!!
I uninstalled both and found an MVC specific version in nuget packages.. Lord!
I have a controller method which was previously working and it's not clear what change, if any, has caused the following problem:
The controller is not hit. There is no exception, no response, no status code or indeed any response from the server (checked in Fiddler). The request eventually times out.
UPDATE
See my answer below. Also see my follow up question:
Web API Controller method executes to end. No HTTP response. Hangs
Is your project under source control? You mention that it was previously working. If you're under source control you may be able to rollback changes to your controller, test and isolate the change that caused the problem.
Another thing to check would be changes to routing. Perhaps your controller hasn't changed, but something with routing did. Routing can be tricky, there are some tools to help. A quick Google search came up with this:
https://blogs.msdn.microsoft.com/webdev/2013/04/04/debugging-asp-net-web-api-with-route-debugger/
I haven't used it myself, but I may in the future as I've run into routing problems before.
This problem appears to be an artefact of the actual problem; which appears to be that the browser/middleware/web-app are queuing the requests and previous hung requests are causing the behaviour described eventually noticed to be 'waiting for available socket...')
What happens in a clean room situation is slightly different (the controller method executes to the end and returns a result, however there is no HTTP response) and warrants a new question, see Web API Controller method executes to end. No HTTP response. Hangs
I've created a new MVC5 Web Application, and when I try to login with Google or Facebook, the ExternalLoginCallback Action in the AccountController is called, but GetExternalLoginInfoAsync() always returns null:
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
Because it's always null, it just redirects back to the login page and the process starts over. How can I fix this?
To get OWIN Google login to work properly on a standard Visual Studio 2013, ASP.Net MVC5 site, I had to:
Setup a Google OpenId account at https://console.developers.google.com/project
Set the callback URL there to blah/signin-google. Important notes on things you don't need to do:
You don't need to use HTTPS for Google to redirect back; you can even redirect back to plain http://localhost, no problem.
You don't need to setup anything for the redirect URL - no routes, Controller Actions or special permissions in Web.Config. The redirect URL is always /signin-google and OWIN handles this behind the scenes for you.
As an example, if your site was me.com, you might have these 3 callback URLs in the Google Developer Console:
http://localhost:53859/signin-google
http://test.me.com/signin-google
https://me.com/signin-google
The first one including whatever port number VS gave you for your project.
Enable the Google+ API. This is one hidden b**** of a gotcha and is the root cause of the problem in the question here - if you don't do this, it's easy to miss that the Request to /account/ExternalLoginCallback includes &error=access_denied, and that's because Google said no to a permissions request OWIN made for the user's Google+ basic profile. I can't tell whose fault this is, Google's or Microsoft's.
To enable the Google+ API in the Developers Console, click APIs on the left, hunt for Google+, click that and hit Enable. Yes you really do need to do that. You're hosed if you don't do that.
Add the ClientId and ClientSecret Google gave you in the Developers Console to Startup.Auth, but improve the code in the process to explicitly use OAuth2, and explicitly ask for the user's email address:
var google = new GoogleOAuth2AuthenticationOptions()
{
ClientId = "123abc.apps.googleusercontent.com",
ClientSecret = "456xyz",
Provider = new GoogleOAuth2AuthenticationProvider()
};
google.Scope.Add("email");
app.UseGoogleAuthentication(google);
That's it. That finally got it working.
Just want to reiterate one more time, there are a LOT of answers about this and issues like it where OWIN/Google isn't working, and nearly all of them are wrong for the current VS2013/MVC5/OWIN template.
You don't need to modify Web.Config at all.
You don't need to create any special Routes whatsoever.
You should not attempt to point /signin-google to a different place, or use a different callback URL, and you definitely shouldn't attempt to tie it directly to /account/externallogincallback or externalloginconfirmation, because those are both separate from /signin-google and necessary steps in the OWIN/Google process.
OK, I found out why it's null. You have to enable Google + API in the Google console. Also make sure the secret key is not concatenated with a space at the end after you paste it to your code. Why can't they return a normal error? I don't know.
It seems that Nuget package Microsoft.Owin.Security.Facebook version 3.0.1 no longer works with Facebook Login.
Update this package to the pre-release 3.1.0 version, you can use the following:
Install-Package Microsoft.Owin.Security.Facebook -Pre
As others correctly mentioned, most of the time that's because you do not have permission to the Google+ API so here is how to get permission for a project in Google API Manager to Google+ API
Step 1. Select You Project from the top combobox and go to Dashboard > Enable API
Step 2: Search for Google plus and select it
Step 3: Enable it!
if you return to dashboard for that project you can see the list of enabled API's for that project at the bottom
I got it to work by simply updating all the nugget package in the application and it worked.
I know it's silly, but after a long struggle, restarting IIS solved the issue for me.
This solved my problem:
Enable the Google+ API. This is a gotcha and is the root cause of the problem in the question here - if you don't do this, it's easy to miss that the Request to /account/ExternalLoginCallback includes &error=access_denied, and that's because Google said no to a permissions request OWIN made for the user's Google+ basic profile. I can't tell whose fault this is, Google's or Microsoft's.
To enable the Google+ API in the Developers Console, click APIs on the left, hunt for Google+, click that and hit Enable.
I did the following to get it working.
Logon to the developer portal, locate your application and do the following.
App details > App centered Listed Platforms > Select Yes for website
I ran in to this issue today and it turned out that I defined the remote cookie after I assigned the providers.
Make sure you place...
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
before...
app.UseFacebookAuthentication(
appId: "",
appSecret: "");
I wanted to contribute to this one also. I just recently got this working. I had the problem with the GetExternalLoginInfoAsync returning null but only in production.
After a lot of searching I finally found my answer it was simply a problem with my database. In production I had set the wrong connection string so it would not connect properly but it was basically silent about it. The only thing that happened was GetExternallLoginInfoAsync returned null. So check you database connection string if this happens!
Also on a sidenote, the only thing that was needed to get this working was:
Set up a project in the Google console
Enable Google+ API
Copy your client id and client secret to the Startup.Auth.cs file.
You do not have to enable HTTPS, you do not have to create custom routes. But make sure your database is working properly!
For those who are experiencing this problem for Web Api. Other solutions doesnt help AuthenticationManager.GetExternalLoginInfoAsync(); returns always null even google plus api is enabled.
use this custom function to get logininfo. obviously Microsoft has a bug for GetExternalLoginInfoAsync when requesting over web api.
private async Task<ExternalLoginInfo> AuthenticationManager_GetExternalLoginInfoAsync_WithExternalBearer()
{
ExternalLoginInfo loginInfo = null;
var result = await Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalBearer);
if (result != null && result.Identity != null)
{
var idClaim = result.Identity.FindFirst(ClaimTypes.NameIdentifier);
if (idClaim != null)
{
loginInfo = new ExternalLoginInfo()
{
DefaultUserName = result.Identity.Name == null ? "" : result.Identity.Name.Replace(" ", ""),
Login = new UserLoginInfo(idClaim.Issuer, idClaim.Value)
};
}
}
return loginInfo;
}
Although the answers above are all good, in my instance none of these worked - I'd checked and double checked the Google settings and agree with Chris Moschini that there's a lot of misleading info.
For me it was a 'doh moment when I realised that my Out of Process state service was not started! No errors (as a login was the first thing I was attempting after a reboot where the state service is set to manual start-up on the machine) just a Null from GetExternalLoginInfoAsync
Hope this helps someone else out.
After much searching and head scratching as well as following numerous red herring answers here on Stackoverflow I eventually went through all my options on my Google dev console and discovered a little blue [Enable] button on the Google+API overview page. I clicked this and hey presto it worked. Forget all the baloney you read about callback url and route configs, OWIN overrides the google default /signin-google redirect uri in any case and sends you back to ExternalLoginCallback. Just stick with the default implementation all will be good so long as you enable your Google+API.
It is true that you are going to need the Google plus Enabled. The big thing for me was the project URL. Open the properties window (View -> Properties Window) in VS and then right click the project and select properties. In small properties window copy your SSL URL, and then in the larger properties window select the Web tab and paste that URL in the Project URL.
Fixed the issue for me.
See in greater detail: https://learn.microsoft.com/en-us/aspnet/mvc/overview/security/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on
For me I was migrating and old but working .NET 4.6.1 MVC website to core 1.1. Work stopped before I could get it working, when I picked it back up, I was then migrating to 2.x.
My problem was that the callback from Google was met with a 404 from my site. I thought it was supposed to hit AccountController.ExternalLoginCallback so I added a [Route(...)] to it and sure enough, Google's callback hit the action.
This then hit the null returned in this line (what kind of maniac returns a null?)
var externalLoginInfo = await this.SignInManager.GetExternalLoginInfoAsync();
I reverse engineered it to find under the hood its ultimately getting the handler for ExternalScheme which for my was the cookies handler!
It all seemed wrong and I felt somehow that the middleware was supposed to just intercept the callback URI so I removed my [Route(...)] and the 404 problem came back.
I then found that I need to add this during startup.
applicationBuilder.UseAuthentication();
This solves the 404 but gives another issue.
No authenticationScheme was specified, and there was no DefaultSignInScheme found.
By adding a default scheme here, I resolve the error above.
serviceCollection.AddAuthentication(IdentityConstants.ExternalScheme)
.AddGoogle(googleOptions => Configuration.Bind("OAuth2:Providers:Google", googleOptions))
.AddExternalCookie();
Now AccountController.ExternalLoginCallback is invoked again by some magic but I am back to the null return value.
I added this code above the offending line, which is essentially what is happening under the hood (looking at Microsoft's code on GitHub). Interestingly, h is of type CookieAuthenticationHandler and a has all my claims and information from Google inside!
var authHandler = this.HttpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
var h = await authHandler.GetHandlerAsync(this.HttpContext, IdentityConstants.ExternalScheme);
var a = await h.AuthenticateAsync();
var externalLoginInfo = await this.SignInManager.GetExternalLoginInfoAsync();
Digging into GitHub and copy pasting internal code its running into my controller, I can see that it's failing to find ClaimTypes.NameIdentifier in my claims, this is the ProviderKey used later.
Hmm....
Concerned I was using old 1.x AccountController code with newer 2.x identity bits I did find some samples that still use this stuff, and some samples that use Razor Pages for it all, too. I'll continue with what I have.
So I'm next going to investigate mapping additional Google user JSON payload items into the claims. I think if my Google account ID (numeric, I guess) was mapped then everything would work.
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/additional-claims?view=aspnetcore-2.2
Final Fix
I finally resolved the issue by adding a "claim action" to pull my Google identifier out of the JSON coming back from Google!
serviceCollection.AddAuthentication(IdentityConstants.ExternalScheme)
.AddGoogle(googleOptions =>
{
Configuration.Bind("OAuth2:Providers:Google", googleOptions);
googleOptions.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub", "string");
})
.AddExternalCookie();
The sub field contains what eventually ends up in the nameidentifier claim and then into the ProviderKey that the AccountController wants.
In my case the solution was update Nuget package Microsoft.Owin.Security and Microsoft.Owin.Security.Google
All of the other answers didn't solve this for me, so if your in the same boat then make sure your registration controller action has the RequireHttps attribute:
// GET: /Account/LoginRegister
[AllowAnonymous]
[RequireHttps]
public ActionResult LoginRegister()
{
return View(new RegisterLoginViewModel());
}
I am working on a site which is programmed in C# .net. It uses a CMS called ADX Studio (a decision which predates my time there) which provides a shonky form of URL Rewriting (as far as I can tell it works by assigning an aspx page as the default 404 handler in IIS).
I have an web form which lives at a rewritten URL. I edited it so that the html form's action points back to the rewritten URL:
var u = new Uri(Request.RawUrl.Split(new char[1] { ';' }).Last());
userAdminForm.Action = u.PathAndQuery;
(kind of ugly but works based on what Request.RawUrl is on these rewritten URLs).
The "pretty" URL is something like this:
http://www.site.com/admin/user/edit/
On my development box (Windows XP/ IIS 5) when I initially tried POSTing back to URLs like this I got a HTTP 405 error. I worked around this by adding a script mapping so Aspnet_isapi.dll handles all (*) requests. And everything works fine on my development machine.
I just pushed my changes to the live server (Windows Server 2003 R2 and IIS 6) and the post fails silently. The page refreshes but all of my logic (from within an IsPostBack path in the code) doesn't get hit. No errors are displayed, it just doesn't work.
If I remove my code setting the .Action of the form then the postback works but it is posting to the ugly URL corresponding to the physical location of the aspx file rather than my page.
Am I missing a simple way to make this work? I don't want to be switching URL rewriting method or anything as this is a large legacy site and is unfortunately pretty dependent on ADX Studio so I don't want to do anything that will break that.
[edited because somehow the code above lost its code highlighting]
The issue is that the page's <form> tag is referencing the "ugly" url as the action. You can resolve that by completely removing the action tag from the form. Browsers will, by default, postback to the same page, ie. the "pretty" url.
This article explains how to accomplish an "actionless" form (~ two thirds of the way down) http://msdn.microsoft.com/en-us/library/ms972974.aspx
It seems like the problem is the same as it was on IIS 5. I can get it to work by doing the following in the IIS Manager:
Right click on the relevant website and select "Properties"
Choose the "Home Directory" tab
Click "Configuration" down in the "Application settings"
Click "Insert" next to the "Wildcard application maps"
Browse to the location of aspnet_isapi.dll (in my case: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll )
Untick "Check that file exists"
Click "OK" back through the Russian doll of dialogs.
This is basically the same as the approach that I linked to in the question for IIS5. However, it's not optimal because IIS is running every request through asp (even static files). Which seems like it can only slow things down. I'd like to be able to specify that asp only needs invoking for HTTP POST requests at least.
The weird thing is that IIS5 gave a HTTP 405 error when POSTing to an extension without a registered ISAPI extension but IIS6 just fails silently. And the page is being run through IIS (I can debug with a breakpoint in the Page_Load function) but IsPostBack (and IsCrossPagePostBack) don't get correctly set. Could it be related to the view state? Is there any alternative to my solution described above?
I've come to what I think is an optimal solution for this problem. It turns out that ADXStudio CMS does use the default 404 rule to do some form of URL rewriting. This has a problem with http POST:
when IIS initially executes a custom
URL on a 404 error, it changes POST to
GET, even if the client does a POST
request.
(thanks to elite brains' blog post about setting up IIS6 and ASP.NET MVC).
Rather than creating my own HttpModule I decided instead to use Ionics Isapi Rewrite Filter to rewrite my URLs. I then set the 404 error handler in IIS to the default. And I created this IIRF.ini file to redirect all requests to the same format as the 404 handler produced:
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /Default.aspx?404;http://%{HTTP_HOST}$1 [U,L]
And everything seems to work great. The advantage over my previous answer is that the rewrite code is low level and runs fast and the -f and -d switches mean that if a file actually exists it isn't re-written and so static files don't have the overhead of running through .net.