SSO with Azure ADFS and OWIN - asp.net

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.

Related

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!

Sharing FormsAuthentication Cookie Between WCF & ASP.NET Application

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

Login control won't login

I've been using VS 2013 and created an Asp.Net web forms application using the template with Bootstrap. I have a login control that does all of the default behaviour using AspNet tables within SQL. When I run within the IDE I have no problems in any browser logging into the site (IE, Chrome) I was manly using IE when running.
However, I have deployed the site to my local server for testing and it stops working in IE. What happens is it goes through the validation of the user name/password fine, gets to the redirect but the user is never logged in.
If I browse to the server using chrome on my pc it will work fine if I do the same within IE it won't work 95% of the time but I have managed to login occasionally. I have asked other colleague's to try, Chrome doesn't work for most of them or IE. I have also tried on my ipad chrome will work but safari won't!
So in file startup.auth.vb
Partial Public Class Startup
' For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301883
Public Sub ConfigureAuth(app As IAppBuilder)
'Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(AddressOf ApplicationDbContext.Create)
app.CreatePerOwinContext(Of ApplicationUserManager)(AddressOf ApplicationUserManager.Create)
app.CreatePerOwinContext(Of ApplicationSignInManager)(AddressOf ApplicationSignInManager.Create)
' Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
.Provider = New CookieAuthenticationProvider() With {
.OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(Of ApplicationUserManager, ApplicationUser)(
validateInterval:=TimeSpan.FromMinutes(10),
regenerateIdentity:=Function(manager, user) user.GenerateUserIdentityAsync(manager))},
.LoginPath = New PathString("/Account/Login")})
' Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie)
' Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5))
' Enables the application to remember the second login verification factor such as phone or email.
' Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
' This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie)
End Sub
End Class
Then on my login button
Protected Sub LogIn(sender As Object, e As EventArgs)
If IsValid Then
' Validate the user password
Dim manager = Context.GetOwinContext().GetUserManager(Of ApplicationUserManager)()
Dim signinManager = Context.GetOwinContext().GetUserManager(Of ApplicationSignInManager)()
' This doen't count login failures towards account lockout
' To enable password failures to trigger lockout, change to shouldLockout := True
Dim result = signinManager.PasswordSignIn(Email.Text, Password.Text, RememberMe.Checked, shouldLockout:=True)
Select Case result
Case SignInStatus.Success
IdentityHelper.RedirectToReturnUrl(Request.QueryString("ReturnUrl"), Response)
Exit Select
Case SignInStatus.LockedOut
Response.Redirect("/Account/Lockout")
Exit Select
Case SignInStatus.RequiresVerification
Response.Redirect(String.Format("/Account/TwoFactorAuthenticationSignIn?ReturnUrl={0}&RememberMe={1}",
Request.QueryString("ReturnUrl"),
RememberMe.Checked),
True)
Exit Select
Case Else
FailureText.Text = "Invalid login attempt"
ErrorMessage.Visible = True
Exit Select
End Select
End If
End Sub
End Class

How to provide different pages on login for different users?

I'm working on web application which has a database
UserName|Password|UserType
Anil|Anil|Manager
ghouse|Ghouse|Admin
raghu|raghu|User
Now my task is to provide each user their own page on login...They all have a same login page.
I tried this code it's working fine for two users. What to do if I have more than two users?
SqlConnection con = new SqlConnection("Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=DebitCareBankApp;Data Source=SSDEV7-HP\\SQLEXPRESS");
string cmdStr = "select LoginType from Login where UserName='" + TxtUsername.Text + "' AND Password = '" + TxtPassword.Text + "'";
SqlCommand cmd = new SqlCommand(cmdStr, con);
con.Open();
Object TypeUser = cmd.ExecuteScalar();
con.Close();
if (TypeUser != null)
{
LblError.Visible = false;
LblError.Text = "";
if (TypeUser.ToString() == "Manager")
Response.Redirect("~/CallingAgents/CallingAgents_Login.aspx");
else
Response.Redirect("~/UserRegistrationForm.aspx");
}
else
{
LblError.Visible = true;
LblError.Text = "Invalid Credentials Entered, Try again";
}
I think you should create a common class where insert your user type on successful login.
In that common class redirect it to respective page.
On successful login:
Response.Redirect(clsCommon.GetDefaultPage(userType));
your commaon class code:
public static string GetDefaultPage(string userType){
//Default Redirects
Dictionary<string, string> userInfo = new Dictionary<string, string>(){
{"Manager","~/ManagerLogin.aspx"}, {"Admin","~/AdminLogin.aspx"},
{"User","~/UserLogin.aspx"}
};
return userInfo[roleName];
}
If you are using struts then you can redirect to different pages depending upon some Id. In actionforward you can achieve so. Or you can get some values from the url and try to redirect it
A simple way would be to use the Login-control and provide event handlers for the Authenticate event and the LoggedIn event. But i think it would be worth while for you to check out the capabilities of the asp.net membership system.
I assume you are not using Membership provider and make your login functionality by hand.
I do not fully understand the purpose of this customization. It make no sense for me. But there are multiple solutions for you:
convert the login page (aspx) into a user/custom control (ascx) and put in into different pages - simple, quick but not fully transparent, more info ScottGu
use IIS URL-Rewrite engine to provide multiple entry-points (urls)
to the same login page - clear, recomended, more info ScottGu
With first scenario you need to check UserType for credentials given by the user and confront it with page Url (aspx). In the second scenario, you need to obtain Request.RawUrl which contain base Url and make simple case.
Make use of sessions.
For a workaround, you can follow this:
+provide the same login page.
+Ask for username and password.
+use a drop down for selecting the usertype (ie Admin or Manager or User).
So based on the selection from drop down list you process the request.
I hope this helps.

Sharepoint not getting current user NetworkCredential from active directory

we have strange problem, we have single signon and we are trying to fetch unread email count from Exchange ews webservice, the problem is it always gets same count for all user which is actually for server user.
'it should now get for current user who requested the page
'but its always for server user where sharepoint is installed
Public Sub GetUnreadEmailCount()
Dim errormsg As String = String.Empty
Dim UnreadCount As Integer = 0
Dim esb As New ExchangeServiceBinding
Try
esb.RequestServerVersionValue = New RequestServerVersion
esb.RequestServerVersionValue.Version = ExchangeVersionType.Exchange2007_SP1
esb.UseDefaultCredentials = True
esb.Url = Domain + "/EWS/Exchange.asmx"
ServicePointManager.ServerCertificateValidationCallback = New RemoteCertificateValidationCallback(AddressOf CertificateValidationCallBack)
Dim biArray(1) As BaseFolderIdType
Dim dfFolder As New DistinguishedFolderIdType
dfFolder.Id = DistinguishedFolderIdNameType.inbox
biArray(0) = dfFolder
Dim geGetFolder As New GetFolderType
geGetFolder.FolderIds = biArray
geGetFolder.FolderShape = New FolderResponseShapeType
geGetFolder.FolderShape.BaseShape = DefaultShapeNamesType.AllProperties
Dim gfResponse As GetFolderResponseType = esb.GetFolder(geGetFolder)
Dim rmta As ResponseMessageType() = gfResponse.ResponseMessages.Items
Dim rmt As FolderInfoResponseMessageType = DirectCast(rmta(0), FolderInfoResponseMessageType)
If rmt.ResponseClass = ResponseClassType.Success Then
Dim folder As FolderType = DirectCast(rmt.Folders(0), FolderType)
UnreadCount = folder.UnreadCount
End If
Label1.Text = vbCrLf + "Unread email count : " + UnreadCount.ToString
' Return UnreadCount
Catch ex As Exception
If Not ex.Message Is Nothing Then errormsg = ex.Message
Try
If Not ex.InnerException.Message Is Nothing Then errormsg = errormsg + " : " + ex.InnerException.Message
Catch e As Exception
End Try
Finally
If esb IsNot Nothing Then esb.Dispose() : esb = Nothing
If Not errormsg = String.Empty Then
Label1.Text = vbCrLf + "Error : " + errormsg
End If
End Try
End Sub
We were actually having the same problem, although we were not using single sign on. So I'm not sure this is exactly what you are experiencing.
The problem is that you can not have a user on Machine A give their credentials to Machine B (SharePoint?) and then have Machine B send those credentials on to Machine C
It's referred to as the "Double Hop" problem and is a security feature, however I'm not really into the technical side of it. Our solution was to use Kerberos.
I hope this helps you, if not, that it helps you rule out this specific issue :)
Your server side code is running as the AppPool's identity, which is your sharepoint service account. I'm assuming that's what you mean by 'the server user.'
esb.UseDefaultCredentials = true;
will use the creds of the context. I'm not sure of what's available in the EWS services, so if you can use a higher-privileged account and get based on the user coming in, i.e., HttpContext.Current.User.Identity as a parameter, then that may be the best way.
You could authenticate via javascript directly to the EWS services, skipping server-side code altogether, and write something that consumes & displays the server stuff as you need it.
You'd need to find a way to auth the user directly to the EWS services. Double-hop is an issue with NTLM, as your NTLM ticket is only valid for the first hop. Kerberos fixes that, but you still need to impersonate.

Resources