We have a validate jwt policy in APIM to validate jwt token. we are generating token from our function app using the azure.identity library. till now we were using system assigned identity for generating the token using the below method.
var tokenCredential = new DefaultAzureCredential();
var accessToken = await tokenCredential.GetTokenAsync(
new TokenRequestContext(scopes: new string[] { "https://management.azure.com" + "/.default" }) { });
token is generated successfully. and we are able to successfully validate the token in the policy. below is the APIM xml policy.
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Invalid or Expired token" require-expiration-time="true" require-signed-tokens="true">
<openid-config url="https://login.microsoftonline.com/tenantid/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>https://management.azure.com</audience>
</audiences>
<issuers>
<issuer>https://sts.windows.net/tenantid/</issuer>
</issuers>
<required-claims>
<claim name="oid" match="any">
<value>objectid of the managed identity/system assigned</value>
</claim>
</required-claims>
</validate-jwt>
now we have assigned the user managed identity and assigned the identity to the function app with the below code I am able to generate the token but in APIM it was throwing weird error.
var azureServiceTokenProvider = new ManagedIdentityCredential(clientId: "client-id-of-managed-identity-id");
accessToken = await azureServiceTokenProvider.GetTokenAsync(new Azure.Core.TokenRequestContext(new[] { "https://management.azure.com" + "/.default" }));
APIM is telling
"JWT Validation Failed: IDX10501: Signature validation failed. Unable to match key: \nkid: ''.\nExceptions caught:\n ''.
any idea on how to overcome this ?
I have updated my code to use DefaultAzureCredential instead of ManagedIdentityCredential and it started working.
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{ ManagedIdentityClientId = "managed_identity_clinet_id" });
AccessToken accessToken = await credential.GetTokenAsync(
new TokenRequestContext(scopes: new string[] { "https://management.azure.com" + "/.default" }) { });
I passed the client ID of the managed Identity and able to proceed with the generated token.
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
I have two clients for my SignalR app: Javascript client for web browsers and Android client (Gurgen/SignalR-.net-core-android-client) that use JWT Bearer Authentication.
I added this attribute to my SignalR Hub:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme + "," + CookieAuthenticationDefaults.AuthenticationScheme)]
public class MyHub : Hub
And my Startup.cs file:
public void ConfigureServices(IServiceCollection services)
{
//…
services.AddAuthentication()
.AddCookie(cfg => cfg.SlidingExpiration = true)
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
};
});
services.AddSignalR();
//…
Android client login successfully with header authentication bearer token. But web client comes failed connecting to the hub (401 Unauthorized).
When I remove [Authorize] attribute the javascript client works!
What is default Javascript SignalR Client AuthenticationScheme? Or what is the issue I made?
I use dotnet core 2.1, Microsoft.AspNetCore.SignalR and my IDE is Visual Studio for Mac.
This is from SignalR docs:
In standard web APIs, bearer tokens are sent in an HTTP header. However, SignalR is unable to set these headers in browsers when using some transports. When using WebSockets and Server-Sent Events, the token is transmitted as a query string parameter.
services.AddAuthentication(options =>
{
// ...
})
.AddJwtBearer(options =>
{
// ...
// We have to hook the OnMessageReceived event in order to
// allow the JWT authentication handler to read the access
// token from the query string when a WebSocket or
// Server-Sent Events request comes in.
// Sending the access token in the query string is required due to
// a limitation in Browser APIs. We restrict it to only calls to the
// SignalR hub in this code.
// See https://learn.microsoft.com/aspnet/core/signalr/security#access-token-logging
// for more information about security considerations when using
// the query string to transmit the access token.
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
I have an Asp.Net Core 2.0 WebApi which is authenticating against AAD:
services.AddAuthentication(options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; })
.AddJwtBearer(options =>
{
options.Authority = "https://login.microsoftonline.com/TENANT.onmicrosoft.com";
options.Audience = "CLIENT_ID";
});
My SPA app gets the token from AAD and sent it as bearer header. All works fine.
I have create a Job in Azure Scheduler and setup Active Directory OAuth:
After running a job I get this error: Bearer error="invalid_token", error_description="The audience is invalid".
When I set options.Audience in AddJwtBearer(...) to https://management.core.windows.net/ the Job works but not the SPA.
I guess, I need to set Audience to an array ['CLIENT_ID', "https://management.core.windows.net/"] but the options.Audience is type of string. If I don't set Audience at all, both Spa and Job does not work (401 unauthenticated). Setting Audience to CLIENT_ID,https://management.core.windows.net/ does not work either.
Is there a way how to enable multiple audiences in AddJwtBearer?
I think I ran into the same problem as you. To make it work I moved audience from options and into the TokenValidationParameters, which accepts multiple entries. Check the code below:
.AddJwtBearer(options =>
{
options.Authority = "https://login.windows.net/trades.no";
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidAudiences = new List<string>
{
"AUDIENCE1",
"AUDIENCE2"
}
};
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" }
}
};
}