How to query ADFS repository for authentication in ASP.NET - asp.net

I have an ASP.NET Web Forms application and ADFS correctly implemented.
I successfully use ADFS for SSO in many applications but now I need to use the ADFS repository just to validate login credentials on-premises, not for the login itself.
My application is a simple form with Textboxes for username and password and a Login button. Once the user inserts username and password and clicks on login I need to check with ADFS whether the data are correct, receive the response and based on that perform some other task.
In the SSO I already implemented it is the STS itself that displays the pop-up for login credentials but in this case I want this task to be fulfilled by my app.
Anybody might tell me if that is possible and point me to the right direction?

Are you sure you want to have your own login form in a web app? That doesn't sound fair, if the ADFS is further federated with other identity providers, your check could just miss that.
Having said that, if you really want this, you should enable a usernamemixed endpoint endpoint in the ADFS configuration, configure your application as a relying party and request a token:
string stsEndpoint = "https://WIN-2013.win2008.marz.com/adfs/services/trust/13/usernamemixed";
string relyingPartyUri = "https://www.yourrelyingpartyuri.com";
WSTrustChannelFactory factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(stsEndpoint));
factory.TrustVersion = TrustVersion.WSTrust13;
// Username and Password here...
factory.Credentials.UserName.UserName = "remote_user01";
factory.Credentials.UserName.Password = "the_password";
RequestSecurityToken rst = new RequestSecurityToken
{
RequestType = Microsoft.IdentityModel.Protocols.WSTrust.WSTrust13Constants.RequestTypes.Issue,
AppliesTo = new EndpointAddress(relyingPartyUri),
KeyType = Microsoft.IdentityModel.Protocols.WSTrust.WSTrust13Constants.KeyTypes.Bearer,
};
IWSTrustChannelContract channel = factory.CreateChannel();
SecurityToken token = channel.Issue(rst);
//if authentication is failed, exception will be thrown. Error is inside the innerexception.
//Console.WriteLine("Token Id: " + token.Id);
This particular snippet is copied from this blog entry:
http://leandrob.com/2012/04/requesting-a-token-from-adfs-2-0-using-ws-trust-with-username-and-password/

Related

Headline steps required to extend an existing (Azure based) ASP.NET forms auth app to use ADFS

I have an existing ASP.NET app deployed on Azure. It has its own auth system, essentially a u/p database table and creation of a forms auth cookie:
public void LogIn(LoginDetails userLogin, bool createPersistentCookie)
{
var info = (CustomPrincipalInfo) userLogin;
var timeout = 30;
if (createPersistentCookie)
timeout = 60*24;
var cookie = info.CreateAuthenticationCookie(DateTime.Now, timeout, createPersistentCookie);
HttpContext.Current.Response.Cookies.Add(cookie);
}
public static HttpCookie CreateAuthenticationCookie(this CustomPrincipalInfo info, DateTime current, int timeout, bool remember)
{
var serializer = new JavaScriptSerializer();
var userData = serializer.Serialize(info);
var ticket = new FormsAuthenticationTicket(
1, info.Email, current, current.AddMinutes(timeout), remember, userData);
var secureTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, secureTicket) { Domain = FormsAuthentication.CookieDomain };
if (remember)
cookie.Expires = current.AddMinutes(timeout);
return cookie;
}
Most of the customers just register details, log in using their details with us etc, but we've been asked to integrate with a customer that uses ADFS for SSO. My reading around the topic suggests that we need to run an ADFS server, integrate our app with it, and then engage in a process of establishing trust between their ADFS server and ours
I'm curious to know if, given that it's hosted on Azure already, there is anything we can do with Azure/AAD to skip the "run an ADFS server" part, because we don't really have any infrastructure for doing so. The app startup routine is older school Global.asax style, using castle windsor for DI/IoC:
public class MvcApplication : HttpApplication
{
protected void Application_Start()
{
DependencyRegistrar.Register();
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
InitializeInfrastructure();
MigrateDatabaseSchema();
DataAnnotationConfig.RegisterValidationAttributes();
BundleConfig.RegisterBundles();
}
The resources I'm turning up are generally older stuff:
ADFS with existing ASP.Net MVC App - this mentions a separate blog discussing server 2012r2 in depth - we do have such a sevrer, but are looking to retire it rather than add another reason to carry on using it/have to replace it with a newer one
Azure Websites SSO using ADFS brought up an azure sample that seems to imply Azure can help us avoid having to run a dedicated ADFS server, but again the sample is ~9 years old
The most recent one I found seemed to be a reasonable description of the problem I face, but I couldn't understand the answer (which basically seemed to be "read this link, scroll down a bit" - reading the link didn't smack me in the face with anything obviously "this is what you have to do")
Resources surrounding using ADFS on Azure seem massively involved; I don't feel like I need/want/to pay for load balancers, multiple storage accounts, DMZs and proxies etc.. This is literally so that one user at one customer can sign in a handful of times a month
What set of steps do I take to create a really basic ADFS SSO auth mechanism in this existing app, hook in to how it IDs users so that when joe.bloggs#customer.com comes and signs in via ADFS I can become aware of the email address that is signed in and give him his forms auth cookie like anyone else, and leverages some Azure based facility so that I don't have to run an ADFS server?
There are two ways to do this.
The basic problem is that your credentials are in a DB and neither AAD nor ADFS supports this.
You could run identityserver4 in Azure and configure idsrv4 to authenticate against the DB. Then you could federate idsrv4 with the partner ADFS.
Or you could move the users into Azure AD via the Graph API and then federate AAD with ADFS.

401 when calling UserInfo using ADFS 4.0 and OpenID Connect

I've successfully created a new Application Group with a Server Application as well as a Web API and the OpenID Connect protocol is working w/out any issues until I try and make a call to UserInfo. The Relying Party identifier is the same GUID as Client ID of the Server Application (per the examples I have read online). I get the error below when trying to call UserInfo:
WWW-Authenticate: Bearer error="invalid_token", error_description="MSIS9921: Received invalid UserInfo request. Audience 'microsoft:identityserver:21660d0d-93e8-45db-b770-45db974d432d' in the access token is not same as the identifier of the UserInfo relying party trust 'urn:microsoft:userinfo'."
Any help would be greatly appreciated.
I also recently got this error using ADFS with the ASP.NET Core OpenIDConnect providers. In my case, disabling the UserInfo request altogether resolved the issue:
var openIdOptions = new OpenIdConnectOptions
{
...
GetClaimsFromUserInfoEndpoint = false
};
After doing this, I still had the claims that I needed for my app - email, SID, name, etc. I'm sure there are scenarios where this would not work, but it's good to know you might not need /userinfo at all. I would still be interested in knowing why the token returned from ADFS can't be used to call /userinfo, and how to fix it in ASP.NET OpenIDConnect providers.
Just set the resource accordingly:
options.Resource = "urn:microsoft:userinfo";

Implement Office 365 styled Basic Authentication (Active Profile)

I'm working on a SaaS application built around ASP.net MVC & WebAPI and want to make it easy for enterprises to use my service. Example would be Office 365 Basic Authentication (Active Profile) where the user enters his username/password on microsoft's site (or desktop app) and he is authenticated against his employer's Active Directory. My understanding so far is that I would need to create a RP-STS which will accept credentials and then forward those to AD FS Proxy running on the client company's AD server. Is this correct?
If yes, then how do I implement this? Setting up AD server adding a Relying Party and AD FS Proxy Role is easy, so that's really not an issue. I just need to figure out how to create/setup RP-STS service and any other steps involved in this process. There just isn't an example/tutorial of this in .net
I believe this msdn blog post describes exactly what you're asking for. It has a complete walkthrough of the entire process, including creating an RP by creating a normal WCF service, and then use the provided utility to configure the service to trust your ADFS.
http://blogs.msdn.com/b/mcsuksoldev/archive/2011/08/17/federated-security-how-to-setup-and-call-a-wcf-service-secured-by-adfs-2-0.aspx
Edit:
This code, taken from the linked article (comments are mine), is a demonstration of active federation. The client application is manually retrieving a security token from the ADFS. Passive Federation would involve forwarding the user to a secure web page in which they could send their credentials directly to the ADFS. The major benefit of Passive Federation is that the end user's secret credentials are provided directly to the ADFS, and the RP's client side code never has access to it.
var requestTokenResponse = new RequestSecurityTokenResponse();
//The line below is the 'Active' federation
var token = Token.GetToken(#"mydomain\testuser", "p#ssw0rd", "http://services.testdomain.dev/wcfservice/Service.svc", out requestTokenResponse);
var wcfClient = new FederatedWCFClient<MyTestService.IService>(token, "WS2007FederationHttpBinding_IService"); // This must match the app.config
var client = wcfClient.Client as MyTestService.IService;
var result = client.GetData();
Console.WriteLine(result);
wcfClient.Close();
Take a look at these links:
https://github.com/OfficeDev/O365-WebApp-SingleTenant
https://github.com/OfficeDev/O365-WebApp-MultiTenant
It shows how to make an application using the office 365 api to authenticate and authorize the users.
Be aware about Single Tenant and Mult Tentant application, and choose the right one.
It's really easy to do that, I've done it couple months ago.
I found the answer on the blog: http://leandrob.com/2012/04/requesting-a-token-from-adfs-2-0-using-ws-trust-with-username-and-password/
What this code essentially does is that it directly authenticates with the tenant's ADFS endpoint and gets a token as well. That's what I was looking for.
var stsEndpoint = "https://[server]/adfs/services/trust/13/UsernameMixed";
var relayPartyUri = "https://localhost:8080/WebApp";
var factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(stsEndpoint));
factory.TrustVersion = TrustVersion.WSTrust13;
// Username and Password here...
factory.Credentials.UserName.UserName = user;
factory.Credentials.UserName.Password = password;
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointAddress(relayPartyUri),
KeyType = KeyTypes.Bearer,
};
var channel = factory.CreateChannel();
SecurityToken token = channel.Issue(rst);
Another good article on that blog is: http://leandrob.com/2012/02/request-a-token-from-adfs-using-ws-trust-from-ios-objective-c-iphone-ipad-android-java-node-js-or-any-platform-or-language/ - which covers other similar scenarios.

Setting ASP.Net Permissions - Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

I have an MVC 4 application that allows a user to change their Active Directory password through a password reset functionality page. I have the following piece of code that sets the new password:
DirectoryEntry de = sr.GetDirectoryEntry();
de.Invoke("SetPassword", new object[] { newPassword });
de.Properties["LockOutTime"].Value = 0;
Upon trying to submit the form with the new password details I am having the following error written to the application event log:
0x80070005 (E_ACCESSDENIED))
I have set the Identity property of the Application Pool to NetworkService and had thought that this would have resolved the issue of connecting. Is there anything else further I need to ensure so that my ASPNET application can connect to the AD.
tl;dr
In our case, this started happening randomly. Turns out it's because our self-signed SSL certificate had expired. After creating a new one in IIS, the problem was resolved.
Explanation
This thread lead me to the cause.
I will briefly recap what SetPassword does here so you can see why you need it. That particular ADSI method really bundles 3 methods under the covers. It first tries to set the password over a secure SSL channel using LDAP. Next, it tries to set using Kerberos set password protocol. Finally, it uses NetUserSetInfo to attempt to set it.
The main problem is that only the first two methods will generally respect the credentials that you put on the DirectoryEntry. If you provide the proper credentials and an SSL channel for instance, the LDAP change password mechanism will work with those credentials...
If you check the NetUserSetInfo method, you will notice there is no place to put a username/password for authorization. In other words, it can only use the unmanaged thread's security context. This means that in order for it to work, it would have to impersonate the username/password combination you provided programmatically first...
LDAP over SSL is apparently the best way to go (and was the method that we had been using), and it appears (clarifications welcome) that once our self-signed SSL certificate had expired, it skipped Kerberos and fell back to NetUserSetInfo, which failed because it was not using the credentials we provided. (Or it just failed on Kerberos, as the poster said he had never seen the credentials passed in for Kerberos)
So after creating a new self-signed certificate (for COMPUTER.DOMAIN.local), the problem was resolved.
Here is the code (in case anyone's looking for it):
DirectoryEntry myDE = new DirectoryEntry(#"LDAP://OU=GroupName,DC=DOMAIN,DC=local");
myDE.Username = "administrator";
myDE.Password = "adminPassword";
DirectoryEntries myEntries = myDE.Children;
DirectoryEntry myDEUser = myEntries.Find("CN=UserName");
myDEUser.Invoke("SetPassword", new object[] { "NewPassword" });
myDEUser.Properties["LockOutTime"].Value = 0;
// the following 2 lines are free =)
myDEUser.Properties["userAccountControl"].Value = (int)myDEUser.Properties["userAccountControl"].Value | 0x10000; // don't expire password
myDEUser.Properties["userAccountControl"].Value = (int)myDEUser.Properties["userAccountControl"].Value & ~0x0002; // ensure account is enabled
myDEUser.CommitChanges();

Where do WCF Client cookies persist?

I am writing a wrapper around a SOAP API. I have a service reference set up in VS2010 to point to the WSDL. When I make a call to login, the API returns a session variable in a cookie.
I've set allowCookies="true" on my binding in config.
I've implemented two API calls in my wrapper so far: login and logout.
I have a test harness that is a simple ASP.NET application that has a page for login and a page for logout.
When login is submitted:
using (var ApiClient = new ApiClient())
{
ApiClient.Login(txtUsername.Text, txtPassword.Text, txtOrganization.Text, txtIPAddress.Text);
}
And now in my ApiClient.Login method:
using (var soapService = new WSDLInterfaceClient())
{
var loginCredentials = new loginRequest
{
username = username,
password = password,
organization = organization
};
if (!string.IsNullOrWhiteSpace(ipAddress))
loginCredentials.ipAddress = ipAddress;
var loginResponse = soapService.Login(loginCredentials);
}
So this all goes off without a hitch. I was thinking I would need to remove the usings and have a class level WSDLInterfaceClient that I would use within my wrapper because I figured the cookies would be wiped out each time I constructed a new client. But that is simply not the case.
The logout method is implemented similarly. The logout API call will throw an exception if you try to logout without being logged in. Oddly enough, when I go to my logout page and submit (which in turn is constructing a new wrapper client and therefore a new service client) it recognizes that I am logged in. If I try to logout again it throws the exception as expected.
The cookies seem to be working in that even when constructing both a new interface client and a new wrapper client on each page, the cookies persist.
While this isn't a bad thing, I am perplexed as to how it is working. Is there somewhere that ASP.NET/WCF is saving these cookies for the session? How would this work in a console app?

Resources