42

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"
                }
            }
        };
    }
}
Okan Aslankan
  • 3,016
  • 2
  • 21
  • 26

5 Answers5

29

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...
});
Matt
  • 12,569
  • 4
  • 44
  • 42
  • 1
    **jwt.io** link helped to find out that - with `.net core 2.2` + `Microsoft.AspNetCore.Authentication.AzureAD.UI` the **clientId** value needs `api://` prefixed. With `.net core 3.1` + `microsoft.identity.web` it works without api://` prefix. – Avi Jul 11 '20 at 09:34
20

To avoid the error, audience should be consistently added in 4 places

  1. In My (e.g. MVC) client as custom Scope.
  2. In API application as ApiName
  3. In IdentityServer Clients configuration as AllowedScope
  4. 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

  1. 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...
});
  1. 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"
};
  1. 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",
  },
};
  1. 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,
  },
};
Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
  • 1
    Perhaps the link clarified everything, but since it seems dead... My understanding is that the name used in dbo.ApiResources should line up with the apiName in your API, while your client's named scope must align with (a) a scope listed in dbo.ApiScopes -- which itself must (via ApiResourceId) be child to your resource in dbo.ApiResources and (b) scope in dbo.ClientScopes. That said, the scope and resource names *need* *not* *be* *identical*. I've succeed (and gained sanity) with three values such as "Client" for client, "ApiResource" for the resource, and "ApiScope" for scope. – Wellspring Jul 05 '19 at 22:01
  • 1
    So many [wrong] options in IdentityServer4, so little time :) This step by step approach lead me to the solution I needed (matching the API name to the scope in the IS config). If only they did not call everything a scope and use them for many different uses! – iCollect.it Ltd Dec 22 '20 at 21:33
  • 1
    I'm scrutinizing your (greatly helpful) answer and as I'm analysing the implications of the contents for my specific case, I couldn't help myself taking the liberty to edit your contribution a bit (mostly highlighting and formatting). I hope you don't mind. (In fact, I hope that you regard it as an improvement and are delighted to see it.) – Konrad Viltersten Jul 26 '21 at 09:00
3

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

Yaroslav N.
  • 406
  • 1
  • 7
  • 10
  • 1
    Helped me a lot. Thanks! I had to authenticate a Teams app (when running outside of teams) and my audience was like `api:///` – szab.kel Nov 04 '21 at 08:30
0

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" }
    }        
};
   }
rajquest
  • 535
  • 1
  • 5
  • 10
0

In case the authority is an IdentityServer3 but you are using and IdentityServer4.AccessTokenValidation a line must be added to the configuration as shown below

enter image description here

Hope this help someone.