Implement Office 365 styled Basic Authentication (Active Profile) - asp.net

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.

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.

Access Sharepoint Online document list through ASP.NET

I have been searching for quite sometime on this topic without finding the silver bullet. We have an intranet solution in ASP.NET where we simply want to show some files from our Sharepoint Online in Office 365.
There are a number of hits when you search for it on Google but nothing seems to be the "simple" way to go. I'm not a Sharepoint developer, but have a good understanding of the concepts as I manage our company's Office 365 and Sharepoint Online installations.
I have found current answers that tell me that REST services is the way to go, but here I need an OATH token to access them, and this is where my Sharepoint knowledge is limited. From what I have read the token can only be granted through an installed Sharepoint App, but I know for a fact that it can be done without it too.
I have purchased a synchronization tool that syncs our file share with a Sharepoint Document List and for this I don't need any tokens, I just type in my credentials and I don't have to install anything in Sharepoint.
So what am I seeking to get from this question?
Someone who knows Sharepoint well to steer me in the right direction and who maybe has a guide and knows that that method works. Maybe REST is the right way to go, but it just seems odd that my Sync software can do it without then.
If I do need to install a token granter in Sharepoint a little assistance in what to be aware of securitywise and how to approach it the best way.
Any help is greatly appreciated and thanks in advance! :)
Microsoft has two set of APIs that can access SharePoint Online:
SharePoint Client Object Model (CSOM):
With this one you can just use username and password to do authentication. follow the link you can find examples of how to access document list.
SharePoint REST API:
With this you need to use an OAuth token to do the authentication.
For you case, you should use the first one.
SharePoint Online also supports Claims-Based authentication mode. The general idea here is to obtain authentication cookies (apart from OAuth authentication flow where access token is issued) from the user credentials. Once the authentication cookie is obtained, you could perform authenticated SOAP (for example via CSOM API) or REST requests against SharePoint Online resources as demonstrated below.
Since you are developing ASP.NET web application,
SharePointOnlineCredentials class from SharePoint Online Client
Components SDK could be utilized which basically implements
claims-based authentication scheme. It could be installed via nuget
Example 1: request list items via CSOM API
using (var ctx = GetContext(webUri.ToString(), userName, password))
{
var list = ctx.Web.Lists.GetByTitle("Documents");
var items = list.GetItems(CamlQuery.CreateAllItemsQuery());
ctx.Load(items);
ctx.ExecuteQuery();
foreach (var item in items)
{
Console.WriteLine(item.FieldValues["FileRef"]);
}
}
where
private static ClientContext GetContext(string url,string username, string password)
{
var ctx = new ClientContext(url);
var securePassword = new SecureString();
foreach (char c in password) securePassword.AppendChar(c);
ctx.Credentials = new SharePointOnlineCredentials(username, securePassword);
return ctx;
}
Example 2: request list items via REST API
using (var client = new SPHttpClient(webUri, userName, password))
{
var listTitle = "Tasks";
var endpointUrl = string.Format("{0}/_api/web/lists/getbytitle('{1}')/items",webUri,listTitle);
var data = client.ExecuteJson(endpointUrl);
foreach (var item in data["value"])
{
Console.WriteLine(item["Title"]);
}
}
where
SPHttpClient.cs - implements HTTP client for SharePoint Online (SPHttpClient class)
SPHttpClientHandler.cs - implements HTTP handler for SharePoint Online

Get AngularJS to talk to .NET Web API secured with Azure AD

I have two different web projects on Microsoft Azure. One project is a .NET MVC web application and the other project is a .NET Web API.
Both projects are configured to use Azure AD. The MVC web application is able to get a token and use it to make requests against the Web API. Here's sample code from the MVC web app.
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
ClientCredential credential = new ClientCredential(clientId, appKey);
result = authContext.AcquireTokenSilent(todoListResourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
// Make a call against the Web Api
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, webApiBaseAddress + "/api/list");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
So this code works just fine. However, what I need to do now is call the Web API directly from an AngularJS application. When I try to do that, I get a 401 unauthorized error.
The way I am doing this is by adding a header to the HTTP GET request sent by AngularJS. I'm setting "Bearer" to the result.AccessToken value that I am passing to the page from my MVC application (code above).
Obviously this doesn't work. I suppose now my question is what are my options? Is there an official or better way to do this? Let's say I wanted to make calls to the Web API from standard JavaScript (lets forget the complexities of AngularJS). Is there a way to authenticate with Azure AD?
the canonical way of obtaining a token for an in-browser JS application would be to use the OAuth2 implicit flow. Azure AD does not currently expose that flow, but stay tuned: we are working on enabling the scenario. No dates to share yet.
HTH!
V.
The work I mentioned in the older answer finally hit the preview stage. Please take a look at http://www.cloudidentity.com/blog/2014/10/28/adal-javascript-and-angularjs-deep-dive/ - that should solve precisely the scenario you described. If you have feedback on the library please do let us know!
Thanks
V.

ASP.NET MVC with Forms Auth and WebApi with Basic Auth

I have a WebApi using Basic Auth nicely. And I have an MVC site using Forms Auth nicely. But here's the catch:
Client X has a dedicated database with any number of Contacts and Products. The MVC site is a dedicated site for them (via {clientId} routing), which allows their Contacts to log in (via Forms Auth) and place orders for their products. The Contact must be Form-ly logged in to place an order.
The product orders (need to) hit the WebApi to be recorded in the Client's database.
But since the WebApi uses Basic Auth to validate the Client, not the Contacts who placed the orders, every request comes back is 401 - Unauthorized.
I've checked out ThinkTecture as suggested by a number of posts here on SO, however it doesn't get me what I need because I'm not looking to allow Forms Auth in the WebApi. I don't want to authenticate the Contact from the Client's database in the WebApi, I want to authenticate the Client in the WebApi.
Has anyone come across a similar scenario and am I missing something glaringly obvious? Perhaps I need to implement both Forms and Basic on the site?
The very standard Api call I'm making from the site (where the UserName and Password are the Client's, not the Contact's):
var clientId = new Guid(RouteData.Values["clientId"].ToString());
var baseUrl = ConfigurationManager.AppSettings["ApiBaseAddress"];
var authHeader = Convert.ToBase64String(Encoding.ASCII.GetBytes(String.Format("{0}:{1}", _shoppingCartSettings.UserName, _shoppingCartSettings.Password)));
var requestUrl = String.Format("api/{0}/inventory", clientId.ToString());
var httpWebRequest = WebRequest.Create(baseUrl + requestUrl);
httpWebRequest.Headers.Add(HttpRequestHeader.Authorization, "Basic " + authHeader);
httpWebRequest.Method = "GET";
httpWebRequest.Accept = "application/json";
httpWebRequest.ContentType = "application/json";
try
{
using (var httpWebResponse = httpWebRequest.GetResponse())
{
// we never get here because of a 401
}
}
catch (WebException ex)
{
using (var httpWebResponse = ex.Response)
{
// we always get here
}
}
If I set up a separate test client and make the same call, it works great :/
Is your Web API under the same virtual directory and configuration as the MVC site? It looks like the Forms Auth HTTP module kicks in for your API, which you don't want. As long as you don't plan to call the API directly from the browser, move it to a separate virtual directory that is set up exclusively for basic auth, no forms auth module in the web.config for the API.
Why not have one login for your MVC site that has the ability to submit orders for every Client? It makes sense for your WebAPI to only allow Clients to submit orders for themselves. But I don't think it makes sense to have your MVC site authenticate as different Clients based on the Contact. Your MVC site would have to store the passwords for each Client.
Instead, create one login for the MVC site and give it the ability to submit an order for any Client.
After much banging of head against the not-so-proverbial wall, and a much needed shove by #0leg, I've discovered the cause.
In the Properties of my WebApi project file under Web > Servers, the Visual Studio Development Server was being used with a Virtual Path of "/", whereas my MVC project file was set up to use the Local IIS Web Server. The MVC project also had the Apply server settings to all users (store in project file) option checked.
Setting both to use the local IIS server resolved it.
Upon further contemplation, this now seems logical since they were essentially running on different servers.
Posting this for posterity's sake.

WCF Forms Based Authentication Via Web App - Passing Credentials

I have a simple web service whereby the security is handled via forms based authentication.
WCFTestService.ServiceClient myService = new
WCFTestService.ServiceClient();
myService.ClientCredentials.UserName.UserName = "user";
myService.ClientCredentials.UserName.Password = "secret";
lblResult.Text = myService.GetData(1231);
myService.Close();
I'm accessing this via a web app. So I want to do the above once but for security/performance not have to do it again. I was thinking something like the the below but as I'm using FormsAuthentication this wont work...
//Obtain the authenticated user's Identity and impersonate the original caller
using (((WindowsIdentity)HttpContext.Current.User.Identity).Impersonate())
{
WCFTestService.ServiceClient myService2 = new WCFTestService.ServiceClient();
lblResult.Text = "From Logged On Credentials"+myService2.GetData(1231);
myService2.Close();
}
What you're trying to do is establish a "secure session" between your client and your service. This is a concept that will only work with the wsHttpBinding - so if you're not using that particular binding, it won't work.
To establish a secure session, you need to set a number of specific config properties in the client and server's config files - you can certainly find those settings by reading the docs (look for "establishSecurityContext") or check out Michele Leroux Bustumante's excellent WCF screencast on security fundamentals on MSDN.
But really: I wouldn't recommend trying to use secure session by all means. Under normal circumstances, using per-call services is the preferred option, and the overhead for re-authenticating with each service call is really negligable.
Marc

Resources