Our team is using Itfoxtec as the saml2 handler in our SP as follows:
A client clicks on the link of the login API.
The API redirects the user to the IdP login page.
On successful login, The API gets a SAML2 response to the ASC route.
We fetch the claims from the response.
If the user is good to go, we generate a JWT token to be used in next requests to other services using the Authorization header, otherwise we send unauthorized response.
Here is the configuration of the handler:
builder.Services
.AddAuthentication("saml2")
.AddCookie("saml2", cookieAuthenticationOptions =>
{
cookieAuthenticationOptions.SlidingExpiration = true;
cookieAuthenticationOptions.LoginPath = new PathString("/saml/request");
cookieAuthenticationOptions.Cookie.SameSite = SameSiteMode.None;
cookieAuthenticationOptions.Cookie.SecurePolicy = CookieSecurePolicy.Always;
Task UnAuthorizedResponse(RedirectContext<CookieAuthenticationOptions> context) =>
Task.FromResult(context.Response.StatusCode = (int)HttpStatusCode.Unauthorized);
cookieAuthenticationOptions.Events.OnRedirectToAccessDenied = UnAuthorizedResponse;
cookieAuthenticationOptions.Events.OnRedirectToLogin = UnAuthorizedResponse;
});
As we can see that the use of Itfoxtec is done in the initial login process only, where the user either gets unauthorized response, or gets JWT token and no additional calls to the SP is done.
Note that when I remove the Cookie related configurations, I get an exception thrown while fetching the claims and creating a session on the following line:
customClaims = await saml2AuthnResponse.CreateSession(HttpContext, claimsTransform: GetCustomClaimsPrincipal);
Our question is, is there any way else to use the Itfoxtec as a SAML2 handler but without using Cookies?
You do not need to use session cookies. If you do not use session cookies you should not call the CreateSession method which create the cookie based .NET session.
The SAML 2.0 Authn request is validated in the Unbind method and you can thereafter safely read the users claims in saml2AuthnResponse.ClaimsIdentity.Claims.
FoxIDs use the ITfoxtec Identity SAML 2.0 component without using the cookie based .NET session, you can see how the SAML 2.0 Authn request is validated in the AuthnResponseAsync method.
Related
I have a MVC app that has a couple of WebAPI endpoints. The reason for this is we have an app that should be able to communicate with the application. For authentication, I am using Identity.
After I logged in and want to log out again, it works in the UI. So I implemented the same logic in the Logout actions in one of my API endpoints:
public async Task<JsonResult> LogOut()
{
await _signInManager.SignOutAsync();
_logger.LogInformation("User logged out.");
return new JsonResult(new { Anything = "Logout successful." });
}
I also tried most of the approaches I found here when I search for the same question. However, no matter what I do, the cookie ".AspNetCore.Identity.Application" is always in the next request and I am still authenticated.
I am using Postman to test the API endpoints, if that makes any difference.
Right now you only have cookie authentication and when you call SignOutAsync() a response is being generated which indicates to delete the authentication cookie and in response to that browser deletes the authentication cookie so in the next call there is no cookie and you are not logged in anymore but if you store the authentication cookie before sign out and then add it to the browser after you sign out you are still logged in because your credential is in the cookie.
So this is how browser behaves, and you don't have this behaviour in postman or HttpClient.
Your options are to either use Reference Token for your apis
or
You can config a Session Store with your Cookie Authentication
I've got all the code working to generate the JWT and I've wired up my ConfigureServices with the proper config code but I'm not seeing the header actually get set.
I assumed that the middleware would do this for you but maybe not, is it up to me to return the token from the login method of my controller and the client to then take it and set the header for subsequent requests?
No it does not.
The way it works is that you send your login credentials to a login server. In most cases its the same but in more secure applications this won't be the case.
The server then authenticates your credentials, creates a JWT token and sends that back to you.
You can then use that JWT in your header when making a request to the application server:
"Authorization":"Bearer xxxxx.yyyyy.zzzzz"
This needs to be done with every call to the server because the point of JWT is that it is stateless, meaning the server does not save the data at all. Instead in reads the JWT token with every call and grants access/functionality based on that.
I have an on-premise Dynamics CRM (2016) that is configured with ADFS (3.0). When a user want's to Login, they get redirected to the ADFS login page and the user enter their Windows AD credentials.
From a .net core application I need to make request to the CRM api using HttpClient. When I try to send the credentials like I normally would for a Windows Auth CRM it doesnt work. I get a 401 Unauthorized. Like below.
HttpClient client = new HttpClient(new HttpClientHandler() { Credentials = new NetworkCredential("myuser", "mypassword", "mydomain") });
var result = client.GetAsync("https://mycrmaddress/api/data/v8.0/accounts");
I also tried using Adal to retrieve a token and attach it as a bearer token to the request but I'm unable to get a token with adal. When I try I receive the following:
The authorization server does not support the requested 'grant_type'. The authorization server only supports 'authorization_code'
ADFS 3.0 doesn't support this flow.
I cannot upgrade to ADFS 4.0 so I would like to know what are my options to make an authenticated call to CRM api (without prompting a login window as this application is a service).
Is there any configuration I can do on ADFS so my first example work? Or is it possible to do it with Adal even if it's ADFS 3.0? Or any other solution...
I found the answer to my question. It's kinda hackish, but I tested it myself and it works. As a temporary solution this will do the trick.
Details are available here: https://community.dynamics.com/crm/f/117/t/255985
ADFS 3.0 supports the Authorization Code flow and this what we will use in this case.
We need to retrieve an authorization code. Normally at this steps a windows is prompted to the user to enter its credentials. By doing a POST and sending the user/password it's possible to retrieve an authorization code.
{authProvider} - ADFS Uri - something like
https://adfs.mycompany.com/adfs/oauth2/
{ClientId} - The Guid used to
by your infrastructure team to add your application to ADFS
{RedirectUri} - The IFD Uri for dynamics - should match the redirect
Url used to by your infrastructure team to add your application to
ADFS
username - The User set up on ADFS and in Dynamics
password - The password for the above user
Then we make the following call with these information using HttpClient.
var uri = $"{authProvider}authorize?response_type=code&client_id={clientId}&resource={redirectUri}&redirect_uri={redirectUri}";
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string,string>("username",username),
new KeyValuePair<string,string>("password",password),
});
var responseResult = _httpManager.PostAsync(uri, content).Result;
The response content will be an html page (Remember normally this flow prompts a login page to the user). In this page there will be a form that contains the authorization code. using a library like HtmlAgilityPack retrieve the token. This is the hackish part of the solution.
Now that we have an authorization code we need to retrieve an access token.
For that we need to make the following call
var uri = $"{authProvider}token";
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string,string>("grant_type","authorization_code"),
new KeyValuePair<string,string>("client_id",clientId),
new KeyValuePair<string,string>("redirect_uri",redirectUri),
new KeyValuePair<string,string>("code",code)
});
var response = await _httpManager.PostAsync(uri, content);
The response content will be a json string that will contain the access token.
With the access token, make the call to CRM rest API.
You will need to attach the token to the HttpClient in the header as a bearer token.
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",token);
httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
From now on you can make calls to CRM api and you will be authorized. However be carefull normally access token are short lived. You will either need to increase their lifetime or request a new token everytime it's expired.
I'm updating our MVC application to use IdentityServer v3 to implement OpenID Connect authentication using implicit flow.
I've overridden the IdentityServer AuthenticateLocalAsync method of the UserServiceBase class to call into our existing authentication service to validate the supplied credentials. This service also sets various cookies required by our application.
The issue I'm encountering is that upon entering the credentials in the login page, it doesn't redirect to the ReturnUri specified in the OpenIdConnectAuthenticationOptions parameter of UseOpenIdConnectAuthentication method. Instead it remains on the login page and appears to go into an infinite loop, alternating between calls to the notification handlers SecurityTokenValidated and SecurityTokenReceived. There is no indication of any error in the IdentityServer logs.
I think the cause of the issue is related to setting the application-specific cookies in our authentication service. If I comment out the following, it then successfully redirects upon logging in:
var cookie = new HttpCookie("AppCookie") { HttpOnly = false, Secure = true };
cookie ["CustomField1"] = HttpUtility.UrlEncode(customValue1);
cookie ["CustomField2"] = HttpUtility.UrlEncode(customValue2);
this._httpContext.Response.Cookies.Set(cookie);
However these cookies are required by our application and so need to be set in the response. Why would setting these cookies prevent it from redirecting? Are there any workarounds? Any assistance would be much appreciated.
I have an Owin based Web App and a backend Web API, they are authenticated against AAD and the workflow can be describe as below listed.
Web App authenticates end users against AAD using Federation Authentication.
Web App requests a JWT from AAD for accessing the backend Web API.
The main code for authenticating end users.
public void ConfigureAuth(IAppBuilder app)
{
// other code...
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata
});
}
The main code for getting JWT for accessing the backend API:
internal async Task<string> GetAccessToken()
{
var authContext = new AuthenticationContext(authority);
var credential = new ClientCredential(clientId, appKey);
var result = await authContext.AcquireTokenAsync(apiId, credential);
// Here, what I wanted is to use the other overloaded method
// authContext.AcquireTokenAsync(apiId, credential, userAssertion);
// But to instantiate a UserAssertion instance, the only way is
// to use the constructor new UserAssertion(assertionString)
// and the assertionString should be in JWT format
// unfortunately, the assertionString from Ws-Federation auth is
// for sure in SAML2 format. So, the question is:
// Give I am using Ws-Federation auth protocal, How can I pass the
// user information in requesting a JWT to backend API resource?
return result.AccessToken;
}
Generally, the whole authentication workflow is OK, I can both authenticate end users and get JWT for accessing backedn APIs. But the problem is that there is no end user claims in the JWT. I am sure I should get users claims from the federation authentication result and then put them in the process of requesting the JWT. Unfortunately, with all methods, libraries and classes I didn't find a solution to do that.
BTW, https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect gives an example how to obtain a JWT with end user claims included, but the solution does not work with my scenario as I am using Federation authentication rather than OpenID Connect.
Edit
To make the question clear: in the web app, I would like to request a JWT token for accessing the backend web api by using the method AuthenticationContext.AcquireTokenAsync.
From my demo code, you can see I am using the AcquireTokenAsync(apiId, clientCredential) overloaded verion. But this version does not attach the end users claims inside. Actually what I needed is the AcquireTokenAsync(apiId, clientCredential, userAssertion) overloaded method.
However, to instantiate a UserAssertion, I need the user assertion string which is the AccessToken from user authentication result. Unfortunetaly, the UserAssertion class only accept JWT format assertion string, but the Ws-Federation authentication returns the SAML2 format assertion string, so I am not able to instantiate a UserAssertion instance.
So, my question is: given the condition that I am using Ws-Federation authentication protocol for authenticating an end user, in the backend how can I pass the user assertion information (it is in SAML2 format) to AAD for requesting a JWT for a backend api resource?
AAD provides "canned" claims. There are no claims rules to add other attributes to the token.
Refer: Supported Token and Claim Types.
If you want other attributes, you need to use the Graph API.