I'm trying to setup an Identity Server 4 solution with EF Identity DB. The issue I'm having maybe with info stored in the Identity DB. Not sure if I have the client, API, Provision, Scopes... setup correct. This is my first attempt working with EF Identity DB. I can post DB info if it helps resolve the issue.
I'm able to login and retrieve the Secure User Claim information,
The Identity Server (Request from API) Token request validation and Token validation are successful. Log info below:
Token request validation success
{
"ClientId": "mvc",
"ClientName": "mvc.client",
"GrantType": "authorization_code",
"AuthorizationCode": "416bf70a2fdfc9b507207e2d2ebaea04b1d26b0dbeab246c3a08c14d4eed0d88",
"Raw": {
"client_id": "mvc",
"client_secret": "***REDACTED***",
"code": "416bf70a2fdfc9b507207e2d2ebaea04b1d26b0dbeab246c3a08c14d4eed0d88",
"grant_type": "authorization_code",
"redirect_uri": "http://localhost:5002/signin-oidc"
}
}
Token validation success
{
"ValidateLifetime": true,
"AccessTokenType": "Jwt",
"ExpectedScope": "openid",
"Claims": {
"nbf": 1517178337,
"exp": 1517181937,
"iss": "http://localhost:5000",
"aud": "http://localhost:5000/resources",
"client_id": "mvc",
"sub": "8ae24a28-59f5-48a6-92c6-c6cac551341b",
"auth_time": 1517178333,
"idp": "local",
"scope": [
"openid",
"profile",
"api1"
],
"amr": "pwd"
}
}
but API Token authorization is failing. The API fails with error message:
2018-01-28 17:28:38.011 -05:00 [INF] Failed to validate the token eyJhbGciOiJSUzI1NiIsImtpZCI6IjY3NTYzMGI1NWQ3NWE3OTI4MmNmZWI2OGNiN2I2MDZmIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1MTcxNzgzMzcsImV4cCI6MTUxNzE4MTkzNywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwL3Jlc291cmNlcyIsImNsaWVudF9pZCI6Im12YyIsInN1YiI6IjhhZTI0YTI4LTU5ZjUtNDhhNi05MmM2LWM2Y2FjNTUxMzQxYiIsImF1dGhfdGltZSI6MTUxNzE3ODMzMywiaWRwIjoibG9jYWwiLCJzY29wZSI6WyJvcGVuaWQiLCJwcm9maWxlIiwiYXBpMSJdLCJhbXIiOlsicHdkIl19.meLK1puLfEapFz-sL5IC9NlByfecDDtasPa_Vzba62FZXRJZijlQHysVNRbDKBI5TMqJf3GEs30D4U4T_IoCPz75lkLqCC4XtKROpsIwktm6lpHs-2GEXAZRmhLYDvmlUqwBNmv94XeuiYX8JWm9M-EHIL96Pn4eSlo-ItStgpHCK4sGfSUlTK_CZAfjfgeNXrhR0Sh9ZlXhZyZ5ucyQf8goLSJFk8SkFz3gsNA_gn_L_Q8r7cQk_Sq7Dyqq2YAvTocnwgp8o3UXOEf7h_rGnslLJqLbsnj9Cz8dZPuhknFgP2XZQn1m7ctAuVkz4s4mSJ75g--wYPQ1mJZUHV3l2Q.
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException: IDX10214: Audience validation failed. Audiences: 'http://localhost:5000/resources'. Did not match: validationParameters.ValidAudience: 'api1' or validationParameters.ValidAudiences: 'null'.
at Microsoft.IdentityModel.Tokens.Validators.ValidateAudience(IEnumerable`1 audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
Here is the API startup class:
#region "ConfigureServices"
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
}
#endregion
#region "Configure"
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseAuthentication();
app.UseMvc();
}
#endregion
Here is the MVC Client Startup class:
#region "ConfigureServices"
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("api1");
options.Scope.Add("openid");
//options.Scope.Add("offline_access");
});
}
#endregion
#region "Configure"
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
#endregion
Here is the Identity Server Startup:
#region "ConfigureServices"
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
string connectionString = Configuration.GetConnectionString("DefaultConnection");
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddAspNetIdentity<ApplicationUser>()
// this adds the config data from DB (clients, resources)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
// this adds the operational data from DB (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
options.TokenCleanupInterval = 120; // interval in seconds. 15 seconds useful for debugging
});
services.AddAuthentication()
.AddGoogle("Google", options =>
{
options.ClientId = "434483408261-55tc8n0cs4ff1fe21ea8df2o443v2iuc.apps.googleusercontent.com";
options.ClientSecret = "3gcoTrEDPPJ0ukn_aYYT6PWo";
})
.AddOpenIdConnect("oidc", "OpenID Connect", options =>
{
//options.Authority = "https://demo.identityserver.io/";
//options.ClientId = "implicit";
//options.SaveTokens = true;
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.ClientId = "mvc";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
}
#endregion
#region "Configure"
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
// app.UseAuthentication(); // not needed, since UseIdentityServer adds the authentication middleware
app.UseIdentityServer();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
#endregion
MVC Client Controller:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;
using System.Net.Http;
using Newtonsoft.Json.Linq;
using IdentityModel.Client;
namespace MvcClient.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[Authorize]
public IActionResult Secure()
{
ViewData["Message"] = "Secure page.";
return View();
}
public async Task Logout()
{
await HttpContext.SignOutAsync("Cookies");
await HttpContext.SignOutAsync("oidc");
}
public IActionResult Error()
{
return View();
}
public async Task<IActionResult> CallApiUsingClientCredentials()
{
var tokenClient = new TokenClient("http://localhost:5000/connect/token", "mvc", "secret");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");
var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken);
var content = await client.GetStringAsync("http://localhost:5001/identity");
ViewBag.Json = JArray.Parse(content).ToString();
return View("json");
}
public async Task<IActionResult> CallApiUsingUserAccessToken()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var client = new HttpClient();
client.SetBearerToken(accessToken);
var content = await client.GetStringAsync("http://localhost:5001/identity");
ViewBag.Json = JArray.Parse(content).ToString();
return View("json");
}
}
}
API Controller:
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using IdentityServer4.AccessTokenValidation;
namespace Api.Controllers
{
[Route("[controller]")]
[Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme)]
public class IdentityController : ControllerBase
{
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
}
Appreciate your assistance resolving this issue and furthering my knowledge. Paul
The provided code doesn't add value. What I would like to see is how you configured api1. It seems that your configuration is missing something. The following tables should contain a record for api1: