Setting realm and issuer for web service at Runtime (windows azure ACS) - rest

We have the scenario in our project,
We have Tenant-1 to Tenant-n which consume Restful Service S1. The tenants have a 1 to 1 relationship with IDP. Client has to federate the tenant UI through Restful Service using ACS with the help of tenant specific IDP configured in ACS at the time of onboarding.
Tenant-1 mapped to IdP1 (Eg: Yahoo)
Tenant-2 mapped to Idp2 (Eg: Google)
Restful Service returns a JavaScript as JSON, which is hosted within the Tenant’s Web UI. So if the tenant has already logged on to the Tenant UI using IDP specific to him via his own application, then for any requests from the tenant UI to Restful Service, the Restful service should federate to tenant specific IdP based on the partner information (mapping of tenant to IdP) configured during the onboarding process.
I am setting Realm in the Global.asax as shown below.
public class WebApiApplication : System.Web.HttpApplication
{
public event EventHandler RedirectingToIdentityProvider;
public override void Init()
{
FederatedAuthentication.WSFederationAuthenticationModule.RedirectingToIdentityProvider += WSFederationAuthenticationModule_RedirectingToIdentityProvider;
}
void WSFederationAuthenticationModule_RedirectingToIdentityProvider(object sender, RedirectingToIdentityProviderEventArgs e)
{
Tenant tenant = GetTenantDetails(subId); // Gets the tenant information from MetaData based on subscriptionId
if (tenant != null)
{
e.SignInRequestMessage.Realm = tenant.Realm + "CMS/";
}
}
protected void Application_Start()
{
FederatedAuthentication.FederationConfigurationCreated += OnServiceConfigurationCreated;
}
private void OnServiceConfigurationCreated(object sender, FederationConfigurationCreatedEventArgs e)
{
if (tenant != null)
{
e.FederationConfiguration.WsFederationConfiguration.Issuer = tenant.Issuer;
Uri uri = new Uri(tenant.Realm + "CMS/");
if (!e.FederationConfiguration.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris.Contains(uri))
e.FederationConfiguration.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(tenant.Realm + "CMS/"));
e.FederationConfiguration.WsFederationConfiguration.Realm = tenant.Realm + "CMS/";
}
}
Further the Realm is set at the per request level too, as shown below.
public class MetaDataModule : IHttpModule
{
private static string WSFederationAuthenticationModuleName = string.Empty;
public void Init(HttpApplication httpContextApplication)
{
var requestWrapper = new EventHandler(DoSyncRequestWorkToGetTenantDetails);
httpContextApplication.BeginRequest += requestWrapper;
}
private static void DoSyncRequestWorkToGetTenantDetails(object sender, EventArgs e)
{
var httpContextApplication = (HttpApplication)sender;
Tenant tenant = GetTenantDetails(); // Gets the tenant information from MetaData based on subscriptionId
if (tenant != null)
{
WSFederationAuthenticationModule wsfed = (WSFederationAuthenticationModule)httpContextApplication.Modules["WSFederationAuthenticationModule"];
wsfed.FederationConfiguration.WsFederationConfiguration.Issuer = tenant.Issuer;
Uri uri = new Uri(tenant.Realm + "CMS/");
if (!wsfed.FederationConfiguration.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris.Contains(uri))
wsfed.FederationConfiguration.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(tenant.Realm + "CMS/"));
wsfed.FederationConfiguration.WsFederationConfiguration.Realm = tenant.Realm + "CMS/";
//FederatedAuthentication.FederationConfiguration.WsFederationConfiguration.Issuer = tenant.Issuer;
//Uri uri = new Uri(tenant.Realm + "CMS/");
//if (!FederatedAuthentication.FederationConfiguration.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris.Contains(uri))
// FederatedAuthentication.FederationConfiguration.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(tenant.Realm + "CMS/"));
//FederatedAuthentication.FederationConfiguration.WsFederationConfiguration.Realm = tenant.Realm + "CMS/";
}
}
Please find the modules registered in the Web.config and the remaining part of WIF configuration too.
In spite of resetting the Realm for each request, the new value does not get assigned.
Client does not want their tenants to implement any authentication or federation related code from their end for this to work.
Please let me know if you can think of any solution to this issue with the help of Passive Federation.

You should customize the realm in the Application_AuthenticateRequest method in your Global.asax.
Take a look at this link.

Related

How to authenticate with Azure AD / OpenId but use Entity Framework based user/role data

I'm trying to improve the authentication story for a legacy ASPNet MVC/OWIN app - Currently, it uses the AspNetUsers / AspNetRoles / claims etc tables along with forms + cookie based authentication.
I want to use Azure AD / OpenID Connect for authentication but then load the user profile/roles from the database as currently. Basically, no more password management within the app. Users themselves will still need to exist/be created within the app.
The application is quite dependent on some custom data associated with these users so simply using the roles from Active Directory isn't an option.
The OpenID auth works, however I'm not sure how to use the existing Identityuser / IdentityUserRole / RoleManager plumbing in conjunction with it.
Basically once the user authenticates with Open ID we'll want to load the corresponding user from the database (matching on email address) and use that user profile / roles going forward.
In particular, the AuthorizeAttribute (with specific roles specified) should continue to function as before.
This is what I have so far:
public class IdentityConfig
{
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext(AppIdentityDbContext.Create);
app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);
ConfigureAuth(app);
}
/// <summary>
/// Configures OpenIDConnect Authentication & Adds Custom Application Authorization Logic on User Login.
/// </summary>
/// <param name="app">The application represented by a <see cref="IAppBuilder"/> object.</param>
private void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
//Configure OpenIDConnect, register callbacks for OpenIDConnect Notifications
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ConfigHelper.ClientId,
Authority = String.Format(CultureInfo.InvariantCulture, ConfigHelper.AadInstance,
ConfigHelper.Tenant), // For Single-Tenant
PostLogoutRedirectUri = ConfigHelper.PostLogoutRedirectUri,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
RoleClaimType = "roles",
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error/OtherError?errorDescription=" +
context.Exception.Message);
return Task.FromResult(0);
},
SecurityTokenValidated = async context =>
{
string userIdentityName = context.AuthenticationTicket.Identity.Name;
var userManager = context.OwinContext.GetUserManager<AppUserManager>();
var user = userManager.FindByEmail(userIdentityName);
if (user == null)
{
Log.Error("User {name} authenticated with open ID, but unable to find matching user in store", userIdentityName);
context.HandleResponse();
context.Response.Redirect("/Error/NoAccess?identity=" + userIdentityName);
return;
}
user.DateLastLogin = DateTime.Now;
IdentityResult result = await userManager.UpdateAsync(user);
if (result.Succeeded)
{
var authManager = context.OwinContext.Authentication;
ClaimsIdentity ident = await userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ExternalBearer);
// Attach additional claims from DB user
authManager.User.AddIdentity(ident);
// authManager.SignOut();
// authManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident);
return;
}
throw new Exception(string.Format("Failed to update user {0} after log-in", userIdentityName));
}
}
});
}
}
Here's what I ended up doing:
public class IdentityConfig
{
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext(AppIdentityDbContext.Create);
app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);
ConfigureAuth(app);
}
/// <summary>
/// Configures OpenIDConnect Authentication & Adds Custom Application Authorization Logic on User Login.
/// </summary>
/// <param name="app">The application represented by a <see cref="IAppBuilder"/> object.</param>
private void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieDomain = ConfigHelper.AuthCookieDomain,
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromHours(2)
});
//Configure OpenIDConnect, register callbacks for OpenIDConnect Notifications
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ConfigHelper.ClientId,
Authority = String.Format(CultureInfo.InvariantCulture, ConfigHelper.AadInstance, ConfigHelper.Tenant),
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
RoleClaimType = ClaimTypes.Role
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error/OtherError?errorDescription=" + context.Exception.Message);
return Task.FromResult(0);
},
RedirectToIdentityProvider = context =>
{
// Set the post-logout & redirect URI dynamically depending on the incoming request.
// That allows us to use the same Azure AD app for two subdomains (these two domains give different app behaviour)
var builder = new UriBuilder(context.Request.Uri);
builder.Fragment = builder.Path = builder.Query = "";
context.ProtocolMessage.PostLogoutRedirectUri = builder.ToString();
context.ProtocolMessage.RedirectUri = builder.ToString();
return Task.FromResult(0);
}
}
});
app.Use<EnrichIdentityWithAppUserClaims>();
}
}
public class EnrichIdentityWithAppUserClaims : OwinMiddleware
{
public EnrichIdentityWithAppUserClaims(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
await MaybeEnrichIdentity(context);
await Next.Invoke(context);
}
private async Task MaybeEnrichIdentity(IOwinContext context)
{
ClaimsIdentity openIdUserIdentity = (ClaimsIdentity)context.Authentication.User.Identity;
string userIdentityName = openIdUserIdentity.Name;
var userManager = context.GetUserManager<AppUserManager>();
var appUser = userManager.FindByEmail(userIdentityName);
if (appUser == null)
{
Log.Error("User {name} authenticated with open ID, but unable to find matching user in store", userIdentityName);
return;
}
appUser.DateLastLogin = DateTime.Now;
IdentityResult result = await userManager.UpdateAsync(appUser);
if (result.Succeeded)
{
ClaimsIdentity appUserIdentity = await userManager.CreateIdentityAsync(appUser, DefaultAuthenticationTypes.ExternalBearer);
openIdUserIdentity.AddClaims(appUserIdentity.Claims);
}
}
}
It's quite similar to what I had originally - (note: RoleClaimType = ClaimTypesRoles not "roles") except instead of trying to deal with the user in the SecurityTokenValidated callback, I've added some custom middleware that finds a matching user (by email address) and adds the claims (app roles) from the matching app user to the authenticated user identity (OpenID identity).
Finally, I protected all controller actions with a (custom) AuthorizeAttribute (not shown here) that ensures the authenticated user at least belongs to the "User" role (if not, redirects them to a "no access" page indicating that we've authenticated them but they have no access in the system).
The OpenID auth works, however I'm not sure how to use the existing Identityuser / IdentityUserRole / RoleManager plumbing in conjunction with it.
The application is quite dependent on some custom data associated with these users so simply using the roles from Active Directory isn't an option.
For your requirement, I assume that you could build your identity server (e.g. IdentityServer3) and leverage IdentityServer3.AspNetIdentity for identity management using ASP.NET Identity.
For your web client application, you could use OpenID Connect middleware and set Authority to your custom identity server and set your pre-configured ClientId on your identity server.
Moreover, you could follow this tutorial for quickly getting started with IdentityServer3 and the full samples IdentityServer3 Samples.

How to forward jwt token in Vert.x REST service

I have a Vert.x REST service that receive requests with jwt tokens, and I want to call my another REST service passing received token. Between router handler and WebClient call I have a business logic layer. My question is if there is a method to provide token to webClient other than passing it explicitly through my business logic layer? In other words is it possible to retrieve somehow my RoutingContext and token from e.g. vertxContext or an other component?
Example code demonstrating what I would like to achieve:
Verticle cass
public class RestApiVerticle extends AbstractVerticle {
businessLogicService service;
#Override
public void start() throws Exception {
initService();
HttpServer server = vertx.createHttpServer();
Router router = Router.router(vertx);
JWTAuth authProvider = JWTAuth.create(vertx, getAuthConfig());
router.route("/*").handler(JWTAuthHandler.create(authProvider));
router.route("/somePath").handler(this::handleRequest);
server.requestHandler(router::accept).listen(config().getInteger("port"));
}
private void handleRequest(RoutingContext context){
service.doSomeBusinessLogic(); //I could pass context here, but I thing this is not a proper way to do it, as business logic should not know about RequestContext
}
private void initService(){
ExternalAPICaller caller = new ExternalAPICaller(WebClient.create(vertx));
service = new BusinessLogicService(caller);
}
private JsonObject getAuthConfig() {
return new JsonObject();
}
}
BusinessLogicService:
public class BusinessLogicService {
ExternalAPICaller caller;
public BusinessLogicService(ExternalAPICaller caller){
this.caller = caller;
}
public void doSomeBusinessLogic(){
caller.doSth();
}
}
ExternalAPICaller:
public class ExternalAPICaller {
WebClient client;
public ExternalAPICaller(WebClient client){
this.client = client;
}
public void doSth(){
String TOKEN = null; // I would like to retrive here my token from some vertx component
client.post("externalAPIpath")
.putHeader("Authorization", "Bearer" + TOKEN)
.send(ctx -> {
//(..)
});
}
}
My implementation is in JavaScript (Node.js/Express), but I used cookies to send the JWT to the client.
res.cookie("auth", token);
return res.redirect(`http://localhost:3000/socialauthredirect`);
When you call your do business logic method you could pass the request authorization header value as it contains your untouched jwt token. Then on your web client add a header with that value and of course named authorization and your token is forwarded to the next service.

How to create a user using microstrategy SDK in java (External Security Module)

My goal is to create user using microstrategy SDK and assign filters and groups to the user.
I have a java class CreateUser & SSOESM from SDK. Do I have to create a plugin of the create user class and deploy it in microstrategy intelligence server.
public class CreateUser {
public static WebIServerSession sessionInfo;
public static final String loginName = "NewUser";
public static final String password= "";
public static final String fullName= "New User";
public static final String description="New User Created Programattically";
/* The following information is required to login and manipulate the User management API */
/* iServerName is the IServer we are connecting to */
public static final String iServerName = "localhost";
/* projectName is the project name we are connecting to */
public static final String projectName = "";
/* loginName is the user name we use to login the project */
public static final String adminLoginName = "administrator";
/* loginPasswd is the password we use to login the project */
public static final String adminLoginPasswd = "";
public static void main(String[] args) {
sessionInfo = getServerSession(iServerName, projectName, adminLoginName, adminLoginPasswd);
UserBean user = null;
try {
//Instantiate a new user
user = (UserBean)BeanFactory.getInstance().newBean("UserBean");
user.setSessionInfo(sessionInfo);
user.InitAsNew();
//Fetch properties for the user
WebUser ua = (WebUser) user.getUserEntityObject();
WebStandardLoginInfo loginInfo = ua.getStandardLoginInfo();
//Set basic user information
ua.setLoginName(loginName);
ua.setFullName(fullName);
user.getObjectInfo().setDescription(description);
loginInfo.setPassword(password);
//Set other properties
Calendar cal = Calendar.getInstance();
cal.set(2012, 11, 21);
Date d = cal.getTime();
loginInfo.setPasswordExpirationDate(d); //Password expires on November 21, 2012
loginInfo.setPasswordExpirationFrequency(90); //90 days to expire
loginInfo.setPasswordExpiresAutomatically(true); //If set to false, password never expires
loginInfo.setStandardAuthAllowed(true); //The user can log in using standard auth
user.save();
} catch (WebBeanException ex) {
System.out.println("Error creating a user: " + ex.getMessage());
}
}
public static WebIServerSession getServerSession(String serverName, String Project, String loginName, String password) {
WebIServerSession sessionInfo = null;
try {
WebObjectsFactory woFact = WebObjectsFactory.getInstance();
sessionInfo = woFact.getIServerSession();
sessionInfo.setServerName(serverName);
sessionInfo.setProjectName(Project);
sessionInfo.setLogin(loginName);
sessionInfo.setPassword(password);
sessionInfo.setApplicationType(EnumDSSXMLApplicationType.DssXmlApplicationCustomApp);
//Create a new session
sessionInfo.getSessionID();
} catch (WebObjectsException ex) {
System.out.println("Error creating a sesion");
}
return sessionInfo;
}
}
My goal is when a user try to logon the user should be created on the fly using the sdk classes.
I have to create a plugin and configure the plugin to use the java class you have created as an ESM.
https://lw.microstrategy.com/msdz/MSDZ_World2015/docs/projects/WebSDK/output/HTML5/Content/topics/esm/specifying_the_custom_esm_to_use.htm
With that said its important to understand that the actions you are performing are very expensive. They may degrade the user experience if you are attempting to provide a fast SSO experience. Depending on the implementation you have it may be better to create a custom task, which can be fired when the user authenticates with the third party application. This task can perform all the actions you are describing, and then return a session state. Which can be used in any subsequent connections to MicroStrategy Web.

ASP.NET MVC5 Identity account lockout without Entity Framework

I have created an ASP.NET MVC5 application that allows users to access their financial data including invoices, services and products that they have acquired from our company.
The application gets all data from a set of WCF services including a list of all registered users that have access to the system. I'm using ASP.NET Identity object and claims to authorize users into the application, everything works fine I only have to use the credentials (email and password) to invoke the WCF service which returns an object containing the details about the User or a NULL value if there's no match.
However, there's a new requirement to implement an account lockout after 5 failed login attempts (the account will be locked for 20 minutes before allowing users to try again) which is a feature already included in ASP.NET identity 2.0. I have been "googling" for a couple of days, but couldn't find an example (or even a similar approach) of how to implement this requirement without storing users in Entity Framework and a local DB.
Is there any way of adding just the account lockout feature (with the 20 minutes lockout) using a WCF service as a datasource to my ASP.NET MVC5 application? Any ideas?
This is actually my first ASP.NET MVC5 application, so don't really know much about all features provided on it, any help will be appreciated.
This is how the Login (POST) looks like:
//Authentication handler
IAuthenticationManager Authentication
{
get { return HttpContext.GetOwinContext().Authentication; }
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
try
{
//Validate user from service
CWP_AccountService.User userObject = AccountClient.GetUserByCredentials(model.Email, model.Password);
if (userObject != null)
{
//Create Claims
var identity = new ClaimsIdentity(
new[] { new Claim(ClaimTypes.Name, userObject.Email) },
DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name, ClaimTypes.Role);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userObject.Email));
identity.AddClaim(new Claim(ClaimTypes.Sid, userObject.UserID.ToString()));
int cookieDurationPersistence = 20160;
int cookieDuration = 2;
Authentication.SignIn(new AuthenticationProperties { IsPersistent = model.RememberMe, ExpiresUtc = (model.RememberMe) ? DateTime.Now.AddMinutes(cookieDurationPersistence) : DateTime.Now.AddMinutes(cookieDuration) }, identity);
//Check if there is a local return URL
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/") && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return RedirectToLocal(returnUrl);
}
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("system-error", "The Email/Password provided is not valid.");
}
}
catch(Exception ex)
{
ModelState.AddModelError("system-error", "Error");
logger.Warn(ex.ToString());
}
}
return View(model);
}
The Startup.Auth.cs:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
}
}

Claims transformation in ADFS 3.0 by making a call to REST API

We have a ASP.NET Web API (REST service) in our enterprise that gives us the list of coarse-grained claims for a user that we want to inject into the adfs token before passing the token onto the application. Does anyone know if making a rest call is possible using the Custom attribute store (by passing param's to the custom attribute store from the Claims rule language in ADFS 3.0) ?
Any help regarding this would be greatly appreciated!
Thanks,
Ady.
I'm able to make the REST call from the Custom Attribute store. For those who are still wondering about this can look at the below code.
using System;
using System.Collections.Generic;
using System.Text;
using System.IdentityModel;
using Microsoft.IdentityServer.ClaimsPolicy.Engine.AttributeStore;
using System.Net.Http;
using System.Net;
namespace CustomAttributeStores
{
public class CoarseGrainClaimsAttributeStore : IAttributeStore
{
#region Private Members
private string API_Server = "https://<Server Name>/API/";
#endregion
#region IAttributeStore Members
public IAsyncResult BeginExecuteQuery(string query, string[] parameters, AsyncCallback callback, object state)
{
string result = string.Empty;
if (parameters == null)
{
throw new AttributeStoreQueryFormatException("No query parameter.");
}
if (parameters.Length != 1)
{
throw new AttributeStoreQueryFormatException("More than one query parameter.");
}
string userName = parameters[0];
if (userName == null)
{
throw new AttributeStoreQueryFormatException("Query parameter cannot be null.");
}
//Ignore SSL Cert Error
//TODO: Need to set the SSL cert correctly for PROD Deployment
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (var client = new HttpClient())
{
//The url can be passed as a query
string serviceUrl = API_Server + "GetAdditionalClaim";
serviceUrl += "?userName=" + userName;
//Get the SAML token from the API
result = client
.GetAsync(serviceUrl)
.Result
.Content.ReadAsStringAsync().Result;
result = result.Replace("\"", "");
}
string[][] outputValues = new string[1][];
outputValues[0] = new string[1];
outputValues[0][0] = result;
TypedAsyncResult<string[][]> asyncResult = new TypedAsyncResult<string[][]>(callback, state);
asyncResult.Complete(outputValues, true);
return asyncResult;
}
public string[][] EndExecuteQuery(IAsyncResult result)
{
return TypedAsyncResult<string[][]>.End(result);
}
public void Initialize(Dictionary<string, string> config)
{
// No initialization is required for this store.
}
#endregion
}
}
No, you can't do this with the claims rules language.
This is somewhat of a hack but you could write the claims to e.g. a DB and then use a custom attribute store
If you have access to WIF on the client side, you can augment the claims there e.g. Adding custom roles to windows roles in ASP.NET using claims.