I've successfully created a new Application Group with a Server Application as well as a Web API and the OpenID Connect protocol is working w/out any issues until I try and make a call to UserInfo. The Relying Party identifier is the same GUID as Client ID of the Server Application (per the examples I have read online). I get the error below when trying to call UserInfo:
WWW-Authenticate: Bearer error="invalid_token", error_description="MSIS9921: Received invalid UserInfo request. Audience 'microsoft:identityserver:21660d0d-93e8-45db-b770-45db974d432d' in the access token is not same as the identifier of the UserInfo relying party trust 'urn:microsoft:userinfo'."
Any help would be greatly appreciated.
I also recently got this error using ADFS with the ASP.NET Core OpenIDConnect providers. In my case, disabling the UserInfo request altogether resolved the issue:
var openIdOptions = new OpenIdConnectOptions
{
...
GetClaimsFromUserInfoEndpoint = false
};
After doing this, I still had the claims that I needed for my app - email, SID, name, etc. I'm sure there are scenarios where this would not work, but it's good to know you might not need /userinfo at all. I would still be interested in knowing why the token returned from ADFS can't be used to call /userinfo, and how to fix it in ASP.NET OpenIDConnect providers.
Just set the resource accordingly:
options.Resource = "urn:microsoft:userinfo";
Related
I have configured IdentityServer 3 to use external IdentityProvider which is pointing to AAD.
As of now, when I send a request to IdentityServer, I am properly redirected to the AAD for login, however, the 'state' parameter that I am sending to IdentityServer is overridden, and the value of OpenIdConnect.AuthenticationProperties is encrypted and sent to the AAD as the state in the query string.
For eg:
https://localhost:44333/idpaad/connect/authorize?client_id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&redirect_uri=https://localhost:44394/&response_mode=query&response_type=code&scope=openid%20email&state=9b0e82c3-e623-42f1-bede-493243c103e7
Here,
https://localhost:44333/idpaad/connect/authorize -> IdentityServer endpoint
state=9b0e82c3-e623-42f1-bede-493243c103e7 -> client generated GUID sent as querystring.
when I see in the "RedirectToIdentityProvider" middleware in the StartUp.cs of IdentityServer OpenIdConnectAuthenticationNotifications, the value of state is updated to
OpenIdConnect.AuthenticationProperties=(protected values) instead of the GUID and the same is also returned as a query string back to the Redirect URI.
enter image description here
Is there a way to send the original state and not override it by IdentityServer3?
While using wsFederation, I am not getting this issue and the same is forwarded directly to the IdP.
Any help is deeply appreciated.
Most of the time it's advisable for an Azure Active Directory integrated application to maintain an application state when sending request to Azure AD for login. And the recommended way to achieve this is to use the ‘state’ parameter as defined in the OpenID Connect standards.
If you check this document form OpenID, you will find that primary reason for using the state parameter is to mitigate CSRF attacks.
RECOMMENDED. Opaque value used to maintain state between the request and the callback. Typically, Cross-Site Request Forgery (CSRF, XSRF) mitigation is done by cryptographically binding the value of this parameter with a browser cookie.
The ‘state’ parameter is used for both preventing cross-site request forgery attacks and to maintain user’s state before authentication request occurs.
In an ASP.NET or ASP.NET CORE web application using OpenID Connect OWIN middleware, the ‘state’ parameter is maintained automatically by the middleware when sending out an authentication request, this is the only reason you are seeing the state parameter getting overridden in your case.
But if you want you can add custom data in your state parameter. Use the following code in OpenIdConnectNotifications’s RedirectToIdentityProvider event to inject custom data into the ‘state’ parameter.
var stateQueryString = notification.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = notification.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.Add("MyData","123");
notification.ProtocolMessage.State = stateQueryString[0] + "=" + notification.Options.StateDataFormat.Protect(state);
Check this document and Microsoft identity platform and OpenID Connect protocol for detailed information.
I am just finished trying to implement this: https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2/tree/master/1.%20Desktop%20app%20calls%20Web%20API
Instead of using a WPF application I am using a Blazor WASM client.
I added the token to the outgoing requests to my .net core API but I always get a 401.
Blazor authenticates well and gets the token back, seems to be working fine but:
the audience has the wrong GUID
"scp" (scope) is missing, hence the token being invalid for usage
If I run the sample from the link mentioned above and decode the token I can see a correct AUD & SCP in the token. So it's probably something with my configuration in Blazor?
Config in Blazor
// AD authentication
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://xxx-xxxx-xxxx-xxxx/access_as_user");
});
builder.Services
.AddHttpClient<IApiClient, ApiClient>(client => client.BaseAddress = _baseUri)
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
Response
Bearer error="invalid_token", error_description="The audience '63ee4227-xxxx-xxxx-xxxx' is invalid"
The audience GUID is the clientID of my Blazor app registration
Code in Startup.cs
...
services.AddProtectedWebApi(Configuration)
.AddInMemoryTokenCaches();
...
...
app.UseAuthentication();
app.UseAuthorization();
...
Any idea what could have been wrong?
When I was learning to use Blazor WebAssembly I followed this tutorial which does something very similar and it may be easier for you to implement as you don't have to try and convert any codefrom WPF.
However, one thing to try is to change this:
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://xxx-xxxx-xxxx-xxxx/access_as_user");
To this:
options.ProviderOptions.DefaultAccessTokenScopes.Add("xxx-xxxx-xxxx-xxxx/access_as_user");
In my application I am calling two API endpoints, one is hosted within the same domain as the Blazor application and the other is on a separate domain. When requesting a token for the external domain the api:// prefix must be used. However, for the same domain it must be excluded.
I have a test console app which I'm pointing at a local instance of Identity Server 3 to request an access token. The following code does this and returns my token fine (passing a single scope "scope.test.client").
static TokenResponse GetClientToken(string clientId, string clientSecret, string[] scopes)
{
var uri = new Uri(string.Concat(ID_BASE_URI, ID_URL_TOKEN));
var client = new TokenClient(
uri.AbsoluteUri,
clientId,
clientSecret);
return client.RequestClientCredentialsAsync(string.Join(" ", scopes)).Result;
I then use this token to call an API also running locally. This takes the TokenResponse obtained above and passed it to this method:
static void CallApi(string url, TokenResponse response)
{
try
{
using (var client = new HttpClient())
{
client.SetBearerToken(response.AccessToken);
Console.WriteLine(client.GetStringAsync(url).Result);
}
}
catch (Exception x)
{
Console.WriteLine(string.Format("Exception: {0}", x.Message));
}
}
The API (an ASP.NET WebApi project) uses an Owin Startup class to enforce bearer token authentication for all requests:
appBuilder.Map(baseApiUrl, inner =>
{
inner.UseWebApi(GlobalConfiguration.Configuration);
// Enforce bearer token authentication for all API requests
inner.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://identityserver/core",
ValidationMode = ValidationMode.ValidationEndpoint,
RequiredScopes = new[] { "scope.test.client" }
});
});
It also ensures all API requests are handled by a custom authorize attribute:
GlobalConfiguration.Configuration.Filters.Add(new DefaultApiAuthorizeAttribute());
Debugging this API, the first line in my overridden OnAuthorize method (in DefaultApiAuthorizeAttribute) is this:
var caller = actionContext.RequestContext.Principal as System.Security.Claims.ClaimsPrincipal;
If I break on this line I can see that actionContext.RequestContext.Principal is always null. However, I can see that ((System.Web.Http.Owin.OwinHttpRequestContext)actionContext.RequestContext).Request.Headers contains an Authorization header with the bearer token passed from my console app.
So it would seem that the API project is not authenticating the bearer token. Certainly the Identity Server logs suggest it isn't being hit at all after issuing the initial access token. So I'd appreciate your expert advice about why this might not be happening, or at least some pointers about where to look.
I suspect it might have something to do with SSL. Both sites are hosted locally under self-signed SSL certs, although Identity Server is configured to not require SSL and uses the idsrv3test.pfx development certificate for signing. I do have another test MVC web app which delegates authentication to the same IS3 instance which works fine locally, so I believe my IS3 instance is configured correctly.
You need to call UseIdentityServerBearerTokenAuthentication before you call UseWebApi. When you set up an OWIN Middleware Pipeline, the order is important.
In your case, Web API will be handling your requests before they get sent onto Identity Server (if they get sent on at all).
I imagine a range of possible issues could have the impact I described, but in my case I was able to find the cause by adding a diagnostics log to my consuming API. This led me to discover that the problem was an assembly conflict. The Owin middleware was looking for a Newtonsoft.JSON assembly with version 8.0.0.0 but my consuming API (actually running on top of a CMS intance) was using 7.0.0.0.
For anyone else who wants to find the answer fast, rather than spend hours tweaking configurations, here's the documentation that describes how to add this logging: https://identityserver.github.io/Documentation/docsv2/consuming/diagnostics.html
I have a c# console application that references the ADAL.net library (Microsoft.IdentityModel.Clients.ActiveDirectory version 2.19.208020213)
The purpose of the console app is to consume a HTTP endpoint which is protected with ADFS.
The implementation of the ADFS auth is as follows....
var uc = new UserCredential("user", "password");
var ctx = new AuthenticationContext("https://sts.example.com/adfs", false);
var token = ctx.AcquireToken(ClientResourceUri, ClientId, uc);
The call to AcquireToken throws an exception...
This method overload is not supported by
'https://sts.example.com/adfs/'
Calling AcquireToken without the UserCredential object, and instead providing a redirectUri works, but throws up a dialog prompting for username and password, which is unsuitable as the console app will be executed in a non user environment...
var redirect = new Uri("https://example.com/arbitaryRedirect");
var token = ctx.AcquireToken(ClientResourceUri, ClientId, redirect);
//dialog is shown
If i switch to the latest alpha release of the adal.net library (3.6.212041202-alpha)
the error is more revealing...
MSIS9611: The authorization server does not support the requested
'grant_type'. The authorization server only supports
'authorization_code' or 'refresh_token' as the grant type.
However, mining google yields very little.
Is it actually possible to authenticate silently against ADFS?
Would i be correct in assuming (based upon answers in other posts) that the correct approach is to use WsTrustChannelFactory instead?
If not, what is the best approach?
It is possible using ADAL 3.x and ADFS in Windows Server 2016, with pretty much the same code you posted. Combinations of older versions of either ADAL or ADFS won't work.
Alternatively, you can use WS-Trust - which is significantly harder to handle, but can get the job done.
i have some code that tries impersonate the callers windows security settings and then connect to another WCF service on a different machine
WindowsIdentity callerWindowsIdentity = ServiceSecurityContext.Current.WindowsIdentity;
using (callerWindowsIdentity.Impersonate())
{
NetTcpBinding binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
EndpointAddress endpoint = new EndpointAddress(new Uri("net.tcp://serverName:9990/TestService1"));
ChannelFactory<WCFTest.ConsoleHost.IService1> channel = new ChannelFactory<WCFTest.ConsoleHost.IService1>(binding, endpoint);
WCFTest.ConsoleHost.IService1 service = channel.CreateChannel();
return service.PrintMessage(msg);
}
But I get the error:
"the caller was not authenticated by the service"
System.ServiceModel .... The request for security token could not be satisfied because authentication failed ...
The credentials I am trying to impersonate are valide windows credential for the box the service is on.
Any ideas why?
In order to support your scenario, you need to have an understanding of how Protocol Transition and Constrained Delegation work. You will need to configure both Active Directory and your WCF service endpoint(s) to support this. Note the use of the Service Principal Name (SPN). Take a look at the following link and see if they help you. The article has a sample to demonstrate the complete end-to-end configuration required to make this work.
How To: Impersonate the Original Caller in WCF Calling from a Web Application
Agree with marc_s this is the double-hop problem.
You need to get the windows authentication all the way through, therefore:
The request must be made in the context of a windows users
IIS must be configured to use windows authentication
Web.config must be set up for windows authentication with impersonate = true
The user that your application pool is running as, must be allowed to impersonate a user. This is the usual place where the double-hop problem occurs.
There is a right called "Impersonate a client after authentication"
http://blogs.technet.com/askperf/archive/2007/10/16/wmi-troubleshooting-impersonation-rights.aspx
Impersonation from you service to the next is a tricky issue, known as "double-hop" issue.
I don't have a final answer for that (I typically avoid it by using an explicit service account for the service that needs to call another service).
BUT: you should definitely check out the WCF Security Guidance on CodePlex and search for "Impersonation" - there are quite a few articles there that explain all the ins and outs of impersonating an original caller and why it's tricky.
Marc
If you are sure you have the credentials right on both hops, the next thing that could be causing the issue is the lack of the EndpointDnsIdentity being set on the endpoint.
DnsEndpointIdentity identity = new DnsEndpointIdentity("localhost"); // localhost is default. Change if your service uses a different value in the service's config.
Uri uri = new Uri("net.tcp://serverName:9990/TestService1");
endpoint = new EndpointAddress(uri, identity, new AddressHeaderCollection());