I have been trying to figure this out all weekend, but can't seem to get it to work. Please help.
I have setup IdentityServer 4, an MVC application and an API. I get an access token after login, but when I try to access the API it throws 500 internal server error (if I have an Authorize attribute)
this is my identityserver config file:
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("api1", "my API")
};
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "mvc",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = { "https://localhost:5002/signin-oidc" },
//FrontChannelLogoutUri = "https://localhost:5002/signout-oidc",
PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },
AllowOfflineAccess = true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
};
This is the identityServer startup class:
public IWebHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
Environment = environment;
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
if (System.Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Production")
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDbConnection")));
else
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddAspNetIdentity<ApplicationUser>();
// not recommended for production - you need to store your key material somewhere secure
builder.AddDeveloperSigningCredential();
services.AddAuthentication()
.AddGoogle(options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "xxx.apps.googleusercontent.com";
options.ClientSecret = "xxx";
options.ReturnUrlParameter = "https://xxx.azurewebsites.net/signin-google";
}).AddFacebook(facebookOptions =>
{
facebookOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
facebookOptions.ClientId = "xxx";
facebookOptions.ClientSecret = "xxx";
facebookOptions.ReturnUrlParameter = "https://xxx.azurewebsites.net/signin-facebook";
});
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
This is my API startup class:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System.Configuration;
namespace SF.API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration
{
get;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// accepts any access token issued by identity server
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = ConfigurationManager.AppSettings["IdentityServerAddress"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
// adds an authorization policy to make sure the token is for scope 'api1'
//services.AddAuthorization(options =>
//{
// options.AddPolicy("ApiScope", policy =>
// {
// policy.RequireAuthenticatedUser();
// policy.RequireClaim("scope", "api1");
// });
//});
}
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
app.UseStaticFiles();
}
}
}
this is the Controller class:
[Route("identity")]
[Authorize]
public class IdentityController : ControllerBase
{
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
[Route("/Test")]
public IActionResult Test()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
This is the MVC application startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://xxx.azurewebsites.net";
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.SaveTokens = true;
options.Scope.Add("api1");
options.Scope.Add("offline_access");
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute()
.RequireAuthorization();
});
}
This is how I call the API:
public async Task<IActionResult> CallApi()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var content = await client.GetStringAsync("https://localhost:6001/Test");
ViewBag.Json = JArray.Parse(content).ToString();
return View("json");
}
My access token looks like this:
"eyJhbGciOiJSUzI1NiIsImtpZCI6IkExNDYxOUUzOTAwNjM5ODA2NUU4RkUwQjJFMkU1RThFIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2NDg5ODEyMjMsImV4cCI6MTY0ODk4NDgyMywiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eXNlcnZlcmlkZW50aXR5cHJvdmlkZXJzZi5henVyZXdlYnNpdGVzLm5ldCIsImF1ZCI6Imh0dHBzOi8vaWRlbnRpdHlzZXJ2ZXJpZGVudGl0eXByb3ZpZGVyc2YuYXp1cmV3ZWJzaXRlcy5uZXQvcmVzb3VyY2VzIiwiY2xpZW50X2lkIjoibXZjIiwic3ViIjoiZDc5NGQxOGUtNGYyOC00MmE3LWFkMTQtZDdiMWMxMDcwOTE5IiwiYXV0aF90aW1lIjoxNjQ4OTgxMjIzLCJpZHAiOiJsb2NhbCIsImp0aSI6IjZBRTE3Q0RBMjBGQkNGNDExQzc3QUIyQkNBNTE3M0YzIiwic2lkIjoiRkMxREY5QUZCODQyRkZEN0JGRjk5MTY0RTYyN0M2ODYiLCJpYXQiOjE2NDg5ODEyMjMsInNjb3BlIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJhcGkxIiwib2ZmbGluZV9hY2Nlc3MiXSwiYW1yIjpbInB3ZCJdfQ.Grcu-dbrLy6LHHxsW2FJsSIhmwQBEl1jQ2LRvJhBFzZ8j0HAqk129Q8JncJFSFBjQkEls8xBFN-OxyvhJ5o7dmgpkgYENbfjl7jC04yhvSh_MzLqG2h_mme1mwsC3xzuKbQR1yczei-j92WUMeP-CvzUtr2vbJd2lJv0YvpJvykGF4BbKrQMPLPZnlLFRkPm5LcdFfUsrHrCz3R0JZ7tpVSwGMjGMlDHlAMAR04Fzf6YQhbKUEydNdTIWFP2akyBoWuRwAvTXbOA8vm9GZpeTbo8S4At5X7RhOR_J-zIjk1QWKhqN9kVMnMLXpO_NmZ6iQ66pcnT0G75rtFEfFtISQ"
Is there anything wrong with how I access the token? I can't see the scopes for example?
What else could be wrong?
**Update: I added app.UseAuthorization() in the API.Startup class. Then I got 401 Unauthroized instead
CodePudding user response:
One problem with the API is that it lacks
app.UseAuthentication(); app.UseAuthorization();
In the API startup class.