The audience is invalid error - jwt

I have 3 projects 1- Javascript SPA 2- Web API Project, 3- IdentityServer with EF Core
I started debugging API and Identity Server and successfully get the jwt token but, when I try to get value from API method which has Authorize Attribute I get an error:
WWW-Authenticate →Bearer error="invalid_token", error_description="The audience is invalid"
I could not found any property about audience in auth options. This is my configuration in API project
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
ApiSecret="secret",
Authority = "http://localhost:5000",
ApiName="fso.Api",
RequireHttpsMetadata = false,
});
And my Config.cs file in Identity
public class Config
{
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource()
{
Name = "fso.Api",
DisplayName = "feasion API",
Scopes =
{
new Scope("api1"),
new Scope(StandardScopes.OfflineAccess)
},
UserClaims =
{
JwtClaimTypes.Subject,
JwtClaimTypes.EmailVerified,
JwtClaimTypes.Email,
JwtClaimTypes.Name,
JwtClaimTypes.FamilyName,
JwtClaimTypes.PhoneNumber,
JwtClaimTypes.PhoneNumberVerified,
JwtClaimTypes.PreferredUserName,
JwtClaimTypes.Profile,
JwtClaimTypes.Picture,
JwtClaimTypes.Locale,
JwtClaimTypes.IdentityProvider,
JwtClaimTypes.BirthDate,
JwtClaimTypes.AuthenticationTime
}
}
};
}
public static List<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Email(),
new IdentityResources.Profile(),
};
}
// client want to access resources (aka scopes)
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "fso.api",
AllowOfflineAccess=true,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AllowedScopes =
{
StandardScopes.OfflineAccess,
"api1"
}
}
};
}
}

See here for what this claim is about:
The aud (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the aud claim when this claim is present, then the JWT MUST be rejected....
So your API's name must exist in the aud claim for the JWT to be valid when it is validated by the middleware in your API. You can use jwt.io to look at your token by the way, that can be useful to help make sense of it.
In order to have IdentityServer to add your API's name to the aud claim your client code (which is attempting to get a resource from the API and therefore needs an access token) should request a scope from your API. For example like this (from an MVC client):
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
Authority = Configuration["IdpAuthorityAddress"],
ClientId = "my_web_ui_id",
Scope = { "api1" },
//other properties removed...
});

To avoid the error, audience should be consistently added in 4 places
In My (e.g. MVC) client as custom Scope.
In API application as ApiName
In IdentityServer Clients configuration as AllowedScope
In API Resources configuration as ApiResource
See details ( previously available in IdentityServer4 wiki):
When configuring a new API connection in identityServer4, you can get an error:
WWW-Authenticate: Bearer error="invalid_token",
error_description="The audience is invalid"
To avoid the error, Audience should be consistently added in 4 places
In My (e.g. MVC) client as custom Scope :
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
Authority = Configuration["IdpAuthorityAddress"],
ClientId = "my_web_ui_id",
Scope = { "openid", "profile", "offline_access", "MyApi" },
//other properties removed for brevity...
});
In API application as ApiName
//Microsoft.AspNetCore.Builder.IdentityServerAuthenticationOptions
var identityServerAuthenticationOptions = new IdentityServerAuthenticationOptions()
{
Authority = Configuration["Authentication:IdentityServer:Authority"],
RequireHttpsMetadata = false,
EnableCaching = false,
ApiName = "MyApi",
ApiSecret = "MyApiSecret"
};
In IdentityServer \IdentityServerHost\Configuration\Clients.cs
(or corresponding Clients entry in the database)
var client = new Client
{
ClientId = clientId,
//other properties removed for brevity...
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
//IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.OfflineAccess, "MyApi",
},
};
In IdentityServer \IdentityServerHost\Configuration\Resources.cs (or corresponding ApiResource entry in the database) as apiResource.Scopes
var apiResource = new ApiResource
{
Name = "MyApi",
ApiSecrets =
{
new Secret("MyApiSecret".Sha256())
},
UserClaims =
{
JwtClaimTypes.Name,
JwtClaimTypes.Profile,
},
};

In your app configuration file in AD configuration section add "Audience" line:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "<-- Enter the Client Id -->",
"Audience": "<-- Enter the Client Id -->",
"TenantId": "<-- Enter the tenantId here -->"
}
In my case "ClientId" & "Audience" was the same.
P.S.: And if after that you'll see
IDW10201: Neither scope or roles claim was found in the bearer token
Add another line to AD configuration:
"AllowWebApiToBeAuthorizedByACL": true
More here

In IdentityServer had to add claim "aud" to the jwt Token. In Order to do that under .AddJwtBearer("Bearer", options => options.Audience="invoice" and set ApiResource
Reference Link https://identityserver4.readthedocs.io/en/latest/topics/resources.html#refresources
public static readonly IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("invoice", "Invoice API")
{
Scopes = { "invoice.read", "invoice.pay", "manage" }
}
};
}

Related

protect asp.net web api 2 project with identity server 4

I have an asp.net web api 2 project with .Net framework 4.8 and a centralized Identity Server 4 project. I want to validate jwt/access token generated from IS4 in my web api 2 project. I can understand its a duplicate question but somehow I am unable to find any suitable help and I am not sure what's missing. I have used IdentityServer3.AccessTokenValidation for token validation in web api project.
Startup.cs
using IdentityServer3.AccessTokenValidation;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(WebApplicationApiNew.Startup))]
namespace WebApplicationApiNew
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44373",
RequiredScopes = new[] { "api1" },
});
}
}
}
Calling this API with a valid JWT bearer token still gives 401:
[Authorize]
[HttpPost]
public String GetName1()
{
if (User.Identity.IsAuthenticated)
{
var identity = User.Identity as ClaimsIdentity;
if (identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
}
return "Valid";
}
else
{
return "Invalid";
}
}
Error details:
2021-07-24 20:41:25.4133|DEBUG|Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationMiddleware|Authentication failed
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException: IDX10214: Audience validation failed. Audiences: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. Did not match: validationParameters.ValidAudience: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]' or validationParameters.ValidAudiences: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
at Microsoft.IdentityModel.Tokens.Validators.ValidateAudience(IEnumerable`1 audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateAudience(IEnumerable`1 audiences, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at Microsoft.Owin.Security.Jwt.JwtFormat.Unprotect(String protectedText)
at Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationHandler.<AuthenticateCoreAsync>d__3.MoveNext()
The error shows:
IDX10214: Audience validation failed
If your JWT token contains an "aud" claim, the authentication middleware
is attempting to validate the audience claim, but there is no audience specified in your options. Can you try one of the following:
Set an audience in the options:
Audience = "[your audience]"
Disable audience validation in the options:
TokenValidationParameters.ValidateAudience = false;
Refer to the identity server documentation for more details:
https://docs.identityserver.io/en/latest/topics/apis.html
I found solution to my problem. IS3 requires aud claim (with /resources in url) to be present in jwt/access token in order to validate the token. But on the other side IS4 has stopped emitting the aud claim by default. So we need to explicitly set IS4 server to emit aud claim to support legacy/old access token validation with IdentityServer3.AccessTokenValidation.
services.AddIdentityServer(options =>
{
...
options.EmitStaticAudienceClaim = true; // <- for older access token validation
})
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
...

JWT Token .NET Core Identity Server problem

I am trying to secure a .NET 5.0 Web API with OAuth Client Credentials flow.
My Client is requesting a token from the IdentityServer4 instance and supplying it to the API. The API is then returning a 401 error when I access and endpoint. I notice the following header:
WWW-Authenticate header contains Bearer error=\"invalid_token\", error_description=\"The audience 'empty' is invalid\"
Which suggests my JWT does not contain the audience paramater.
My JWT request code looks like the following:
var tokenResponseType = await serverClient.RequestClientCredentialsTokenAsync(new
ClientCredentialsTokenRequest
{
Address = discoveryDocument.TokenEndpoint,
ClientId = "client_id",
ClientSecret = "client_secret",
Scope = "ApiOne",
});
The code to validate the Token is here:
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", config =>
{
config.Authority = "https://localhost:44335/";
config.Audience = "ApiOne";
config.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateActor = true,
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true
};
config.RequireHttpsMetadata = false;
});
I believe the JWT token should contain the audience parameter. When I request the JWT I can't find a way to set the audience parameter.
I've used jwt.io to debug my JWT token and this confirms the audience value is not set. I expected setting the Scope on the request would do this.
What is lacking is the ApiScope and ApiResource configuration in IdentityServer.
First you need an ApiScope defined, like:
new ApiScope(name: "ApiOneScope",
displayName:"You can manage the ApiOne system.",
userClaims: new List<string>{ });
The ApiScope is a scope that the client can request access to.
then you need a ApiResource defined like:
new ApiResource()
{
Name = "ApiOne",
DisplayName = "Orders API Service",
Scopes = new List<string> { "ApiOneScope" },
};
The ApiResource is the actual Api, that end up in the audience claim when the clients requests the scope named ApiOneScope.
To complement this answer, I write a blog post that goes into more detail about this topic:
IdentityServer – IdentityResource vs. ApiResource vs. ApiScope

SAML2 with Owin - unable to authenticate

I have sample WebForms application with Owin. Tried to do SAML2 authentication with Azure AD IdP. It works fine, user is registered in application and authenticated.
Now I need to use other IdP. So I changed my application and nothing.
Saml response contains success, so IdP authenticated me. But calling Context.GetOwinContext().Authentication.GetExternalLoginInfo() returns null.
I found some posts about "external cookie", but I don't think this is my problem, because Azure sample works fine. Switching to other IdP failed.
Only difference seems to be in SAML Xml format.
Azure returns as
<samlp:Response Destination="https://localhost:44390/Saml2/Acs"
ID="_5eaccd77-fa78-4f59-86d9-67049ef074ce" InResponseTo="id73419322f1cc440184f456548cee7d09"
IssueInstant="2018-12-21T15:00:58.248Z" Version="2.0"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
</samlp:Response>
but other IdP returns as
<saml2p:Response Destination="https://localhost:44390/Saml2/Acs"
ID="_9547020d571863ef02c1f6d3dc8d94d7" InResponseTo="id46574a117a254f06a272ec02769b1a3c"
IssueInstant="2018-12-21T14:31:54.505Z" Version="2.0"
xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
</saml2p:Response>
But namespaces should be ok.
So it must be something in SAML response? How can I find problem?
private static Saml2AuthenticationOptions CreateSaml2Options()
{
var spOptions = CreateSpOptions();
var saml2Options = new Saml2AuthenticationOptions(false)
{
SPOptions = spOptions
};
var idp = new IdentityProvider(new EntityId("XXX"), spOptions)
{
AllowUnsolicitedAuthnResponse = true,
Binding = Saml2BindingType.HttpPost,
SingleSignOnServiceUrl = new Uri("XXX")
};
saml2Options.IdentityProviders.Add(idp);
return saml2Options;
}
private static SPOptions CreateSpOptions()
{
const string language = "cs-cz";
var spOptions = new SPOptions
{
EntityId = new EntityId("app:vwg.skoda.nia"),
AuthenticateRequestSigningBehavior = SigningBehavior.Always,
ReturnUrl = new Uri("https://localhost:44390/Saml2/Acs")
};
var attributeConsumingService = new AttributeConsumingService
{
IsDefault = true,
ServiceNames = { new LocalizedName("Saml 2 Authentication", "en") }
};
attributeConsumingService.RequestedAttributes.Add(new RequestedAttribute("Minimal"));
spOptions.AttributeConsumingServices.Add(attributeConsumingService);
var certPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "/App_Data/XXX.pfx";
var cert = new X509Certificate2(certPath, "XXX");
spOptions.ServiceCertificates.Add(cert);
return spOptions;
}
Sustainsys.Saml2.Exceptions.InvalidSignatureException: The signature verified correctly with the key contained in the signature, but that key is not trusted.
This means that the signature is correct, but that you have not configured the signing key as trusted.
You need to add a certificate with the idp's public key to the IdentityProvider.SigningKeys collection.

Why is IdentityServer redirecting to http rather than https?

I have a very simple MVC5 website that I'm trying to secure with IdentityServer3.
Both my website and my IdentityServer instance are hosted as separate sites in AppHarbor. Both are behind https.
When I hit a resource in my website that is protected by an [Authorize] attribute (e.g., /Home/About), I am successfully redirected to IdentityServer, and I can successfully authenticate.
When IdentityServer POSTs its response back to the website (via app.FormPostResponse.js), the website responds with a 302 redirect to the requested resource - as expected. However, this redirect is to http, not https (see the network trace below).
I'm sure this is just something wrong with my IdentityServer config, but I'd appreciate any pointers as to what I've got wrong.
(AppHarbor uses a reverse proxy (nginx I believe) in front of IIS, where SSL terminates - so I have RequireSsl = false for this scenario, as per the IdentityServer documentation.)
Here is my website's Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://<my-idsrv3>.apphb.com/identity",
ClientId = "<my-client-id>",
Scope = "openid profile roles email",
RedirectUri = "https://<my-website>.apphb.com",
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = false
});
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
}
}
Here is Startup.cs from my IdentityServer3 instance:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "My Identity Server",
SigningCertificate = Certificates.LoadSigningCertificate(),
RequireSsl = false,
PublicOrigin = "https://<my-idsrv3>.apphb.com",
Factory = new IdentityServerServiceFactory()
.UseInMemoryUsers(Users.Get())
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get())
});
});
}
}
Here is the definition of my website Client:
new Client
{
Enabled = true,
ClientName = "My Website Client",
ClientId = "<my-client-id>",
Flow = Flows.Implicit,
RedirectUris = new List<string>
{
"https://<my-website>.apphb.com"
},
AllowAccessToAllScopes = true
}
Here is the trace from Chrome, after clicking 'Yes, Allow' on the IdentityServer consent screen:
So it looks like this issue was caused by my client website being behind an SSL-terminating nginx front-end.
With reference to this GitHub issue, I added the following to the start of my website's app configuration:
app.Use(async (ctx, next) =>
{
string proto = ctx.Request.Headers.Get("X-Forwarded-Proto");
if (!string.IsNullOrEmpty(proto))
{
ctx.Request.Scheme = proto;
}
await next();
});
This makes the website aware that incoming requests were over https; this in turn appears to ensure that the IdentityServer3 middleware generates https uri's.
Had the same issue running identityserver4 in an Azure App Service. Even with forced https, the generated urls in .well-known/openid-configuration were still http://.
Fixed using the same solution as the other answer, but using AspNetCore ForwardedHeadersExtensions:
var forwardOptions = new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
// Needed because of mixing http and https.
RequireHeaderSymmetry = false,
};
// Accept X-Forwarded-* headers from all sources.
forwardOptions.KnownNetworks.Clear();
forwardOptions.KnownProxies.Clear();
app.UseForwardedHeaders(forwardOptions);
See also https://github.com/IdentityServer/IdentityServer4/issues/1331 for more discussion on this subject.
Add forwarded headers in your startup
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
});
and
app.UseForwardedHeaders(new ForwardedHeadersOptions()
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
Finally tell the config it has to replace the http to https in the redirect url. I'm still looking for a better way to implement this.
in your .addopenidconnect() add:
Func<RedirectContext, Task> redirectToIdentityProvider = (ctx) =>
{
if (!ctx.ProtocolMessage.RedirectUri.StartsWith("https") && !ctx.ProtocolMessage.RedirectUri.Contains("localhost"))
ctx.ProtocolMessage.RedirectUri = ctx.ProtocolMessage.RedirectUri.Replace("http", "https");
return Task.FromResult(0);
};
opt.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = redirectToIdentityProvider
};

Issues with GetProfileDataAsync after upgrade to IdentityServer3 v2.5 from v1.6.3

We've been running IdentityServer3 v1.x successfully over the past year, but have now upgraded to v2.5 from v1.6.3.
We have a custom UserService that implements the IUserService, so this was modified for the new context parameters and we are able to login, but are having issues with the GetProfileDataAsync
The UserService that was built for v1.6.3 works fine and we can see 12 requested claim types in requestedClaimTypes
public Task<IEnumerable<Claim>> GetProfileDataAsync(ClaimsPrincipal subject,
IEnumerable<string> requestedClaimTypes = null)
{
var userClaims = claimsService.GetByUserIdAsync(int.Parse(subject.GetSubjectId()));
var claims =
userClaims.Where(x => requestedClaimTypes != null && requestedClaimTypes.Contains(x.Type));
return Task.FromResult(claims);
}
But since upgrading to v2.5, the only requested claim type is sub in context.RequestedClaimTypes, rather than 12 we used to get. The only way to get all 12 in is to change the AlwaysIncludeInIdToken to true
Our updated UserService for v2.5 is
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
if (context == null) throw new ArgumentNullException("context");
var subject = context.Subject;
var requestedClaimTypes = context.RequestedClaimTypes;
var userClaims = await _claimsService.GetByUserIdAsync(int.Parse(subject.GetSubjectId()));
if (userClaims != null)
{
var claims = userClaims.Where(x => requestedClaimTypes != null && requestedClaimTypes.Contains(x.Type));
context.IssuedClaims = claims;
}
}
We use SQL to store our Clients and Scopes but we've not changed any data, other than to use the IdentityServer3.EntityFramework provider
Our logging shows that the 4 scopes are being requested which have their associated scope claims as before
Info: Authorize request validation success {
"ClientId": "MyApp",
"ClientName": "MyApp",
"RedirectUri": "https://xxx:44300/",
"AllowedRedirectUris": [
"https://xxx:44300/"
],
"SubjectId": "9",
"ResponseType": "code id_token",
"ResponseMode": "form_post",
"Flow": "Hybrid",
"RequestedScopes": "openid profile roles user",
"State": "OpenIdConnect.AuthenticationProperties=xxxx",
"Nonce": "xxx",
"SessionId": "xxx",
"Raw": {
"client_id": "MyApp",
"redirect_uri": "https://xxx:44300/",
"response_mode": "form_post",
"response_type": "code id_token",
"scope": "openid profile roles user",
"state": "OpenIdConnect.AuthenticationProperties=xxx",
"nonce": "xxx"
}
}
What do we need to do to get it to request all the claim types as before??
The spec says that if an access token is requested, the id_token should only contain the minimal user-related claims (aka sub). The access token can then be used to retrieve the other claims from the userinfo endpoint.
This is an optimization mechanism to keep the id_token as small as possible.
We had a bug where this was done for id_token token but not for code id_token (which is what you are using). This bug was fixed at some point along the way. I guess that is the behavioural change you are seeing.
Either set the AlwaysIncludeInIdToken property on the scope claims you want to be included - or use the userinfo endpoint to retrieve the claims.