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

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.

Related

Return a custom response when using the Authorize Attribute on a controller

I have just implemented the Bearer token and I have added the Authorize attribute to my controller class and that works fine. It looks like this:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
What I would like to do is to create a more complex response from the server when it fails, rather then the standard 401.
I tried filters but they are not invoked at all.
Any ideas how to do this?
Have a custom scheme, custom authorization handler and poof!
Notice that I injected the Handler in ConfigureServices:
services.AddAuthentication(options =>
{
options.DefaultScheme = ApiKeyAuthenticationOptions.DefaultScheme;
options.DefaultAuthenticateScheme = ApiKeyAuthenticationOptions.DefaultScheme;
})
.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(
ApiKeyAuthenticationOptions.DefaultScheme, o => { });
ApiKeyAuthenticationOptions
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
{
public const string DefaultScheme = "API Key";
public string Scheme => DefaultScheme;
public string AuthenticationType = DefaultScheme;
public const string HeaderKey = "X-Api-Key";
}
ApiKeyAuthenticationHandler
/// <summary>
/// An Auth handler to handle authentication for a .NET Core project via Api keys.
///
/// This helps to resolve dependency issues when utilises a non-conventional method.
/// https://stackoverflow.com/questions/47324129/no-authenticationscheme-was-specified-and-there-was-no-defaultchallengescheme-f
/// </summary>
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
private readonly IServiceProvider _serviceProvider;
public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options, ILoggerFactory logger,
UrlEncoder encoder, ISystemClock clock, IServiceProvider serviceProvider)
: base (options, logger, encoder, clock)
{
_serviceProvider = serviceProvider;
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var token = Request.Headers[ApiKeyAuthenticationOptions.HeaderKey];
if (string.IsNullOrEmpty(token)) {
return Task.FromResult (AuthenticateResult.Fail ("Token is null"));
}
var customRedisEvent = _serviceProvider.GetRequiredService<ICustomRedisEvent>();
var isValidToken = customRedisEvent.Exists(token, RedisDatabases.ApiKeyUser);
if (!isValidToken) {
return Task.FromResult (AuthenticateResult.Fail ($"Invalid token {token}."));
}
var claims = new [] { new Claim ("token", token) };
var identity = new ClaimsIdentity (claims, nameof (ApiKeyAuthenticationHandler));
var ticket = new AuthenticationTicket (new ClaimsPrincipal (identity), Scheme.Name);
return Task.FromResult (AuthenticateResult.Success (ticket));
}
}
Focus on the handler class. Apart from the sample code I've provided, simply utilise the base class properties like Response to set your custom http status code or whatever you may need!
Here's the derived class if you need it.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationhandler-1?view=aspnetcore-3.1

Did Facebook change their App developers API on March 2017?

Well, I am a new guy who is trying to develop a web application whose user are will be authenticated through Facebook. I am developing the application in MVC 4 .Net Framework. As it's internal programs are already done so I need not to do much coding. I have just put the API ID and Secret Key in the scope
OAuthWebSecurity.RegisterFacebookClient(
appId: "750397051803327",
appSecret: "**************************");
And here is my Application http://imgur.com/a/k4Vd0
My Application is taking properly the user permission from the user perfectly. http://imgur.com/a/bqzj5 but after taking permission it is not providing the login state of the user by showing such exception http://imgur.com/a/h81Oh login failed. I debugged form the code end and I observed that it is sending isLoggedin as false http://imgur.com/a/UuLIe therefore my I am not getting the access.
However 2 days before I am not getting such exception. I was getting data simply fine. Here is a snapshot of my previous data. http://imgur.com/a/Bc49F
I need that data again, but how? Is there anything need to change in my application dashboard? Possibly I have changed something in application dashboard. if yes then what is particularly that?
Another things I'm confused that what is the need for PRODUCTS? Do I need anything from the products for this special reason to get the data. If yes then which one shall I need and how to configure it to get back my previous systematic process in which I was getting data smoothly.
If I add App Center from the PRODUCTS I am obtaining two other secret keys like Account Kit App Secret and Account Kit Client Token Is that I need to use these keys for my requested case. For such login approval specific which Products are need or nothing need at all from PRODUCTS. I am so confused about it how to configure an application.
Please suggest me how to solve this problem in addition how to configure my application API. Thank you.
According to Previous Answer I got my solution. In MVC4 everyone write down their AppID and SecurityCode. Due to change of facebook GRAPH API those previous links are broken. Consequently everyone need to change the RegisterFacebookClient calss. But this class is a sealed class in the .Net library, so anyone can't extend or overwrite it. As a result we need to use a wrapper class. Let us consider my Wrapper class is FacebookClientV2Dot3 therefore my class will be
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using DotNetOpenAuth.AspNet.Clients;
using Newtonsoft.Json;
public class FacebookClientV2Dot3 : OAuth2Client
{
#region Constants and Fields
/// <summary>
/// The authorization endpoint.
/// </summary>
private const string AuthorizationEndpoint = "https://www.facebook.com/dialog/oauth";
/// <summary>
/// The token endpoint.
/// </summary>
private const string TokenEndpoint = "https://graph.facebook.com/oauth/access_token";
/// <summary>
/// The user info endpoint.
/// </summary>
private const string UserInfoEndpoint = "https://graph.facebook.com/me";
/// <summary>
/// The app id.
/// </summary>
private readonly string _appId;
/// <summary>
/// The app secret.
/// </summary>
private readonly string _appSecret;
/// <summary>
/// The requested scopes.
/// </summary>
private readonly string[] _requestedScopes;
#endregion
/// <summary>
/// Creates a new Facebook OAuth2 client, requesting the default "email" scope.
/// </summary>
/// <param name="appId">The Facebook App Id</param>
/// <param name="appSecret">The Facebook App Secret</param>
public FacebookClient(string appId, string appSecret)
: this(appId, appSecret, new[] { "email" }) { }
/// <summary>
/// Creates a new Facebook OAuth2 client.
/// </summary>
/// <param name="appId">The Facebook App Id</param>
/// <param name="appSecret">The Facebook App Secret</param>
/// <param name="requestedScopes">One or more requested scopes, passed without the base URI.</param>
public FacebookClient(string appId, string appSecret, params string[] requestedScopes)
: base("facebook")
{
if (string.IsNullOrWhiteSpace(appId))
throw new ArgumentNullException("appId");
if (string.IsNullOrWhiteSpace(appSecret))
throw new ArgumentNullException("appSecret");
if (requestedScopes == null)
throw new ArgumentNullException("requestedScopes");
if (requestedScopes.Length == 0)
throw new ArgumentException("One or more scopes must be requested.", "requestedScopes");
_appId = appId;
_appSecret = appSecret;
_requestedScopes = requestedScopes;
}
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
var state = string.IsNullOrEmpty(returnUrl.Query) ? string.Empty : returnUrl.Query.Substring(1);
return BuildUri(AuthorizationEndpoint, new NameValueCollection
{
{ "client_id", _appId },
{ "scope", string.Join(" ", _requestedScopes) },
{ "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
{ "state", state },
});
}
protected override IDictionary<string, string> GetUserData(string accessToken)
{
var uri = BuildUri(UserInfoEndpoint, new NameValueCollection { { "access_token", accessToken } });
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
using (var webResponse = webRequest.GetResponse())
using (var stream = webResponse.GetResponseStream())
{
if (stream == null)
return null;
using (var textReader = new StreamReader(stream))
{
var json = textReader.ReadToEnd();
var extraData = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
var data = extraData.ToDictionary(x => x.Key, x => x.Value.ToString());
data.Add("picture", string.Format("https://graph.facebook.com/{0}/picture", data["id"]));
return data;
}
}
}
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
var uri = BuildUri(TokenEndpoint, new NameValueCollection
{
{ "code", authorizationCode },
{ "client_id", _appId },
{ "client_secret", _appSecret },
{ "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
});
var webRequest = (HttpWebRequest)WebRequest.Create(uri);
string accessToken = null;
HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
// handle response from FB
// this will not be a url with params like the first request to get the 'code'
Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet);
using (StreamReader sr = new StreamReader(response.GetResponseStream(), rEncoding))
{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var jsonObject = serializer.DeserializeObject(sr.ReadToEnd());
var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject));
Dictionary<string, object> desirializedJsonObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(jConvert.ToString());
accessToken = desirializedJsonObject["access_token"].ToString();
}
return accessToken;
}
private static Uri BuildUri(string baseUri, NameValueCollection queryParameters)
{
var keyValuePairs = queryParameters.AllKeys.Select(k => HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(queryParameters[k]));
var qs = String.Join("&", keyValuePairs);
var builder = new UriBuilder(baseUri) { Query = qs };
return builder.Uri;
}
/// <summary>
/// Facebook works best when return data be packed into a "state" parameter.
/// This should be called before verifying the request, so that the url is rewritten to support this.
/// </summary>
public static void RewriteRequest()
{
var ctx = HttpContext.Current;
var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]);
if (stateString == null || !stateString.Contains("__provider__=facebook"))
return;
var q = HttpUtility.ParseQueryString(stateString);
q.Add(ctx.Request.QueryString);
q.Remove("state");
ctx.RewritePath(ctx.Request.Path + "?" + q);
}
}
Look here you I have replaces all the API links by newer version links.
Now you need to modify your
AuthConfig
Just use a wrapper class
OAuthWebSecurity.RegisterClient(new FacebookClientV2Dot3("AppID", "HassedPassword"));
Then all success. You facebook login will be back in previous state.
However you can face a new issue regarding this new API rather than previous API, the problem is that IP Whitelisting. Like this image. Hope you will need nothing but this. Happy coding.
Yes!!!!
I got the solution of my own problem. According to Facebook developers bug report all Facebook log is not working from 28th March 2017. They also let us know through their developers Facebook group. The post link is here.
According to one of the Developer teams had said we're finding that
facebook authentication just stopped working (2pm EST) across multiple
apps that we manage. apps haven't changed, apps haven't been
suspended..... not sure where to report this since "status" is all
good......

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.

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

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.