I have an existing financial application which uses an API gateway to authenticate web-based users. This gateway maintains a security session for the user, and proxies SOAP calls on to a WebSphere box. It adds a signed SAML assertion to these SOAP calls.
A series of JAX-WS Services are deployed on WebSphere, and these are protected with WebSphere policies to consume the SAML assertions. The identity and group memberships specified in the SAML assertions are then propagated to the WebSphere security context for the service call. All works very well, all of the security logic is done purely by configuration.
New requirements now require that we propagate the sessionId in the API gateway all the way through to WebSphere , and beyond. This is for reasons of traceability.
Clearly we could change the WSDL for all of the services to include some Meta-data fields, but this is a big change, and would require very extensive testing.
I was hoping there might be a way to map some arbitrary attributes from the SAML assertion (Other than Identity and groupMembership) to the WebSphere security context. Or even to access the SAML XML in the (authenticated) JAX-WS Service.
Has anyone done this?
You can have API gateway to add sessionid as an SAML attribute, then retrieve the attribute from Subject after SAML is processed by WebSphere. Here is sample code to get SAML attribute in WebSphere after SAML is processed.
Subject subject = WSSubject.getRunAsSubject();
SAMLToken samlToken = (SAMLToken) AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction() {
public Object run() throws java.lang.Exception
{
final java.util.Iterator authIterator = subject.getPrivateCredentials(SAMLToken.class).iterator();
if ( authIterator.hasNext() ) {
final SAMLToken token = (SAMLToken) authIterator.next();
return token;
}
return null;
}
});
Map<String, String> attributes = samlToken.getStringAttributes();
List<SAMLAttribute> attributes = samlToken.getSAMLAttributes();
Instead of looping through the creds yourself, you can use the WSSUtilFactory API to do it for you: https://www.ibm.com/support/knowledgecenter/SSAW57_9.0.0/com.ibm.websphere.javadoc.doc/web/apidocs/com/ibm/websphere/wssecurity/wssapi/WSSUtilFactory.html
Assuming that you are using a SAML 2.0 token, you can do:
WSSUtilFactory wssuf = WSSUtilFactory.getInstance();
SAMLToken token = wssuf.getSaml20Token();
The getSaml20Token method was added to WSSUtilFactory in 70043, 80013, 85510 and 9000.
Related
We're developing an application that uses a back-end built on .Net Core 2.2 Web API. Most of our controllers merely require the [Authorize] attribute with no policy specified. However, some endpoints are going to require the user to be in a particular Azure AD Security Group. For those cases, I implemented policies like this in the Startup.cs file:
var name = "PolicyNameIndicatingGroup";
var id = Guid.NewGuid; // Actually, this is set to the object ID of the group in AD.
services.AddAuthorization(
options =>
{
options.AddPolicy(
name,
policyBuilder => policyBuilder.RequireClaim(
"groups",
id.ToString()));
});
Then, on controllers requiring this type of authorization, I have:
[Authorize("PolicyNameIndicatingGroup")]
public async Task<ResponseBase<string>> GroupProtectedControllerMethod() {}
The problem is that our users are all in a large number of groups. This causes the Graph API to return no group claims at all, and instead a simple hasGroups boolean claim set to true. Therefore, no one has any groups, and thus cannot pass authorization. This no-groups issue can be read about here.
This string-based policy registration, lackluster as it may be, seems to be what the .Net Core people are recommending, yet it falls flat if the groups aren't populated on the User Claims. I'm not really seeing how to circumnavigate the issue. Is there some special way to set up the AppRegistration for my API so that it does get all of the groups populated on the User Claims?
Update:
In the solution, I do have a service that calls Graph to get the user's groups. However, I can't figure out how to call it before it's too late. In other words, when the user hits the AuthorizeAttribute on the controller to check for the policy, the user's groups have not yet been populated, so the protected method always blocks them with a 403.
My attempt consisted of making a custom base controller for all of my Web API Controllers. Within the base controller's constructor, I'm calling a method that checks the User.Identity (of type ClaimsIdentity) to see if it's been created and authenticated, and, if so, I'm using the ClaimsIdentity.AddClaim(Claim claim) method to populate the user's groups, as retrieved from my Graph call. However, when entering the base controller's constructor, the User.Identity hasn't been set up yet, so the groups don't get populated, as previously described. Somehow, I need the user's groups to be populated before I ever get to constructing the controller.
I found an answer to this solution thanks to some tips from someone on the ASP.NET Core team. This solution involves implementing an IClaimsTransformation (in the Microsoft.AspNetCore.Authentication namespace). To quote my source:
[IClaimsTransformation] is a service you wire into the request pipeline which will run after every authentication and you can use it to augment the identity as you like. That would be where you’d do your Graph API call [...]."
So I wrote the following implementation (see an important caveat below the code):
public class AdGroupClaimsTransformer : IClaimsTransformation
{
private const string AdGroupsAddedClaimType = "adGroupsAlreadyAdded";
private const string ObjectIdClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier";
private readonly IGraphService _graphService; // My service for querying Graph
private readonly ISecurityService _securityService; // My service for querying custom security information for the application
public AdGroupClaimsTransformer(IGraphService graphService, ISecurityService securityService)
{
_graphService = graphService;
_securityService = securityService;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claimsIdentity = principal.Identity as ClaimsIdentity;
var userIdentifier = FindClaimByType(claimsIdentity, ObjectIdClaimType);
var alreadyAdded = AdGroupsAlreadyAdded(claimsIdentity);
if (claimsIdentity == null || userIdentifier == null || alreadyAdded)
{
return Task.FromResult(principal);
}
var userSecurityGroups = _graphService.GetSecurityGroupsByUserId(userIdentifier).Result;
var allSecurityGroupModels = _securityService.GetSecurityGroups().Result.ToList();
foreach (var group in userSecurityGroups)
{
var groupIdentifier = allSecurityGroupModels.Single(m => m.GroupName == group).GroupGuid.ToString();
claimsIdentity.AddClaim(new Claim("groups", groupIdentifier));
}
claimsIdentity.AddClaim(new Claim(AdGroupsAddedClaimType, "true"));
return Task.FromResult(principal);
}
private static string FindClaimByType(ClaimsIdentity claimsIdentity, string claimType)
{
return claimsIdentity?.Claims?.FirstOrDefault(c => c.Type.Equals(claimType, StringComparison.Ordinal))
?.Value;
}
private static bool AdGroupsAlreadyAdded(ClaimsIdentity claimsIdentity)
{
var alreadyAdded = FindClaimByType(claimsIdentity, AdGroupsAddedClaimType);
var parsedSucceeded = bool.TryParse(alreadyAdded, out var valueWasTrue);
return parsedSucceeded && valueWasTrue;
}
}
Within my Startup.cs, in the ConfigureServices method, I register the implementation like this:
services.AddTransient<IClaimsTransformation, AdGroupClaimsTransformer>();
The Caveat
You may have noticed that my implementation is written defensively to make sure the transformation will not be run a second time on a ClaimsPrincipal that has already undergone the procedure. The potential issue here is that calls to the IClaimsTransformation might occur multiple times, and that might be bad in some scenarios. You can read more about this here.
You can use the Microsoft Graph API to query the user's groups instead:
POST https://graph.microsoft.com/v1.0/directoryObjects/{object-id}/getMemberGroups
Content-type: application/json
{
"securityEnabledOnly": true
}
Reference: https://learn.microsoft.com/en-us/graph/api/directoryobject-getmembergroups?view=graph-rest-1.0&tabs=http
The scenario will be:
Your client app will acquire access token (A) for accessing your back-end Web API.
Your Web API application will acquire access token (B) for accessing the Microsoft Graph API with the access token (A) using OAuth 2.0 On-Behalf-Of flow. Access token (B) will be used to get the user's groups.
Web API validates the user's group using a policy (recommended) or custom attribute.
The protocol diagram and sample request are listed in this article using the Azure AD V2.0 Endpoint. This article is for the V1.0 endpoint. Here are code samples for .Net Core.
We are using Azure AD authentication for one of our client application. We want to implement claims based authorization along with it.
Our application set up is Angular Based client app connecting with Web API (both client server secured using Azure AD Bearer Authentication). Server application is hosted using OWIN.
We need to provide custom authorization on server side. There is a provision in Azure AD for adding users and roles. However, that is not enough for us. Our user management is through AD & Security Groups. To gain access to application, users need to part of a base group and further rights (access particular section of application, edit a specific entity etc.) are assigned based on additional groups or given directly to users in the application. Essentially, not all users will be registered in the application and we may have to query the AD using graph API to check which all application specific groups they belong.
OWIN authentication and authorization model is based on Authentication Server and Resource server. We can separate them on need basis. However, in our case, we need to split the authentication and authorization. When the client presents the bearer token, we need to verify if the token is valid and then add claims to user profile. We also need to cache the user claims so that we do not hit the database frequently. (Our client app make multiple Web API calls in one user action.)
What is the location in Identity 2.0 where
I can verify the token &
insert application specific claims
If my entire application revolves around the user authorization and all queries need to be filtered on what data the user can access, which is a more suitable design pattern for the Web API application?
I believe what you're looking for are the Authentication and Authorization filters in the ASP.NET Web API 2.0 stack.
You can implement per-web method authorization by implementing System.Web.Http.Filters.IAuthorizationFilter on an attribute class, then decorate the web action methods of your service controller with that attribute. Web API 2.0 will select a method based on URL routing, notice that there is an attribute on that method implementing IAuthorizationFilter, and will call the ExecuteAuthorizationFilterAsync method on that attribute instance before calling the web method. Placing the authorization step before the web method invocation allows invalid requests to be discarded quickly, before getting into the heavy lifting of parameter binding.
The incoming token is validated by an IAuthenticationFilter implementation which executes before the authorization step.
Documentation and examples are extremely hard to find. Here's one of the few search results that are actually relevant: http://thegrumpycoder.com/post/105427070626/secure-web-services-with-web-api-and-sitecore
you can check if this helps...
UserProfile profile = new UserProfile(); //To deserialize the response stream (JSON)
string tenantId = ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value;
AuthenticationResult result = null;
try
{
// Get the access token from the cache
string userObjectID =
ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")
.Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
//use ClientID, ClientSecret
ClientCredential credential = new ClientCredential("b557ceed-xxxx-xxxx-xxxx-xxxxxxxbc240", "AXFxx//xxxxxxxxxxxxxjVFz4sqYm8NDAPEOLkU=");
result = authContext.AcquireTokenSilent("https://graph.windows.net", credential,
new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
// AcquireTokenSilent may throw exception if the cache is empty. In that case, logout the user and make him login.
string requestUrl = String.Format(
CultureInfo.InvariantCulture,
"https://graph.windows.net/cdmsdev.onmicrosoft.com/groups/b40xxxx-14a8-xxxx-9559-xxxxxxca90c8/members/?api-version=1.6");
//Above grap API url is for getting list of users who belong to a specific group (with GUID b40xxxx-1....)
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
var upn = ClaimsPrincipal.Current.Identity.Name;
string responseString = response.Content.ReadAsStringAsync().Result;
profile = JsonConvert.DeserializeObject<UserProfile>(responseString);
if (profile.Users.Contains(upn)) //check if the current user is in the list of users of the Admin group
return true;
}
}
catch (Exception e)
{
//handle authorization exception here
}
The graph API URL can be replaced with a function to check for membership of a specific group which will directly return a bool value instead of getting all users of that group.
The current application that we are developing consists of 2 applications. A WebApi application, and a MVC frontend application. For the WebApi i added support for bearer token authorization via OWIN. These applications run as seperate websites within the same domain but with their own subdomains site.xxx.xxx, api.xxx.xxx
Authenticating to the WebAPi, f.e. with Postman, works as designed, the principal and identity objects, including the claims, are initialized properly.
The question arises when i want to login to the WEbApi from within the Mvc application.
Is there any way to get the ClaimsPrincipal and the ClaimsIdentity in our MVC application after logging in via the WebAPI via the /token url somewhat sharing the OWIN context, or should we implement the same OWIN authorization functionality inside the MVC application to create a seperate autorization "route"?
Yes, there is. Couple things to note
The token you get back from the web api will be encrypted by default. Your web application needs to decrypt this token to be able to extract the claims from the bearer token. For this, you have to have the same machine key on both of the servers (your webapi web.config and mvc web.config needs to have the same machine key)
Your MVC web app needs to wire up both bearer tokens and application cookies. Your startup.auth.cs might include something like this:
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
static Startup()
{
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
}
public void ConfigureAuth(IAppBuilder app)
{
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
}
Now in your login method
//Assume that the token that you got from web api is in the variable called accessToken
//Decrypt this token first. If your machine keys are the same, the following line will work
var unencryptedToken = Startup.OAuthBearerOptions.AccessTokenFormat.Unprotect(accessToken);
//Next, extract the claims identity from the token
var identity = unencryptedToken.Identity;
//Need to create a claims identity that uses a cookie (not a bearer token). An MVC app
//knows how to deal with a claims identity using an application cookie, but doesn't know
//how to deal with a claims identity using a bearer token. So this is a translation step
//from a web api authentication mechanism to the mvc authentication mechanism
var id = new ClaimsIdentity(identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
//At this moment, your new claims identity using an application cookie is ready, but we still
//need to sign in. Use the OWIN Auth manager from the context to sign in. This will create
//the application cookie and correctly populate User.IsAuthenticated(). From now on, you are
//logged in
AuthenticationManager.SignIn(id);
I have succesfully created a REST web service with Jersey and secured it via java security annotations.
It looks something like this
GET /users/ // gives me all users
GET /users/{id} // gives the user identified by {id}
POST /users/ // creates user
PUT /users/{id} // updates user identified by {id}
DELETE /users/{id} // delete user
I also have setup a realm with two roles: user and admin
I secured all methods so that only admins can access them.
Now i want to give free the PUT /users/{id} and GET /users/{id} methods, so that users can access their own and only their own resources.
Example:
// user anna is logged in and uses the following methods
GET /users/anna // returns 200 OK
GET /users/pete // returns 401 UNAUTHORIZED
Since i could not find a way to configure this through annotations, I am thinking of passing the HTTP request to the corresponding method to check if the user is allowed to access the resource.
It would look something like this for the GET /users/{id} method:
#GET
#Path("/users/{id}")
#RolesAllowed({"admin","user"})
#Produces(MediaType.APPLICATION_JSON)
public Response getUser(
#PathParam("id") String id,
#Context HttpServletRequest req
) {
HttpSession session = request.getSession(false);
if (session != null && session.getValue("userID").equals(id))
return getObject(User.class, id);
return Response.status(Status.UNAUTHORIZED).build();
}
I don't like this aproach because i think i would have to add the userID manualy to the session.
Do you know a more elegant way to solve this?
If not how do you add the userid to the session while using form authentication?
EDIT
Thank you Will and Pavel :) Here is my final solution:
#Context
private SecurityContext security;
// ...
#GET
#Path("/users/{id}")
#RolesAllowed({"admin","user"})
#Produces(MediaType.APPLICATION_JSON)
public Response getUser(#PathParam("id") String id){
if (security.isUserInRole("user"))
if (security.getUserPrincipal().getName().equals(id))
return getObject(User.class, id);
else
return Response.status(Status.UNAUTHORIZED).build();
else
return getObject(User.class, id);
}
In the HttpServletRequest, you can call getRemoteUser() or getUserPrincipal() to get the identity of the logged in user. You would then continue like you are doing in specifically allowing or denying them access to the particular resource.
Blessed Geek is referring more specifically to the aspect of REST regarding stateless transactions and the use of HTTP authentication. While this is an important point in the larger scope of a REST architecture, it's less relevant to your specific question since you don't specify the type of authentication mechanism you're using against your Java EE app, especially since authentication is a container issue in Java EE, not an application issue.
If you're using basic authentication, then you are using HTTP headers to manage authentication and authorization. If you're using form based authentication, then the container is managing this for you via the servlet session, making the service stateful (since sessions are a stateful artifact).
But this has no bearing on your specific question.
One of the most important aspects of deploying REST is understanding the role of http headers and cookies.
For REST to be practical, you need to deploy an authentication framework.
Read
GWT and Google Docs API.
GWT-Platform login + session management
Read up on Google Federated Login, OAuth and OpenID.
Some of my explanations may be outdated, if they were posted before OAuth 2.0.
I've a wcf service deployed to the cloud. Could anyone guuide me through best practices on how I can secure the end point in azure please?
Thanks.
In my opinion, the easiest approach is to use the AppFabric Access Control Service (ACS) to generate a Secure Web Token (SWT) that you pass to the WCF service via an authorization HTTP header. In the service method, you can then read and validate the SWT from the header.
It's pretty straightforward, particularly if you create proxies dynamically rather than using Service References.
This is how I get the SWT from ACS:
private static string GetToken(string serviceNamespace, string issuerKey, string appliesto)
{
WebClient client = new WebClient();
client.BaseAddress = String.Format("https://{0}.accesscontrol.windows.net", serviceNamespace);
client.UseDefaultCredentials = true;
NameValueCollection values = new NameValueCollection();
values.Add("wrap_name", serviceNamespace);
values.Add("wrap_password", issuerKey);
values.Add("wrap_scope", appliesto);
byte[] responseBytes = client.UploadValues("WRAPv0.9", "POST", values);
string response = System.Text.Encoding.UTF8.GetString(responseBytes);
string token = response
.Split('&')
.Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))
.Split('=')[1];
return token;
}
issuerKey, as it was referred to in ACS v1 is now the Password from the Service Identity in ACS v2.
To call the service:
string accessToken = GetToken(serviceNamespace, issuerKey, appliesto);
string authHeaderValue = string.Format("WRAP access_token=\"{0}\"", HttpUtility.UrlDecode(accessToken));
// TInterface is the service interface
// endpointName refers to the endpoint in web.config
ChannelFactory channelFactory = new ChannelFactory<TInterface>(endpointName);
TInterface proxy = channelFactory.CreateChannel();
OperationContextScope scope = new OperationContextScope(proxy as IContextChannel);
WebOperationContext.Current.OutgoingRequest.Headers.Add(HttpRequestHeader.Authorization, authHeaderValue);
// Call your service
proxy.DoSomething();
On the service-side, you extract the token from the header and validate it. I can find out the code for that, if this looks like the approach you want to take.
Try this blog post by Alik Levin as a good starting point.
A typical, broadly interoperable approach would be to use HTTP Basic Authentication over an SSL connection. The approach for running this in Azure is really very similar to how you would achieve this on a traditional Windows server.
You can implement an IIS Http Module and provide your own implementation of a BasicAuthenticationModule - this can work however you want but calling in to ASP.NET Membership (a call to ValidateUser) would be a common approach. The store for that can be hosted in SQL Azure.
You can then surface this to WCF by implementing IAuthorizationPolicy and adding this to your authorizationPolicies WCF config element.
The Patterns and Practices team have a walkthrough of this with complete code at
http://msdn.microsoft.com/en-us/library/ff649647.aspx. You can ignore the brief Windows Forms discussion - being web services, their choice of client is irrelevant.