Use synced Active Directory in Azure to validate users including groups? - asp.net-mvc

I am porting an application to azure and in that app we use Active Directory to authenticate users like the following:
var user = model.UserName.Split('\\');
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, user[0]))
{
if (pc.ValidateCredentials(user[1], model.Password, ContextOptions.Negotiate))
{
using (var adUser = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, user[1]))
{
if (!MembershipService.ValidateUser(model.UserName, model.Password))
{
using (var userDb = new UsersDbContext())
{
if (userDb.aspnet_Users.Count(u => u.UserName.ToLower().Contains(model.UserName)) <= 0)
MembershipService.CreateUser(model.UserName, model.Password, adUser.EmailAddress);
else
{
var msUser = Membership.GetUser(model.UserName);
msUser.ChangePassword(msUser.ResetPassword(), model.Password);
}
}
}
FormsService.SignIn(model.UserName, model.RememberMe);
foreach (var role in Roles.GetAllRoles())
{
using (var group = GroupPrincipal.FindByIdentity(pc, role))
{
if (group != null)
{
if (adUser.IsMemberOf(group))
{
if (!Roles.IsUserInRole(model.UserName, role))
Roles.AddUserToRole(model.UserName, role);
}
else
{
if (Roles.IsUserInRole(model.UserName, role))
Roles.RemoveUserFromRole(model.UserName, role);
}
}
}
}
}
}
}
This works fine on our web-server which is connected to our domain server.
Now I set up an Windows Azure Active Directory and configured it to be synced with our On-Premise AD which also works.
But I am now struggeling on finding a way to connect my PrincipalContext to the WAAD.
Is this even possible and how? If not, what is the alternative?
I only found examples using Single-Sign-On which does this redirection to the MS login page we do NOT want to use, because we have a mixed authentication and depending on the entered user name it either uses the ASP.NET Membership or pulls the user and groups from AD (and actually creates an ASP.NET Membership user as seen above).

No.
You can't really use PrincipalContext with WAAD. Have to explicitly state here that you cannot currently (Jan. 2014) do direct user authentication against WAAD. You will need to rewrite some parts of your application to be compatible:
Authentication happens only on the WAAD side, your code cannot do user+password validation. This also happens on WAAD provided login page. You have limited control on how this page looks like and can customize it via Premium features of WAAD.
You can create users and reset user password using the WAAD Graph API.
Explore the Graph API for additional operations you might need (i.e. ask for user's group membership, direct reports, etc.)
You will have to switch from Windows Authentication to Federated Authentication. Depending on what VS version you are using this might be easy or tough. For VS 2012 there is Identity and Access Tool extension. While in 2013 authentication can only be configured when you create the project and cannot be altered afterwards. But you can copy configuration changes from other project over. You need changes in web.config file along with what is initialized in global.asax. Check it here - although about VS 2013 RC, the process is same in RTM.

Related

oauth 2.0 - Resource owner password flow, can use windows login user credentials

I am using Identity Server 3 and using InMemoryUsers to keep my user/password info,
factory.UseInMemoryUsers(Users.Get());
For one of my client I'am using ResourceOwner password flow,
Flow = Flows.ResourceOwner,
Now, I am able to get access token by below setting, sending user/pass which we store In-Memory,
Now question is,
can I use windows local users credential in place of in-memory users?
There's a service called IUserService which is responsible for getting user and its profile.
When you are using InMemory Users in fact you are using InMemoryUserService.
If you want to use windows local users, you need to implement your own IUserService and get users from windows and then register your service.
public CustomUserService : UserServiceBase
{
public override Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
// You need to implement `GetUserFromWindows` to get users from windows local
var user = GetUserFromWindows(context.UserName, context.Password);
if (user != null)
{
context.AuthenticateResult = new AuthenticateResult(user.Subject, userDisplayName);
}
return Task.FromResult(0);
}
}
factory.UserService = new Registration<IUserService, CustomUserService>();

MSAL and Azure AD: What scopes should I pass when I just want to get the user ID?

I'm using MSAL to get an ID Token which is then used to access an Web API app. I've got a couple of questions and I was wondering if someone could help me understand what's going on.
Let me start with the authentication process in the client side. In this case, I'm building a Windows Forms app that is using the following code in order to authenticate the current user (ie, in order to get an ID Token which will be used to validate the user when he tries to access a Web API app):
//constructor code
_clientApp = new PublicClientApplication(ClientId,
Authority, //which url here?
TokenCacheHelper.GetUserCache());
_scopes = new []{ "user.read" }; //what to put here?
//inside a helper method
try {
return await _clientApp.AcquireTokenSilentAsync(_scopes, _clientApp.Users.FirstOrDefault());
}
catch (MsalUiRequiredException ex) {
try {
return await _clientApp.AcquireTokenAsync(_scopes);
}
catch (MsalException ex) {
return null;
}
}
The first thing I'd like to clear is the value that should be used for the authority parameter. In this case, I'm using an URL on the form:
https://login.microsoftonline.com/{Tenant}/oauth2/v2.0/token
However, I'm under the impression that I could also get away with something like this:
https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
It seems like one endpoint is specific to my Azure AD while the other looks like a general (catch all) URL...Where can I find more information about these endpoints and on what's the purpose of each...
Another thing that I couldn't quite grasp is the scope. I'm not interested in querying MS Graph (or any other Azure related service for that matter). In previous versions of the MSAL library, it was possible to reference one of the default scopes. However, it seems like that is no longer possible (at least, I tried and got an exception saying that I shouldn't pass the default scopes...).
Passing an empty collection (ex.: new List<string>()) or null will also result in an error. So, in this case, I've ended passing the user.read scope (which, if I'm not mistaken, is used by MS Graph API. This is clearly not necessary, but was the only way I've managed to get the authentication process working. Any clues on how to perform the call when you just need to get an ID Token? Should I be calling a different method?
Moving to the server side, I've got a Web API app whose access is limited to calls that pass an ID token in the authentication header (bearer). According to this sample, I should use something like this:
private void ConfigureAuth(IAppBuilder app) {
var authority = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
app.UseOAuthBearerAuthentication(
new OAuthBearerAuthenticationOptions {
AccessTokenFormat = new JwtFormat(GetTokenValidationParameters(),
new OpenIdConnectCachingSecurityTokenProvider(authority)),
Provider = new OAuthBearerAuthenticationProvider {
OnValidateIdentity = ValidateIdentity
}
});
}
Now, this does work and it will return 401 for all requests which don't have a valid ID Token. There is one question though: is there a way to specify the claim from the Ticket's Identity that should be used for identifying the username (User.Identity.Name of the controller)? In this case, I've ended handling the OnValidateIdentity in order to do that with code that looks like this:
private Task ValidateIdentity(OAuthValidateIdentityContext arg) {
//username not getting correctly filled
//so, i'm handling this event in order to set it up
//from the preferred_username claim
if (!arg.HasError && arg.IsValidated) {
var identity = arg.Ticket.Identity;
var username = identity.Claims.FirstOrDefault(c => c.Type == "preferred_username")?.Value ?? "";
if (!string.IsNullOrEmpty(username)) {
identity.AddClaim(new Claim(ClaimTypes.Name, username));
}
}
return Task.CompletedTask;
}
As you can see, I'm searching for the preferred_username claim from the ID Token (which was obtained by the client) and using its value to setup the Name claim. Is there any option that would let me do this automatically? Am I missing something in the configuration of the OAuthBearerAuthenticationMiddleware?
Regarding your First Query -
Where can I find more information about these endpoints and on what's the purpose of each...
Answer -
https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration
The {tenant} can take one of four values:
common -
Users with both a personal Microsoft account and a work or school account from Azure Active Directory (Azure AD) can sign in to the application.
organizations -
Only users with work or school accounts from Azure AD can sign in to the application.
consumers -
Only users with a personal Microsoft account can sign in to the application.
8eaef023-2b34-4da1-9baa-8bc8c9d6a490 or contoso.onmicrosoft.com -
Only users with a work or school account from a specific Azure AD tenant can sign in to the application. Either the friendly domain name of the Azure AD tenant or the tenant's GUID identifier can be used.
Regarding your Second Query on Scope -
Answer - Refer to this document - OpenID Connect scopes
Regarding your Third Query on Claim -
Answer - Refer to this GIT Hub sample - active-directory-dotnet-webapp-roleclaims

ASP .Net MVC and WCF Identity (Claims) Integration

We're building a platform where the client is an ASP .Net MVC one, using ASP Net Identity 2.0 for authentication and authorization (using Claims), which works great on the web side.
We also have a WCF service which allows CRUD operations on the database (for multiple client applications), which gets requests from this ASP .Net MVC client.
As we want to validate (authenticate & authorize) the user before making specific CRUD actions in the WCF side, we need to get the claims of the user from the client, and perform the validations (preferably in a very clean manner using headers or any binding that WCF will be able to support for this matter).
I've been searching the different forums but with no simple answer\tutorial to this specific scenario. Can anyone assist on this matter?
Thanks,
Nir.
I love this:
in your IEndpointBehavior implementation do this on the client end:
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
request.Headers.Add(MessageHeader.CreateHeader("token", "http://myurl.com/service/token", _theToken));
return null;
}
then on the service end add this to your ServiceAuthenticationManager
public override ReadOnlyCollection<IAuthorizationPolicy> Authenticate(
ReadOnlyCollection<IAuthorizationPolicy> authPolicy, Uri listenUri, ref Message message)
{
IPrincipal user = new MyUserPrincipal(null);
if(_currentServiceContractType.GetInterfaces()
.Any(x => x == typeof(IMySecuredService)))
{
var tokenPosition = message.Headers.FindHeader("token", "http://myurl.com/service/token");
if (tokenPosition >= 0 && tokenPosition <= 5)
{
var encryptedToken = message.Headers.GetHeader<string>(tokenPosition);
if (!string.IsNullOrWhiteSpace(encryptedToken))
{
var serializedToken = new MyEncryptionUtility().Decrypt(encryptedToken);
var token = MyTokenSerializer.Deserialize(serializedToken);
var expire = new DateTime(token.ValidToTicks);
if (expire > DateTime.Now)
{
user = new MyUserPrincipal(token);
}
}
}
}
message.Properties["Principal"] = user;
Thread.CurrentPrincipal = user;
return authPolicy;
}
This gives you then the ability to use the built in claims or WIF claims authentication. Eitherway, this is very simple. The token is created by the service and sent to the client (web) and stored in the cookie. then when there are any requests, the token is grabbed from a cookie and then sent along to the service, where, inevitably you can start adding permissions service side, versus doing them on the web/mvc side, making a much cleaner code base using everyone's favorite friend, SOA >= :)

Single authentication pipeline for webapi, mvc and signalr supporting basic and forms

My current services are using MVC to render forms, WebApi to move my viewModels back and forth and signalR for push notifications etc.
If the users are browsing the website they will be using forms auth, but we're introducing some mobile apps and I would like to be able to consume webapi and signalr from the mobile apps using basic auth, without having to maintain two separate sets of controllers.
I have two IPrincipals, a SessionPrincipal and a BasicPrincipal (where Session Principal inherits BasicPrincipal and has some additional contextual data). The idea is that some controllers will require to be on the website (SessionPrincipal), but everything else can be accessed by both web and mobile users (Basic Principal). Some won't require any authorisation at all, so can't just deny the request.
My current approach does the following steps to achieve this (some code omitted for brevity)
Global.asax Application_AuthenticateRequest
var cultureCookie = Request.Cookies["Culture"];
// Set culture ...
var authHeader = Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic"))
{
//Check Username / Password. If okay...
HttpContext.Current.User = new BasicAuthPrincipal(user);
}
else
{
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
// Try and resolve Session from encrypted forms auth data. If okay...
HttpContext.Current.User = new SessionAuthPrincipal(Id, User, Agent);
}
}
Individual Authorize Filters (SessionMVC, SessionApi, BasicApi) that basically boil down to:
return HttpContext.Current.User as SessionPrincipal != null;
// Or
return HttpContext.Current.User as BasicPrincipal != null;
So if they were successfully set in global.asax then proceed to the controller.
Now, I have a working implementation of this, so why am I asking for help?
I'm not sure of certain fringe scenarios that may upset this. Am I asking for trouble for implementing it this way?
I read about HttpContext not being thread safe, but Application_AuthenticateRequest should run before everything else and no further changes are made to that data so I think it should be okay.

Setup a route {tenant}/{controller}/{action}/{id} with ASP.NET MVC?

I would like to setup a multi-tenant ASP.NET MVC app. Ideally, this app would have a route with {tenant}/{controller}/{action}/{id}, each tenant representing an logical instance of the app (simply independent multi-user accounts)
The fine grained details how do that are still quite unclear to me. Any guide available to setup such multi-tenant scheme with ASP.NET MVC?
I am currently working on a similar project using ASP.Net MVC, Forms Authentication and the SQL providers for Membership/Roles/Profile. Here is the approach I am taking:
Register the default route as `{tenant}/{controller}/{action}/{id}
Change the default behavior of the FormsAuthenticationService that comes with the standard MVC template. It should set the UserData of the authentication ticket to include the tenant name (from your route).
public void SignIn(string userName, bool createPersistentCookie, string tenantName)
{
var ticket = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddMinutes(30),
createPersistentCookie, tenantName);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
HttpContext.Current.Response.AppendCookie(cookie);
}
In your global.asax file to do some tenant security checking and allow partioning of users between tenants in one membership database
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
//Since this method is called on every request
//we want to fail as early as possible
if (!Request.IsAuthenticated) return;
var route = RouteTable.Routes.GetRouteData(new HttpContextWrapper(Context));
if (route == null || route.Route.GetType().Name == "IgnoreRouteInternal") return;
if (!(Context.User.Identity is FormsIdentity)) return;
//Get the current tenant specified in URL
var currentTenant = route.GetRequiredString("tenant");
//Get the tenant that that the user is logged into
//from the Forms Authentication Ticket
var id = (FormsIdentity)Context.User.Identity;
var userTenant = id.Ticket.UserData;
if (userTenant.Trim().ToLower() != currentTenant.Trim().ToLower())
{
//The user is attempting to access a different tenant
//than the one they logged into so sign them out
//an and redirect to the home page of the new tenant
//where they can sign back in (if they are authorized!)
FormsAuthentication.SignOut();
Response.Redirect("/" + currentTenant);
return;
}
//Set the application of the Sql Providers
//to the current tenant to support partitioning
//of users between tenants.
Membership.ApplicationName = currentTenant;
Roles.ApplicationName = currentTenant;
ProfileManager.ApplicationName = currentTenant;
}
Partition each tenants data. Here are two options:
4a. Use a separate database for each tenant. This provides the best data security for your tenants. In the shared membership database, add a table that is keyed on unique appid for each tenant and use this table to store and retrieve the connection string based on the current tenant.
4b. Store all data in one database and key each table on the unique tenant id. This provides slightly less data security for your tenants but uses only one SQL Server license.
You will prob find these links useful.

Resources