how can i automate calling a url on a password protected asp.net-mvc site - asp.net-mvc

i have an asp.net-mvc site with sqlserver backend and i am using membershipprovider for login, etc.
I have a few automated things that i want to run daily or weekly as i can do this today if i:
Log In
call URL
so lets say the URL is
www.mysite.com/MyController/RunCleanupScript
I know some people will suggest breaking the code of RunCleanupScript into a standalone script outside of the website but i wanted to see if there was a solution to automating the equivalent to the manual login and then entering in this url to call this script?

Phil Haak has a post about a solution which may work for you - he also warns of the dangers associated. You could use this method to schedule the clean up task. If you move your clean-up code out of the controller then there is no need for the login - it can never be called externally. If you need to still be able to login and force the clean up, then moving the clean up code out of your controller is still the way to go. Your secured action and the scheduler code will both call the clean-up code.
Another option could be to create a windows service that hits the action and stores the required credentials in its config file.

Forms auth together with some scripts calling web pages to aquire a cookie may not be the most stable and maintainable approach for your requirements.
You could support basic auth that makes passing username and password from a script easy. For an example how to implement basic auth in asp.net mvc see this blog post.

You could write a console application which will perform 2 HTTP requests: first to login and second to fetch the protected resource:
using System;
using System.Collections.Specialized;
using System.Net;
public class WebClientEx: WebClient
{
private readonly CookieContainer _cookieContainer = new CookieContainer();
protected override WebRequest GetWebRequest(Uri address)
{
var request = base.GetWebRequest(address);
((HttpWebRequest)request).CookieContainer = _cookieContainer;
return request;
}
}
class Program
{
static void Main()
{
using (var client = new WebClientEx())
{
var values = new NameValueCollection
{
{ "username", "user" },
{ "password", "pwd" },
};
// Login
client.UploadValues("http://example.com/account/logon", values);
// Fetch the protected resource
var result = client.DownloadString("http://example.com/home/foo");
Console.WriteLine(result);
}
}
}

This code will login to a FormsAuthentication site, then use the AUTH cookie to hit any other URL on the site...
string appURL = "https://.../LogOn";
// UserName and Password should match the names of the inputs on your form
string strPostData = String.Format("UserName={0}&Password={1}", "login", "pass");
Cookie authCookie;
CookieContainer cookieJar = new CookieContainer();
// Prepare post to the login form
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(appURL);
req.Method = "POST";
req.ContentLength = strPostData.Length;
req.ContentType = "application/x-www-form-urlencoded";
req.CookieContainer = cookieJar;
req.AutomaticDecompression = DecompressionMethods.GZip
| DecompressionMethods.Deflate;
// Proxy - Optional
// req.Proxy.Credentials = CredentialCache.DefaultCredentials;
// Post to the login form.
StreamWriter swRequestWriter = new StreamWriter(req.GetRequestStream());
swRequestWriter.Write(strPostData);
swRequestWriter.Close();
// Get the response.
HttpWebResponse hwrWebResponse = (HttpWebResponse)req.GetResponse();
// Store the required AUTH cookie
authCookie = cookieJar.GetCookies(new Uri("... your cookie uri ..."))[".ASPXAUTH"];
Now you can access any other URL of the site using the AUTH cookie.
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("... url ...");
req.CookieContainer.Add(new System.Net.Cookie(authCookie.Name,
authCookie.Value,
authCookie.Path, "localhost"));
HttpWebResponse resp = (HttpWebResponse) req.GetResponse();

PowerShell might be a good option for you. Here's a sample that demonstrates how you would post form values to the log-on page and then use the response cookie to make a second call to the admin page.
Note, I borrowed much of this sample from this post.
$LogonUrl = "http://yoursite.com/Account/LogOn"
$UserName = "AdminUser"
$Password = "pass#word1"
$AdminUrl = "http://yoursite.com/MyController/RunCleanupScript"
$cookies = New-Object System.Net.CookieContainer
$formData = "UserName=" + $UserName + "&Password=" + $Password
[net.httpWebRequest] $web1 = [net.webRequest]::create($LogonUrl)
$web1.method = "POST"
$web1.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
$web1.Headers.Add("Accept-Language: en-US")
$web1.Headers.Add("Accept-Encoding: gzip,deflate")
$web1.Headers.Add("Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7")
$web1.AllowAutoRedirect = $false
$web1.ContentType = "application/x-www-form-urlencoded"
$buffer = [text.encoding]::ascii.getbytes($formData)
$web1.ContentLength = $buffer.length
$web1.TimeOut = 50000
$web1.KeepAlive = $true
$web1.Headers.Add("Keep-Alive: 300");
$web1.CookieContainer = $CookieContainer
$reqStrm = $web1.getRequestStream()
$reqStrm.write($buffer, 0, $buffer.length)
$reqStrm.flush()
$reqStrm.close()
[net.httpWebResponse] $response = $web1.getResponse()
$respStrm = $response.getResponseStream()
$reader = new-object IO.StreamReader($respStrm)
$result = $reader.ReadToEnd()
$response.close()
$web2 = new-object net.webclient
$web2.Headers.add("Cookie", $response.Headers["Set-Cookie"])
$result = $web2.DownloadString("$AdminUrl")
Write-Output $result
This could also easily be turned into a Windows Console app as well. Either way, they are easy to schedule with Task Scheduler.
Hope this helps.

Why don't you give WatiN or Selenium a try? You can set up a login step very easy and then test if the other RunCleanupScript page is working properly.
WatiN's main page example:
[Test]
public void SearchForWatiNOnGoogle()
{
using (var browser = new IE("http://www.google.com"))
{
browser.TextField(Find.ByName("q")).TypeText("WatiN");
browser.Button(Find.ByName("btnG")).Click();
Assert.IsTrue(browser.ContainsText("WatiN"));
}
}
You can then have something like:
[Test]
public void TestRunCleanupScript()
{
using (var browser = new IE("www.mysite.com/MyController/RunCleanupScript"))
{
DoLogin(browser)
//navigate to cleanupscript page
//your assert
}
}
public void DoLogin(browser)
{
//navigate to login
//type username and password and hit button
}

I am currently doing this in a production environment. In my case the solution was a no-brainer since MADAM had already been installed in order to allow normal RSS Readers to securely access RSS feeds on the site.
The trick to doing this is to enable Basic Authentication for the pages you want to call automatically using any external processes, that opens you up to a huge number of ways to access the site automatically; this VBScript file, for instance calls the maintenance URL and checks whether the response from the server is exactly SUCCESS.
Option Explicit
Dim result
result = PerformMaintenance("http://www.mysite.com/MyController/RunCleanupScript")
WScript.Quit(result)
Function PerformMaintenance(URL)
Dim objRequest
Set objRequest = CreateObject("Microsoft.XmlHttp")
'I use a POST request because strictly speaking a GET shouldn't change anything on the server.
objRequest.open "POST", URL, false, "LimitedDaemonUser", "SecretDaemonPassword"
objRequest.Send
if (objRequest.ResponseText = "SUCCESS") Then
PerformMaintenance = 0
Else
PerformMaintenance = 1
End If
set objRequest = Nothing
End Function
Basic Authentication is easy enough to get working. Just include MADAM with your project, and configure it in your Web.config.
Adding these Web.config sections/parameters (IIS6) should get your example request working if you use a standard MembershipProvider. You just have to change MyNamespace.MembershipUserSecurityAuthority to a reference to an actual class. The source code for MembershipUserSecurityAuthority is included with MADAM in the demo web application's App_Code folder.
<configuration>
<configSections>
<sectionGroup name="madam">
<section name="userSecurityAuthority" type="System.Configuration.SingleTagSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<section name="formsAuthenticationDisposition" type="Madam.FormsAuthenticationDispositionSectionHandler, Madam" />
</sectionGroup>
</configSections>
<madam>
<userSecurityAuthority realm="MyRealm" provider="MyNamespace.MembershipUserSecurityAuthority, MyNamespace" />
<formsAuthenticationDisposition>
<discriminators all="false">
<discriminator inputExpression="Request.AppRelativeCurrentExecutionFilePath" pattern="~/MyController/RunCleanupScript$" type="Madam.RegexDiscriminator, Madam" />
</discriminators>
</formsAuthenticationDisposition>
</madam>
<system.web>
<httpModules>
<add name="FormsAuthenticationDisposition" type="Madam.FormsAuthenticationDispositionModule, Madam" />
<add name="AuthenticationModule" type="Madam.BasicAuthenticationModule, Madam" />
</httpModules>
</system.web>
</configuration>

Related

ASP.NET MVC with Azure B2C refresh ID token issue

I am developing an ASP.NET MVC app with Azure B2C authentication. It is required that, after the ID token expires (IIS session not expires), any subsequent action call should automatically refresh the ID token with the refresh token and then continue the execution without re-login.
Questions:
Does the solution make sense?
After refreshing the ID token and set the cookies, how can I redirect to the original url and continue execution without re-login?
Thanks, any idea is highly appreciated.
This is my code:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var refreshToken = HttpContext.Current.Request.Cookies["msal.refreshtoken"];
if (refreshToken != null && !string.IsNullOrEmpty(refreshToken.Value))
{
var newIdToken = TokenService.RefreshIdToken(refreshToken.Value);
var idTokenCookie = new HttpCookie("msal.idtoken", newIdToken)
{
Secure = true,
HttpOnly = true
};
HttpContext.Current.Response.Cookies.Set(idTokenCookie);
return;
}
}
// TokenService.RefreshIdToken
public static string RefreshIdToken(string refreshToken)
{
var policyName = ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"];
var B2CDomain = ConfigurationManager.AppSettings["ida:B2CDomain"];
var tenant = ConfigurationManager.AppSettings["ida:Tenant"];
var clientId = ConfigurationManager.AppSettings["ida:ClientId"];
var clientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];
var tokenEndpointUri = $"https://{B2CDomain}/{tenant}/{policyName}/oauth2/v2.0/token";
var httpClient = new HttpClient();
var requestBodyDict = new Dictionary<string, string>
{
{ "grant_type" , "refresh_token" },
{ "client_id" , clientId },
{ "client_secret" , clientSecret },
{ "scope" , $"openid" },
{ "refresh_token" , refreshToken }
};
var request = new HttpRequestMessage
{
RequestUri = new Uri(tokenEndpointUri),
Method = HttpMethod.Post,
Content = new FormUrlEncodedContent(requestBodyDict)
};
var task = Task.Run(() => httpClient.SendAsync(request));
task.Wait();
var response = task.Result;
var task1 = Task.Run(() => response.Content.ReadAsStringAsync());
task1.Wait();
var responseString = task1.Result;
if (response.IsSuccessStatusCode)
{
var idToken = (string)JsonConvert.DeserializeObject<dynamic>(responseString).id_token.ToString();
return idToken;
}
else
{
throw new Exception();
}
}
A couple of thoughts that are too long to put in comments:
Yes the basic idea of ‘use the refresh token to get a new id token’ is how it’s supposed to work.
Googling this question suggests a bewildering array of examples to imitate :-( e.g. Microsoft’s Azure Samples on GitHub for A/D auth for a web app (as opposed to webapi or SPA)
The basic plan for identity problems like this is, find an authoritative example and follow it because that reduces your risk of embarrassing error. ( For instance, Auth0’s example for this scenario says to get a new refresh_token as well as a new id_token. Not doing that might be okay but then the user will be forced to re-login when the refresh token expires. Then you’ll be tempted to use ultra-long-lifetime refresh token, loosening your security a little)
If you can’t find an authoritative example, considering raising an issue or commenting on one.
OTOH, if the code you’ve written works, then maybe you’ve done!
The problem with finding an example to imitate after you’ve got started is trying to find just the right the example for the technology choices you already made. It may be easier to start with an empty project, follow a tutorial, get the tutorial working, then copy the solution back into your app.
To send your user back to their original target you should be able to
var originalUrl= HttpContext.Current.Request.Url;
HttpContext.Current.Response.Redirect(original);
But only do that if getting the id_token succeeded otherwise it creates an infinite loop.

How to call Logic App from MVC application?

Can some one please share how to call logic apps from MVC application?
My requirement is pass data from web form to logic apps http request.
Appreciate your help.
Now i'm able to call / trigger logic app url from MVC application. And i could pass json data to logic app url . Following is my code.
public async Task<JsonResult> Add_recordAsync(register rs)
{
using (var client = new HttpClient())
{
var jsonData = System.Text.Json.JsonSerializer.Serialize(new
{
email = "maruthikiran#gmail.com",
due = rs.Name,
task = rs.Password
});
var content = new StringContent(jsonData);
content.Headers.ContentType.CharSet = string.Empty;
content.Headers.ContentType.MediaType = "application/json";
var response = await client.PostAsync("Logic App URL", content);
}
}
Reference link :https://learn.microsoft.com/en-us/azure/app-service/tutorial-send-email?tabs=dotnet#more-resources

Sign In using raw HttpRequestMessage in ASP.NET MVC

I have been testing some code to sign in users to their Microsoft/school/work accounts using raw HttpRequestMessage and HttpResponseMessage. I know there are libraries available to do this but I want to test the raw approach as well (especially usage of refresh tokens), while looking for the right library to handle it.
I'm currently learning authentication, with limited knowledge of ASP.NET/Core.
I'm following this guide: https://learn.microsoft.com/en-us/graph/auth-v2-user
I've just modified the SignIn() method in AccountController in an example project that used more high level libraries to sign in.
I'm requesting an authorization code.
The SignIn() code:
public void SignIn()
{
using (var httpClient = new HttpClient())
{
try
{
var tenant = "my tenant id";
var clientId = ConfigurationManager.AppSettings["ida:AppID"];
var responseType = "id_token+code";
var redirectURI = ConfigurationManager.AppSettings["ida:RedirectUri"];
var responseMode = "form_post";//query";
var appScopes = ConfigurationManager.AppSettings["ida:AppScopes"];
var scopes = $"openid profile offline_access {appScopes}";
var state = "12345";
//var prompt = "consent";
var url = string.Format("https://login.microsoftonline.com/{0}/oauth2/v2.0/authorize", tenant);
var body = string.Format("client_id={1}&response_type={2}&redirect_uri={3}&response_mode={4}&scope={5}&state={6}", tenant, clientId, responseType, redirectURI, responseMode, scopes, state);
var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Content = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded");
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead).Result;
var content = response.Content.ReadAsStringAsync().Result;
}
catch (Exception ex)
{
}
}
//if (!Request.IsAuthenticated)
//{
// // Signal OWIN to send an authorization request to Azure
// Request.GetOwinContext().Authentication.Challenge(
// new AuthenticationProperties { RedirectUri = "/" },
// OpenIdConnectAuthenticationDefaults.AuthenticationType);
//}
}
I'm just returning void from the method now because I'm not sure what I should return yet.
Debugging and looking at the response variable, the status code is 200, and has some other information to it. However, the content of the HttpResponseMessage, when I paste it into a file and opening it in a browser, displays (or redirects to) https://login.microsoftonline.com/cookiesdisabled, which shows a message saying that I could not be logged in because my browser blocks cookies. However, I don't think this really is the case.
How can I resolve this and have the user log in and consent, and get the authorization code?
I couldn't really find any example in ASP.NET that uses this raw approach. Is it not recommended?
You should fistly understand how OAuth 2.0 authorization code flow works in Azure AD V2.0 :
Microsoft identity platform and OAuth 2.0 authorization code flow
The general process would be like :
When login in client application, user will be redirect to Azure AD login endpoint(https://login.microsoftonline.com/{0}/oauth2/v2.0/authorize) and provides info like which client(client_id) in which tenant(tenant id) user wants to login , and redirect back to which url(redirect_uri) after successful login.
User enter credential , Azure AD validate credential and issue code and redirect user back to redirect url provided in step 1 (Also match one of the redirect_uris you registered in the portal).
The client application will get the code and send http post request with code to acquire access token .
So if you want to manally implement the code flow in your application , you can refer to below code sample :
public async Task<IActionResult> Login()
{
string authorizationUrl = string.Format(
"https://login.microsoftonline.com/{0}/oauth2/v2.0/authorize?response_type=code&client_id={1}&redirect_uri={2}&scope={3}",
"tenantID", "ClientID", "https://localhost:44360/Home/CatchCode",
"openid offline_access https://graph.microsoft.com/user.read");
return Redirect(authorizationUrl);
}
private static readonly HttpClient client = new HttpClient();
public async Task<ActionResult> CatchCode(string code)
{
var values = new Dictionary<string, string>
{
{ "grant_type", "authorization_code" },
{ "client_id", "XXXXXX"},
{ "code", code},
{ "redirect_uri", "https://localhost:44360/Home/CatchCode"},
{ "scope", "https://graph.microsoft.com/user.read"},
{ "client_secret", "XXXXXXXXXXX"},
};
var content = new FormUrlEncodedContent(values);
//POST the object to the specified URI
var response = await client.PostAsync("https://login.microsoftonline.com/cb1c3f2e-a2dd-4fde-bf8f-f75ab18b21ac/oauth2/v2.0/token", content);
//Read back the answer from server
var responseString = await response.Content.ReadAsStringAsync();
//you can deserialize an Object use Json.NET to get tokens
}
That just is simple code sample which will get Microsoft Graph's access token , you still need to care about url encode and catch exception , but it shows how code flow works .

5006 : Unable to redirect to Vendors web site. SagePay

Hi I am using SagePay Server Integration after the payment process the payment process
5006 : Unable to redirect to Vendors web site. The Vendor failed to
provide a RedirectionURL.
my web config file:
<sagePay>
<!-- The public-facing hostname that SagePay can use to contact the site -->
<add key="NotificationHostName" value="ubtfront.azurewebsites.net" />
<!--<add key="NotificationHostName" value="ubtfront.azurewebsites.net" />-->
<!-- The protocol defaults to http, but you can override that to https with the following setting -->
<add key="Protocol" value="http" />
<!-- Your notification controller -->
<add key="NotificationController" value="PaymentResponse" />
<!-- Your notification action. These three settings together are used to build the notification URL -->
<!-- EG: http://my.external.hostname/PaymentResponse/Notify -->
<add key="NotificationAction" value="Notify" />
<!-- Action names for URLS that the user will be directed to after payment either succeeds or fails -->
<!-- The URL is constructed from the notificationHostName and NotificationController. -->
<!-- Eg: http://my.external.hostname/PaymentResponse/Success -->
<add key="SuccessAction" value="Success" />
<add key="FailedAction" value="Failed" />
<!-- VAT multiplier. Currently at 20% -->
<add key="VatMultiplier" value="1" />
<!-- Name of vendor. You will need to change this -->
<add key="VendorName" value="VendorName" />
<!-- Simulator, Test or Live -->
<add key="Mode" value="Test" />
</sagePay>
My Payment Response Controller:
public class PaymentResponseController : Controller
{
IOrderRepository _orderRepository;
public PaymentResponseController(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public ActionResult Notify(SagePayResponse response)
{
// SagePay should have sent back the order ID
if (string.IsNullOrEmpty(response.VendorTxCode))
{
return new ErrorResult();
}
// Get the order out of our "database"
var order = _orderRepository.GetById(response.VendorTxCode);
// IF there was no matching order, send a TransactionNotfound error
if (order == null)
{
return new TransactionNotFoundResult(response.VendorTxCode);
}
// Check if the signature is valid.
// Note that we need to look up the vendor name from our configuration.
if (!response.IsSignatureValid(order.SecurityKey, SagePayMvc.Configuration.Current.VendorName))
{
return new InvalidSignatureResult(response.VendorTxCode);
}
// All good - tell SagePay it's safe to charge the customer.
return new ValidOrderResult(order.VendorTxCode, response);
}
public ActionResult Failed(string vendorTxCode)
{
return View();
}
public ActionResult Success(string vendorTxCode)
{
return View();
}
}
I can't figure out where I am going wrong please help me figure it out. Any kind of help is appreciated....
Please refer following code, you have to pass your success and failed URL with your request, I have achieved this by using following Code:
private static void SetSagePayFormAPIData(IFormPayment request, PaymentGatewayRequest paymentRequest)
{
var isCollectRecipientDetails = SagePaySettings.IsCollectRecipientDetails;
request.VpsProtocol = SagePaySettings.ProtocolVersion;
request.TransactionType = SagePaySettings.DefaultTransactionType;
request.Vendor = SagePaySettings.VendorName;
//Assign Vendor tx Code.
request.VendorTxCode = SagePayFormIntegration.GetNewVendorTxCode();
request.Amount = paymentRequest.GrossAmount;
request.Currency = SagePaySettings.Currency;
request.Description = "Your Payment Description";
request.SuccessUrl = "Your SuccessUrl";
request.FailureUrl = "Your FailureUrl";
request.BillingSurname = paymentRequest.BillingSurname;
request.BillingFirstnames = paymentRequest.BillingFirstnames;
request.BillingAddress1 = paymentRequest.BillingAddress1;
request.BillingCity = paymentRequest.BillingCity;//Pass Billing City Name
request.BillingCountry = paymentRequest.BillingCountry;//Pass Billing City Name
request.DeliverySurname = paymentRequest.DeliverySurname;
request.DeliveryFirstnames = paymentRequest.DeliveryFirstnames;
request.DeliveryAddress1 = paymentRequest.DeliveryAddress1;
request.DeliveryCity = paymentRequest.DeliveryCity;//Pass Delivery City Name
request.DeliveryCountry = paymentRequest.DeliveryCountry;//Pass Delivery Country
//Optional
request.CustomerName = paymentRequest.BillingFirstnames + " " + paymentRequest.BillingSurname;
request.VendorEmail = SagePaySettings.VendorEmail;
request.SendEmail = SagePaySettings.SendEmail;
request.EmailMessage = SagePaySettings.EmailMessage;
request.BillingAddress2 = paymentRequest.BillingAddress2;
request.BillingPostCode = paymentRequest.BillingPostCode;
request.BillingState = "UK";//Pass Billing State
request.BillingPhone = paymentRequest.BillingPhone;
request.DeliveryAddress2 = paymentRequest.DeliveryAddress2;
request.DeliveryPostCode = paymentRequest.DeliveryPostCode; //Delivery Post Code
request.DeliveryState = "UK"; //Pass Delivery State
request.DeliveryPhone = paymentRequest.DeliveryPhone;
request.AllowGiftAid = SagePaySettings.AllowGiftAid;
request.ApplyAvsCv2 = SagePaySettings.ApplyAvsCv2;
request.Apply3dSecure = SagePaySettings.Apply3dSecure;
request.CustomerEmail = paymentRequest.CustomerEmail;
request.BillingAgreement = "";
request.ReferrerId = SagePaySettings.ReferrerID;
request.BasketXml = SagePayPaymentController.ToBasketstring(paymentRequest);
request.VendorData = string.Empty; //Use this to pass any data you wish to be displayed against the transaction in My Sage Pay.
}
Hope it helps you :)
Sage doesn't like ports on the URLs (From Sage's docs):
The Sage Pay servers send an HTTP or HTTPS POST to the NotificationURL script on your server to indicate the outcome of the transaction using ports 80 and 443. Please ensure you use these ports only as hard coding any other ports will generate errors
The SagePayMvc library uses the current context to build the Notify, Success and Failure URLs, meaning it also adds the current request port.
Testing locally, I was expecting my staging (Azure) server would receive the response from Sage but my current port was being added to the request, http://example.com:51118/PaymentResponse/Notify resulting in the 5006 error being thrown by Sage.
I am using MVC5 so I've had to tweak parts of the code in the library to make it work.
I changed the BuildNotificationUrl property on the DefaultUrlResolver class to build the URL without using the port as it would have to be 80 or 443 by default.
You could do something more less like this:
public virtual string BuildNotificationUrl(RequestContext context) {
var configuration = Configuration.Current;
var urlHelper = new UrlHelper(context);
var routeValues = new RouteValueDictionary(new {controller = configuration.NotificationController, action = configuration.NotificationAction});
var url = urlHelper.RouteUrl(null, routeValues, configuration.Protocol, configuration.NotificationHostName);
var uri = new Uri(url);
return uri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped);
}
Hope this helps.

MVC 4 Forms authentication strange behavior

I am using Asp.Net with MVC 4 to build a web application. For authentication, I am using forms authentication. The login page is set correctly and login behaves properly. However, instead of using the default partial login view I am using my own and I use AJAX to log in.
The login controller works fine and here is the code for login.
Here is my code in login action. Here resp is my custom response object
resp.Status = true;
// sometimes used to persist user roles
string userData = "some user data";
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // ticket version
login.username, // authenticated username
DateTime.Now, // issueDate
DateTime.Now.AddMinutes(30), // expiryDate
false, // true to persist across browser sessions
userData, // can be used to store additional user data
FormsAuthentication.FormsCookiePath); // the path for the cookie
// Encrypt the ticket using the machine key
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
// Add the cookie to the request to save it
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookie.HttpOnly = true;
//Response.Cookies.Add(cookie);
Response.SetCookie(cookie);
return Json(resp);
Here is the code of cshtml page to handle this script response
function (respData) {
if (respData.Status) {
window.location.href = "/";
}
if (!respData.Status) {
if (respData.Errors[0].ErrorCode == 1) {
$('#invalid').show();
$('#username').val('');
$('#password').val('');
}
else if (respData.Errors[0].ErrorCode == -1) {
var msg = respData.Errors[0].ErrorDescription;
$('#error_email').text(msg);
}
else {
var msg = respData.Errors[0].ErrorDescription;
$('#error_pwd').text(msg);
}
}
$("#dialog").dialog("close");
},
Everything works fine and the user is successfully redirected to home page on successful login. Also gets a proper message on failure.
The problem is, when I browse any other page after this successful redirection, the subsequent requests are not authenticated.
I did a little bit research and found that the browser is not sending the forms authentication cookie in the subsequent requests and hence those requests are not authenticated.
Any idea on this behavior ? , Am I missing something ?
Try explicitly setting the expiry time on your cookie with:
Cookie.Expires(DateTime.Now.AddMinutes(30));

Resources