Sharing FormsAuthentication Cookie Between WCF & ASP.NET Application - asp.net

For extra security I am generating a FormsAuthentication cookie in my WCF service and returning it to my web application, however I want to then utilize that cookie when making requests to particular services on WCF. How can I configure this to be passed with every request? I have setup the machine and decryption key but every time I inspect the cookies when it hits the WCF service there are no cookies to be found.
I've tried various methods to add the cookie to the header, but it seemed to make sense to just do the below because it generates and adds it to the header.
Return FormsAuthentication.SetAuthCookie(email, False)
Then when I inspect the cookies on the client using...
HttpContext.Current.Response.Cookies
It's empty.
Am I doing something wrong on the client, all I am attempting is the following.
If AccountService.Authenticate(model.Email, model.Password) Then
Return RedirectToAction("Index", "Home")
End If
Hoping that it would just pick up the cookie in the header due to that call to the service.
I'm currently working around this in a not so ideal way, the cookie is returned in the body of the request from the service
Server
If CheckPassword(password, Unicode.GetString(user.Password)) And user.EmailConfirmed Then
Return FormsAuthentication.GetAuthCookie(email, False)
End If
Client
Dim cookie As HttpCookie = AccountService.Authenticate(model.Email, model.Password)
If cookie IsNot Nothing Then
HttpContext.Response.Cookies.Add(cookie)
Return RedirectToAction("Index", "Home")
End If
Authenticate Method (WCF)
Public Function Authenticate(email As String, password As String) As Boolean Implements IAccountService.Authenticate
Dim user As [Public] = db.Publics.Where(
Function(i) i.Email.ToLower = email.ToLower
).FirstOrDefault
If Not IsNothing(user) Then
If CheckPassword(password, Unicode.GetString(user.Password)) And user.EmailConfirmed Then
Dim cookie As HttpCookie = FormsAuthentication.GetAuthCookie(email, False)
HttpContext.Current.Response.Cookies.Add(cookie)
Return True
End If
End If
Return False
End Function

Related

Basic Authentication with asmx web service

I'm trying to implement Basic Authorization for an ASMXweb service. I created the client as a service reference in VS2015. I'm using code in Asmx web service basic authentication as an example.
I'm entering login info in ClientCredentials as below
Dim svc As New WebServiceSoapClient()
svc.ClientCredentials.UserName.UserName = "userId"
svc.ClientCredentials.UserName.Password = "i2awTieS0mdO"
My problem is that in the Authorization HttpModule in the web service, these credentials are not being passed to module. Is there an alternate way to do this?
I found the parts answer at How to add HTTP Header to SOAP Client. I had to combine a couple of answers on that page to get it to work.
Dim svc As New WebServiceSoapClient()
Dim responseService As SoapResponseObject
Using (new OperationContextScope(svc.InnerChannel))
Dim auth = "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes("userId:i2awTieS0mdO"))
Dim requestMessage = New HttpRequestMessageProperty()
requestMessage.Headers("Authorization") = auth
OperationContext.Current.OutgoingMessageProperties(HttpRequestMessageProperty.Name) = requestMessage
dim aMessageHeader = MessageHeader.CreateHeader("Authorization", "http://tempuri.org", auth)
OperationContext.Current.OutgoingMessageHeaders.Add(aMessageHeader)
responseService = svc.ListDistricts(requestService)
End Using
One key thing to be aware of is that the soap client call has to be inside the Using statement. In the above code, this is the next to last line.

SSO with Azure ADFS and OWIN

Thank you for providing help. I have a site that can authenticate with Active Directory Federated Services for Single Sign On. Currently, the way my site works is that, by default, when a user hits my site, my code attempts to log then into SSO (I use the OWIN library for this). If the user is not on our network, it fails to authenticate, and they are redirected to my companies login page, where they can provide their company credentials.
I would like to change this behavior, though. Instead, when the user hits my page, if they authenticate, it should continue as normal and they should be redirected to my site. But, if they do not authenticate, I do not want them redirected to our login page. instead, I want them to be redirected back to my site, where my code will determine what they can and cannot do on the site. I then would want to provide a link, so that they could decide to go to the login page.
I want this behavior because the majority of users of this site will not be a part of the companies network and will not be able to authenticate. SO, they should, by default, just see our home page. But, there may be times when a company member might be working from home, so wont be on our network to auto authenticate. In this case, they would then use the link that sends them to the Azure login page.
Here is the code that I am currently using (site is ASP.net, form web page (not MVC)):
Startup.Auth.vb:
Partial Public Class Startup
Dim appSettings = ConfigurationManager.AppSettings
Private realm As String
Private aadInstance As String
Private tenant As String
Private metadata As String
Private authority As String
Public Sub ConfigureAuth(app As IAppBuilder)
Try
Dim appSettings = ConfigurationManager.AppSettings
If (appSettings("releaseVersion") = "DEBUG") Then
realm = ConfigurationManager.AppSettings("test_ida:RPIdentifier")
aadInstance = ConfigurationManager.AppSettings("test_ida:AADInstance")
tenant = ConfigurationManager.AppSettings("test_ida:Tenant")
ElseIf (appSettings("releaseVersion") = "PROD") Then
realm = ConfigurationManager.AppSettings("ida:RPIdentifier")
aadInstance = ConfigurationManager.AppSettings("ida:AADInstance")
tenant = ConfigurationManager.AppSettings("ida:Tenant")
End If
metadata = String.Format("{0}/FederationMetadata/2007-06/FederationMetadata.xml", aadInstance)
authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant)
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType)
app.UseCookieAuthentication(New CookieAuthenticationOptions())
Dim authOption As WsFederationAuthenticationOptions = New WsFederationAuthenticationOptions()
Dim fn = Function(context)
context.HandleResponse()
context.Response.Redirect("Home/Error?message=" + context.Exception.Message)
Return Task.FromResult(0)
End Function
Dim auth_not As WsFederationAuthenticationNotifications = New WsFederationAuthenticationNotifications() With {
.AuthenticationFailed = fn
}
Dim auth_opt As WsFederationAuthenticationOptions = New WsFederationAuthenticationOptions() With {
.Wtrealm = realm,
.MetadataAddress = metadata,
.Notifications = auth_not
}
If (Not auth_opt.Wtrealm Is Nothing) Then
app.UseWsFederationAuthentication(auth_opt)
Else
End If
Catch ex As Exception
Throw ex
End Try
End Sub
End Class
Then, on my Default.aspx.vb page load event, I do this:
If (Not Request.IsAuthenticated) Then
Try
Dim newAuth As AuthenticationProperties = New AuthenticationProperties()
newAuth.RedirectUri = "/"
HttpContext.Current.GetOwinContext().Authentication.Challenge(newAuth, WsFederationAuthenticationDefaults.AuthenticationType)
Catch ex As Exception
Throw ex
End Try
End If
The problem is, I do not know how to attempt to authenticate the user, determine if they are authenticated, and redirect them accordingly. Any help would be greatly appreciated.
thanks
There is not solid/correct way how to check if anonymous user is inside your network(or I am not aware of one). Possible way is to check IP address (range) users inside your network have publicly on the Internet. This is something you can check with network administrator(s). They may tell you public IP address (range).
Once you know public IP address (range) you can check incoming request to compare if the request is coming from the known reange of IP address (range) inside RedirectToIdentityProvider function.
Dim redirectToIdentityProvider = Function(context)
Dim isCompanyNetworkUser = companyIPAddress == context.Request.RemoteIpAddress
' Or relevant check for range
' Dim isCompanyNetworkUser = (companyIPAddressRangeStart < context.Request.RemoteIpAddress AndAlso companyIPAddressRangeEnd > context.Request.RemoteIpAddress
If Not isCompanyNetworkUser Then
context.State = NotificationResultState.Skipped
context.HandleResponse()
End If
End Function
Dim auth_not As WsFederationAuthenticationNotifications = New WsFederationAuthenticationNotifications() With {
.AuthenticationFailed = fn
.RedirectToIdentityProvider = redirectToIdentityProvider
}
You may want to tweak it a bit as I didn't try it, but may point you to right direction.
Sorry to not providing full code example, but in my opinion :
You may try to bypass the sign-in page prompts, take a look here, which explain you how to :
bypass the sign-in page prompts by adding your company’s existing federated domain name to the end of the Windows Azure Management Portal URL
You may also find usefull information relative to Azure Active Directory Pass-through Authentication which allow that:
When users sign in using Azure AD, this feature validates users' passwords directly against your on-premises Active Directory.
You may also make appear your website as an "application" then added this app to your Azure Active Directory application gallery. then Managing single sign-on for enterprise apps
I found finally that this question may be relative to your question.

ASP.net using OWIN for Azure Active Directory Federation Services Single Sign On

I am working on a project, based on this solution: https://github.com/Azure-Samples/active-directory-dotnet-webapp-wsfederation
Currently, the way I have the user authenticate is by default. When the page loads, I call my login script:
Public Sub SignIn()
If (Not Request.IsAuthenticated) Then
Try
Dim newAuth As AuthenticationProperties = New AuthenticationProperties()
newAuth.RedirectUri = "/"
HttpContext.Current.GetOwinContext().Authentication.Challenge(newAuth, WsFederationAuthenticationDefaults.AuthenticationType)
Catch ex As Exception
End Try
End If
End Sub
EDIT
To add more context, here is my code for APP_START/Startup.Auth.vb:
Partial Public Class Startup
Private realm As String = ConfigurationManager.AppSettings("ida:RPIdentifier")
Private aadInstance As String = ConfigurationManager.AppSettings("ida:AADInstance")
Private tenant As String = ConfigurationManager.AppSettings("ida:Tenant")
Private metadata As String = String.Format("{0}/FederationMetadata/2007-06/FederationMetadata.xml", aadInstance)
Private authority As String = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant)
Public Sub ConfigureAuth(app As IAppBuilder)
Try
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType)
app.UseCookieAuthentication(New CookieAuthenticationOptions())
Dim authOption As WsFederationAuthenticationOptions = New WsFederationAuthenticationOptions()
app.UseWsFederationAuthentication(New WsFederationAuthenticationOptions() With {
.Wtrealm = realm,
.MetadataAddress = metadata,
.Notifications = New WsFederationAuthenticationNotifications() With {
.AuthenticationFailed = Function(context)
context.HandleResponse()
context.Response.Redirect("Home/Error?message=" + context.Exception.Message)
Return Task.FromResult(0)
End Function
}
})
Catch ex As Exception
Throw ex
End Try
End Sub
End Class
What I want to avoid, though, is if someone from outside our network views the site, I don't want them to be redirected to the Azure Single Sign On login page. I just want them to proceed to the website, where my code will handle what they can see and do. I will, eventually, add a login button that will take them to the login page, in the event they are just off site. But, for now, how do I skip the login page?
Second, I want to handle the possibility that Azure ADFS is down. In this case, I just want the user to be redirected to the website, as un-authenticated users. I test this by disconnecting from the Internets and running my app. I've tried using Try blocks, but I still get these errors:
The remote name could not be resolved: 'adfs.myCompany.com'
IOException: Unable to get document from:
https://adfs.myCompany.com/FederationMetadata/2007-06/FederationMetadata.xml
[InvalidOperationException: IDX10803: Unable to create to obtain
configuration from:
'https://adfs.myCompany.com/FederationMetadata/2007-06/FederationMetadata.xml'.]
Are these settings in Azure I should be making or in my code? Any help, with either of these issues, would be great. I needed, I can also add my Start.Auth.vb code, as well.
thanks
Unfortunately, using the samples Microsoft provides will enforce auto-sign on.
That being said, there are two options:
Choose a different authentication scheme
Use a an Azure application with an oAuth code flow to sign in when a user clicks the login in link, then read the user's profile and determine their authorization rights.
If I misunderstood, please let me know. Hope this helps!

How to manitain session values during HttpWebRequest?

In my code I'm sending a HttpWebRequest to a page in my website.
When request sends to this page, It doesn't maintain the Session values.
Below is the code, from where I'm generating the web request:
Public Overloads Shared Function ReadURL(ByVal sUrl As String) As String
Dim sBody As String
Dim oResponse As HttpWebResponse
Dim oRequest As HttpWebRequest
Dim oCookies As New CookieContainer
oRequest = WebRequest.Create("http://localhost:64802/inventory/purchase_order.aspx?id=5654")
oRequest.CookieContainer = oCookies
oResponse = oRequest.GetResponse()
Dim oReader As New StreamReader(oResponse.GetResponseStream())
sBody = oReader.ReadToEnd
oReader.Close()
oResponse.Close()
Return sBody
End Function
Below is the code written on Page_Load of Purchaseorder.aspx.vb:
iDomains_ID = Session("Domains_ID")
iLogin_ID = Session("Login_ID")
sPage = Request.Path
If Request.QueryString.Count > 0 Then sPage &= "?" & Request.QueryString.ToString()
sPage = shared01.Encrypt(sPage, Application("PK"))
If Not User.Identity.IsAuthenticated Or iLogin_ID = 0 Then
Response.Redirect("/login.aspx?page=" & sPage)
Exit Sub
End If
Above code doesn't gets the session values and it redirects to the login page.
So, how i can maintain the session on both pages during HttpWebRequest.
Looking for your replies.
EDIT
I've tried to use CookieContainer class as you can see in above code. But it doesn't work at all.
As an alternative, assuming the calling and called pages are in the same application, you could use the Server.Execute method to load the content of the page without making a separate request to the site:
Public Overloads Function ReadURL(ByVal sUrl As String) As String
Using writer As New StringWriter()
Server.Execute("~/inventory/purchase_order.aspx?id=5654", writer, False)
Return writer.ToString()
End Using
End Function
If I've understood you correctly, you're making a request from one page in your site to another, and you want to send the cookies from the current HttpRequest with your WebRequest?
In that case, you'll need to manually copy the cookies to the CookieContainer:
For Each key As String In Request.Cookies.AllKeys
Dim sourceCookie As HttpCookie = Request.Cookies(key)
Dim destCookie As New Cookie(sourceCookie.Name, sourceCookie.Value, sourceCookie.Path, "localhost")
destCookie.Expires = sourceCookie.Expires
destCookie.HttpOnly = sourceCookie.HttpOnly
destCookie.Secure = sourceCookie.Secure
oCookies.Add(destCookie)
Next
NB: You'll either need to make the ReadUrl function non-Shared, or pass the current HttpRequest as a parameter.
You'll also need to make sure the calling page has EnableSessionState="false" in the <%# Page ... %> directive, otherwise the page you're calling will hang trying to obtain the session lock.
Your code seems like you will need to make a request and a post. The first request will redirect you to your login page. The second will be a request where you post to the login page, which will start the session and (?) store information into the session variables. That post (to the login page) will then redirect you to the page you want.
I used code in this example http://www.codeproject.com/Articles/145122/Fetching-ASP-NET-authenticated-page-with-HTTPWebRe (I tweaked it a bit) to write an application to do this.

Get Session to expire gracefully in ASP.NET

I need a way to tell ASP.NET "Kill the current session and start over with a brand new one" before/after a redirect to a page.
Here's what I'm trying to do:
1) Detect when a session is expired in the master page (or Global.asax) of an ASP.NET application.
2) If the session is expired, redirect the user to a page telling them that their session is expired. On this page, it will wait 5 seconds and then redirect the user to the main page of the application, or alternatively they can click a link to get there sooner if they wish.
3) User arrives at main page and begins to use the application again.
Ok, so far I have steps 1 and 2 covered. I have a function that detects session expiry by using the IsNewSession property and the ASP.NET Session ID cookie value. if it detects an expired session it redirects, waits five seconds and then TRIES to go to the main page.
The problem is that when it tries to redirect, it gets to the part in the master page to detect an expired session and it returns true. I've tried calling Session.Abandon(), Session.Clear(), even setting the session to NULL, with no luck.
Someone out there has had to have faced this problem before, so I'm confident in the community to have a good solution. Thanks in advance.
The problem you are describing happens because asp.net is reusing the sessionid, if the sessionid still exists in the auth cookie when you call abandon() it will just reuse it, you need to explicitly create a new sessionid afaik something like:
HttpCookie mycookie = new HttpCookie("ASP.NET_SessionId");
mycookie.Expires = DateTime.Now.AddDays(-1);
Response.Cookies.Add(mycookie);
For ASP.NET MVC this is what I'm doing with an action method.
Note:
Returns a simple view with no other resources that might accidentally re-create a session
I return the current time and session id so you can verify the action completed succcessfully
public ActionResult ExpireSession()
{
string sessionId = Session.SessionID;
Session.Abandon();
return new ContentResult()
{
Content = "Session '" + sessionId + "' abandoned at " + DateTime.Now
};
}
The code in your master page, which detects an expired session and redirects, should look like this:
if (Session != null
&& Session.IsNewSession
&& Request.Cookies["ASP.NET_SessionId"] != null
&& Request.Cookies["ASP.NET_SessionId"].Value != "")
{
Session.Clear();
Response.Redirect(timeoutPageUrl);
}
Calling session.Clear() before redirecting ensures that on the subsequent page, Session.IsNewSession will be false.
Also note that I am checking for an empty string in the value of of the ASP.NET_SessionId cookie. This helps to prevent a logout from being mistaken as an expired session, if you happen to call Session.Abandon() in your logout process. In that case, make sure you expire the old session cookie as a part of the logout process:
Response.Cookies["ASP.NET_SessionId"].Expires = DateTime.MinValue;
The adding the cookie trick worked for me also, as follows:
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' Code that runs when a new session is started
If Session.IsNewSession Then
'If Not IsNothing(Request.Headers("Cookie")) And Request.Headers("Cookie").IndexOf("ASP.NET_SessionId") >= 0 Then
If Not IsNothing(Request.Headers("Cookie")) AndAlso Request.Headers("Cookie").IndexOf("ASP.NET_SessionId") >= 0 Then
'VB code
Dim MyCookie As HttpCookie = New HttpCookie("ASP.NET_SessionId")
MyCookie.Expires = System.DateTime.Now.AddDays(-1)
Response.Cookies.Add(MyCookie)
'C# code
'HttpCookie mycookie = new HttpCookie("ASP.NET_SessionId");
'mycookie.Expires = DateTime.Now.AddDays(-1);
'Response.Cookies.Add(mycookie);
Response.Redirect("/timeout.aspx")
End If
End If
End Sub
Are you calling Session.Abandon in your special "Your session expired" page? If so, don't.

Resources