Azure cloudapp.net domain and duplicate content issue - c#

I have a C#/MVC4 site hosted on Azure as a web role located at http://www.equispot.com. During a check on Google for some searches related to my site, I came across a search result that links to this page:
http://equispot.cloudapp.net/horses-for-sale/quarter-horses/13
Note the difference in the domain name. Now, I have a canonical tag already (view the source on the cloudapp.net link and you can see the canonical rel tag points to the main site at http://www.equispot.com).
Since that's the case, why would Google have indexed the page at the cloudapp.net domain? I recently noticed a drop in my SERPs and I'm wondering if this is part of the reason (I migrated to Azure about the same time as the SERP change). It may be unrelated but still...
How can I prevent these pages from being indexed by Google or how can I prevent my Azure web role from responding to anything except www.equispot.com and equispot.com? When I had this hosted on premise, I just configured IIS to respond only to my domain (my previous provider produced some dupe content for some reason as well).

You can simply check to make sure that the host the application is running under is the domain name you want. If it is not, then simply do a 302 redirect to the domain name you want.
There are several places where you can inspect the request and do the redirect:
- Global.asax
- Custom module
- Override the OnActionExecuting for action methods

I couldn't find a straightforward way to do this using hostHeader configuration in the ServiceDefinition.csdef file so I rolled my own RedirectInvalidDomainsAttribute class to perform a 301 (Moved Permanently) redirect back to my main site during a request for an invalid domain. In case anyone else runs into the same problem, here's the code:
App_Start/FilterConfig.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new RedirectInvalidDomainsAttribute());
}
RedirectInvalidDomainsAttribute.cs
public class RedirectInvalidDomainsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var url = filterContext.HttpContext.Request.Url;
if (url == null) return;
var host = url.Host;
if (host.Contains("equispot.com") || host.Contains("localhost")) return;
string subdomain = GetSubDomain(host);
Guid guid;
if (Guid.TryParseExact(subdomain, "N", out guid))
{
// this is a staging domain, it's okay
return;
}
// Invalid domain - 301 redirect
UriBuilder builder = new UriBuilder(url) {Host = "www.equispot.com"};
filterContext.Result = new RedirectResult(builder.Uri.ToString(), true);
}
// This isn't perfect, but it works for the sub-domains Azure provides
private static string GetSubDomain(string host)
{
if (host.Split('.').Length > 1)
{
int index = host.IndexOf(".");
return host.Substring(0, index);
}
return null;
}
}

Related

Identity server 4 not removing cookie

I have front app on angular 5 and backend api on c# using identity server.
The problem is that when I click logout button, the token is removed and i am redirected to logout page.
But when I try to refresh main page, I am redirected to microsoftonline.com
authenticated automatically and redirected back to main page
I am missing providing username and password here, and this occurs in chrome incognito.
What I noticed is that if I remove manually the cookie from microsoftonline.com
and repeat the process, this time I will be asked for username and password.
So first I tried to clean all cookies this way but it din't help
foreach (var key in HttpContext.Request.Cookies.Keys)
{
HttpContext.Response.Cookies.Append(key, "", new CookieOptions() { Expires = DateTime.Now.AddDays(-1) });
}
bellow is my accountcontroller logout method and cookie screen
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutViewModel model)
{
var idp = User?.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
var subjectId = HttpContext.User.Identity.GetSubjectId();
if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider)
{
if (model.LogoutId == null)
{
model.LogoutId = await interaction.CreateLogoutContextAsync();
}
try
{
await signInManager.SignOutAsync();
}
catch (NotSupportedException)
{
}
}
// set this so UI rendering sees an anonymous user
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
// get context information (client name, post logout redirect URI and iframe for federated signout)
var logout = await interaction.GetLogoutContextAsync(model.LogoutId);
var vm = new LoggedOutViewModel
{
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
ClientName = logout?.ClientId,
SignOutIframeUrl = logout?.SignOutIFrameUrl
};
await persistedGrantService.RemoveAllGrantsAsync(subjectId, "angular2client");
return View("LoggedOut", vm);
}
If I understand correctly you are federating to Microsoft from your IdentityServer4 service? If so when you sign out of your identity service you should also give the user the option to sign out of the external provider (if it supports the relevant feature - it'd need to define an end_session_endpoint in the discovery document).
This functionality is supported by the standard OIDC middleware so you should be able to initiate signout by calling SignoutAsync() and passing the name of the scheme for the MS federated sign in.
Another option is to always send prompt=login in your external sign in requests and then check the auth_time claim you get back. That way to you force interactive sign in always and also verify when it happened.
Try cleaning the cookies from the HttpContext itself, using the extension method, provided by Identity Server, like here.
Or try this:
await HttpContext.SignOutAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme);
in your Logout controller method.
3rd option (what I have in one of my test MVC clients is):
public ActionResult Logout()
{
Request.GetOwinContext().Authentication.SignOut();
return Redirect("/");
}
public void SignoutCleanup(string sid)
{
var cp = (ClaimsPrincipal)User;
var sidClaim = cp.FindFirst("sid");
if (sidClaim != null && sidClaim.Value == sid)
{
Request.GetOwinContext().Authentication.SignOut("Cookies");
}
}
Where the Logout method is called on the button click, and the SignoutCleanup is the one that is passed to Identity Server, when registering the client as a Client.BackChannelLogoutUri (or Client.FrontChannelLogoutUri, or both, depending on your scenario).
PS: Now, in general I think that your approach is not right, but I don't know your full case, so I'm not judging you - just giving and advice.
For front-end clients (Angular, Vue, vanilla JS etc..) it is recommended to use the client-side oidc-client-js library. And here is the usage example. As I said - this is just an advice, but if you are in the very beginning of your authentication setup, I would recommend you to have a look.

ASP.NET Identity 2 and Anonymous Users

In our developing e-commerce solution we are using AspNet Identity 2.2.1 and it is required that any guest (anonymous) users should complete checkout without prior registration to the website. In order to fullfill this requirement have written an ActionFilter named UserMigrationAttribute which obtains SessionTrackId (string GUID) from cookie -which we set from a HttpModule for every request if SessionTrackId is not found along with request cookies- and creates and actual IdentityUser in database with the username something like SessionTrackId#mydomain.com.
We have decorated our BaseController class with this UserMigration attribute in order to utilize its functions throughout the site.
Everything up to this point works as expected with single downside issue, which is when the page is being loaded for the first time for any user, if we try to make an Jquery Ajax Call to a Method which have [ValidateAntiForgeryToken] attribute, the call fails with the 'The provided anti-forgery token was meant for a different claims-based user than the current user.' error, even though we are sending __RequestVerificationToken parameter with every ajax call.
But if user opens another page by clicking link and/or reloads/refreshes current page, all the subsequent ajax calls complete successfully.
In our understanding UserMigrationAttribute creates user on OnActionExecuting method, but after we signIn user in the process #Html.AntiForgeryToken() is not being updated with the right values.
You may find the UserMigrationAttribute code below;
[AttributeUsage(AttributeTargets.Class)]
public class UserMigrationAttribute : ActionFilterAttribute
{
public ApplicationSignInManager SignInManager(ActionExecutingContext filterContext)
{
return filterContext.HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
public UserManager UserManager(ActionExecutingContext filterContext)
{
return filterContext.HttpContext.GetOwinContext().GetUserManager<UserManager>();
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CreateMigrateCurrentUser(filterContext);
base.OnActionExecuting(filterContext);
}
private static readonly object LockThis = new object();
private void CreateMigrateCurrentUser(ActionExecutingContext filterContext)
{
lock (LockThis)
{
var signInManager = SignInManager(filterContext);
var userManager = UserManager(filterContext);
var sessionTrackId = GetSessionTrackId(filterContext);
if (!filterContext.HttpContext.Request.IsAuthenticated)
{
if (!string.IsNullOrEmpty(sessionTrackId))
{
var username = string.Format("{0}#mydomain.com", sessionTrackId);
var user = userManager.FindByName(username);
if (user == null)
{
user = new User() {UserName = username, Email = username};
var result = userManager.Create(user);
userManager.AddToRole(user.Id, StringResources.AnonymousVisitorsGroup);
}
signInManager.SignIn(user, true, true);
}
}
else
{
if (!string.IsNullOrEmpty(sessionTrackId))
{
var username = string.Format("{0}#mydomain.com", sessionTrackId);
var user = userManager.FindByName(username);
if (user != null)
{
if (!HttpContext.Current.User.IsInRole(StringResources.AnonymousVisitorsGroup))
{
var targetUserId = HttpContext.Current.User.Identity.GetUserId<int>();
var service = new Service();
service.Users.MigrateUser(user.Id, targetUserId);
}
}
}
}
}
}
private string GetSessionTrackId(ActionExecutingContext filterContext)
{
var retVal = string.Empty;
if (filterContext.HttpContext.Request.Cookies["stid"] != null)
{
retVal = filterContext.HttpContext.Request.Cookies["stid"].Value;
}
return retVal;
}
}
Any help or suggestions are highly appreciated.
Thank you,
This is happening because the anti-forgery token is set in a cookie, which will not be updated until the next request. If you're manually signing a user in, you should also issue a redirect (even if to the same page they were already headed to), simply to ensure that the cookie data is correct. This normally happens naturally, as the sign in form will redirect to the URL that needed authorization after the user is signed in, thus negating the problem. Since you're not redirecting currently, the data is out of sync.
However, I have to say that this seems like a very poor solution to this particular use case. Creating some sort of temporary-type user and signing that user in to handle guest checkout creates an unnecessary glut of useless data in your database, at best, and leads to bugs and other issues like this one you're experiencing, at worst.
I also run an ecommerce site, and the way we handled guest checkout is incredibly simplistic. The checkout data is just stored in the session (email, shipping/billing address, etc.). We build a view model to handle the actual checkout where the data necessary for submitting the sale comes either from the user object, if they're logged in, or these session variables, if they aren't. If the user is neither logged in, nor has the requisite session variables set, then they are redirected to the onboarding form where billing/shipping, etc. is collected.
For other aspects like maintaining an anonymous cart, we use a permanent cookie with the cart identifier. If the user ends up creating an account, we associate the anonymous cart with their user, and then remove the cookie. This ensures that their cart survives past the session timeout and things like closing the browser, even if they're anonymous.
In other words, in all these things, no user object is actually needed. If it's there (user is logged in), great, we'll use it. Otherwise, we collect and persist the requisite information for checkout via other means.

ASP.NET core Restrict access to certian pages

I have some admin pages which must be extra secure e.g. accessible only by a local hostname and from a limited range of IPs. How to achieve that regarding than requests can be forged?
I solve this via Authorization filter. It may seem to be lame but it worked for my project (hosted on IIS behind a firewall)
public void OnAuthorization(AuthorizationFilterContext context)
{
var allowedHosts = "127.0.0.1,192.168.1.15";
if (
(allowedHosts.Contains("127.0.0.1")
&& context.HttpContext.Connection.RemoteIpAddress.ToString() == "127.0.0.1"
&& !string.IsNullOrEmpty(context.HttpContext.Request.Headers["X-Forwarded-For"]))
||
(!(context.HttpContext.Request.Host.Host.Contains("localhost"))
&& !allowedHosts.Contains(context.HttpContext.Connection.RemoteIpAddress.ToString()))
)
{
log();
context.Result =new RedirectResult("/404");
}
}
Have you checked the policies?
Maybe you can make an implementation like this:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class LocalHostOnlyAttribute : Attribute, IAuthorizationFilter
{
[Authorize]
public void OnAuthorization(AuthorizationFilterContext context)
{
// restrict if hostname is not localhost.
if(context.HttpContext.Request.Host.Host !="localhost"){
// return UnauthorizedAccess razor page/view if not allowed.
context.Result = new ViewResult() {ViewName = "UnauthorizedAccess" };
}
}
}
Be warned i'm not sure if this is easily spoofable, so it might create a security risk.
Especially when your request is forwared from nginx or another reverse-proxy. The host will always be localhost!
Maybe check request ip equals the ip of the server (but even this i think is a small security risk).

Web API Basic Auth inside an MVC app with Identity Auth

So I have a C# MVC app using Identity for its authentication. I now have a need to expose a few things via Web API to some of my clients. Instead of building a separate app, project, deployment... I've simply added an API Controller to my existing project. To keep things simple for my clients, I've decided to use Basic Auth, opting rather to force my clients into using SSL connections to my API.
I've followed this very useful tutorial to implement the Basic Auth in my API:
http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/
Problem is, that their instructions take over Auth for the entire app...
I need my MVC app to keep using the Identity Auth that it is currently using and hopefully roll my own custom attribute (like [APIAuthorize]) so that it only applies to my API Controller.
I can probably hack around and try to get this to work, but as this is concerning security, I decided to ask for some pro help on how to best implement this. Specifically, I need to know 1) what do I do in my Global.asax (if anything) as the above URL suggests I do this:
protected void Application_Start()
{
GlobalConfiguration.Configuration.MessageHandlers
.Add(new BasicAuthMessageHandler(){
PrincipalProvider = new DummyPrincipalProvider()
});
//...
}
But again, this would take over the Authentication to the entire app... 2) What do I need to do in my custom auth attribute to make all of this work seamlessly.
And of course, if there's a better way to do all of this (without creating a separate app or increasing the implementation difficulty to my clients) then I'm all ears.
I us a filter attribute to adorn the actions i wanted to expose to Simple Auth. I cant remember where i got this code from (probably stackoverflow i just don't have the link so i cant claim credit for it)
public class BasicHttpAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (Thread.CurrentPrincipal.Identity.Name.Length == 0)
{
// Get the header value
AuthenticationHeaderValue auth = actionContext.Request.Headers.Authorization;
// ensure its schema is correct
if (auth != null && string.Compare(auth.Scheme, "Basic", StringComparison.OrdinalIgnoreCase) == 0)
{
// get the credientials
string credentials = UTF8Encoding.UTF8.GetString(Convert.FromBase64String(auth.Parameter));
int separatorIndex = credentials.IndexOf(':');
if (separatorIndex >= 0)
{
// get user and password
string passedUserName = credentials.Substring(0, separatorIndex);
string passedPassword = credentials.Substring(separatorIndex + 1);
SimpleAES crypto = new SimpleAES();
string userName = crypto.DecryptString(ConfigurationManager.AppSettings.Get(Constants.SIMPLEUSERNAME));
string password = crypto.DecryptString(ConfigurationManager.AppSettings.Get(Constants.SIMPLEUSERPASSWORD));
// validate
if (passedUserName == userName && passedPassword == password)
{
Thread.CurrentPrincipal = actionContext.ControllerContext.RequestContext.Principal = new GenericPrincipal(new GenericIdentity(userName, "Basic"), new string[] { });
}
}
}
}
return base.IsAuthorized(actionContext);
}
}
Then i use it as so
[BasicHttpAuthorize]
public HttpResponseMessage MyExposedSimpleAuthAction()

Create Dynamic subdomain in asp.net

I need to do URL rewriting in such a way that
if my request is abc.domain.com, then request should be processed such as
domain.com/default.aspx?cid=abc
for example, if i give .domain.com, request should be assumed as domain.com/default.aspx?cid=
it is not mandatory that i need to give abc alone the subdomain can be any.. but my request should be processed assuming it as querystring value.
My domain is in Shared hosting...
can anyone throw light in this..
The Subdomain must be created and configure on DNS server and on IIS.
After you have setup your site to accept that subdomains, you may direct use the RewritePath to map a subdomain to a specific file with different parameters.
Starting from Application_BeginRequest you check and find the subdomain and rewrite the path as:
protected void Application_BeginRequest(Object sender, EventArgs e)
{
var SubDomain = GetSubDomain(HttpContext.Current.Request.Host);
// this is a simple example, you can place your variables base on subdomain
if(!String.IsNullOrEmpty(SubDomain))
RewritePath(HttpContext.Current.Request.Path + SubDomain + "/", false);
}
// from : http://madskristensen.net/post/Retrieve-the-subdomain-from-a-URL-in-C.aspx
private static string GetSubDomain(Uri url)
{
string host = url.Host;
if (host.Split('.').Length > 1)
{
int index = host.IndexOf(".");
return host.Substring(0, index);
}
return null;
}
Similar:
How to remap all the request to a specific domain to subdirectory in ASP.NET
Redirect Web Page Requests to a Default Sub Folder
Retrieve the subdomain from a URL in C#

Categories

Resources