I am currently implementing IdentityServer4 with my microservices to authenticate users. My client application is a MVC project. While I run the project client application return an error as following,
Sorry, there was an error : invalid_scope Invalid scope Request Id: 8000008d-0000-f700-b63f-84710c7967bb
When I removed apiscopes from config file application works fine but then I didn't get any user claims in my authorization handler.
Here's my code
Config.cs
public static class Config
{
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
public static IEnumerable<ApiResource> GetApis()
{
return new List<ApiResource>
{
new ApiResource("masterweb_api", "Master API")
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "mvc",
ClientSecrets =
{
new Secret("Secret".Sha256())
},
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RequireConsent = false,
RequirePkce = false,
// where to redirect to after login
RedirectUris = { "https://localhost:44367/signin-oidc" },
// where to redirect to after logout
FrontChannelLogoutUri = "https://localhost:44367/signout-oidc",
PostLogoutRedirectUris = { "https://localhost:44367/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"masterweb_api"
},
AllowOfflineAccess = true,
AlwaysSendClientClaims = true,
AlwaysIncludeUserClaimsInIdToken = true
}
};
}
}
AuthServer Startup.cs
public class Startup
{
public IWebHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
Environment = environment;
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
/****Register asp.net core Identity DBConetexts***/
var idenConnectionString = Configuration["DbContextSettings:IdentityConnectionString"];
var dbPassword = Configuration["DbContextSettings:DbPassword"];
var builder = new NpgsqlConnectionStringBuilder(idenConnectionString)
{
Password = dbPassword
};
services.AddDbContext<MembershipDBContext>(opts => opts.UseNpgsql(builder.ConnectionString));
services.AddIdentity<MembershipUser, MembershipRole>(options =>
{
options.Password.RequiredLength = 8;
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+ ";
options.SignIn.RequireConfirmedEmail = false;
}).AddRoles<MembershipRole>().AddEntityFrameworkStores<MembershipDBContext>()
.AddDefaultTokenProviders();
/****Identity Server implementation with asp.net core Identity***/
var idsServerConnectionString = Configuration["DbContextSettings:IdentityServer4ConnectionString"];
var migrationAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
//var userConnectionString = Configuration["DbContextSettings:UserConnectionString"];
var idsServerdbPassword = Configuration["DbContextSettings:DbPassword"];
var idsServerbuilder = new NpgsqlConnectionStringBuilder(idsServerConnectionString)
{
Password = dbPassword
};
var idBuilder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.UserInteraction.LoginUrl = "/Account/Login";
options.UserInteraction.LogoutUrl = "/Account/Logout";
options.Authentication = new AuthenticationOptions()
{
CookieLifetime = TimeSpan.FromHours(10), // ID server cookie timeout set to 10 hours
CookieSlidingExpiration = true
};
}).AddDeveloperSigningCredential()
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseNpgsql(idsServerbuilder.ConnectionString, sql => sql.MigrationsAssembly(migrationAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseNpgsql(idsServerbuilder.ConnectionString, sql => sql.MigrationsAssembly(migrationAssembly));
options.EnableTokenCleanup = true;
}).AddAspNetIdentity<MembershipUser>()
.AddProfileService<ProfileService>();
idBuilder.Services.ConfigureExternalCookie(options =>
{
options.Cookie.IsEssential = true;
options.Cookie.SameSite = (SameSiteMode)(-1); //SameSiteMode.Unspecified in .NET Core 3.1
});
idBuilder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.IsEssential = true;
options.Cookie.SameSite = (SameSiteMode)(-1); //SameSiteMode.Unspecified in .NET Core 3.1
});
}
private void InitializeDatabase(IApplicationBuilder app)
{
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
context.Database.Migrate();
if (!context.Clients.Any())
{
foreach (var client in Config.GetClients())
{
context.Clients.Add(client.ToEntity());
}
context.SaveChanges();
}
if (!context.IdentityResources.Any())
{
foreach (var resource in Config.GetIdentityResources())
{
context.IdentityResources.Add(resource.ToEntity());
}
context.SaveChanges();
}
if (!context.ApiResources.Any())
{
foreach (var resource in Config.GetApis())
{
context.ApiResources.Add(resource.ToEntity());
}
context.SaveChanges();
}
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
// this will do the initial DB population
InitializeDatabase(app);
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// uncomment if you want to add MVC
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
Api Startup.cs
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; }
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var key = AesOperation.AesKey;
//var str = Configuration["PostgreSql:ConnectionString"];
//var encryptedString = AesOperation.EncryptString(key, str);
var encryptedString = Configuration["PostgreSql:ConnectionString"];
var decryptedString = AesOperation.DecryptString(key, encryptedString);
var connectionString = decryptedString;
encryptedString = Configuration["PostgreSql:DbPassword"];
decryptedString = AesOperation.DecryptString(key, encryptedString);
var dbPassword = decryptedString;
var builder = new NpgsqlConnectionStringBuilder(connectionString)
{
Password = dbPassword
};
services.AddDbContext<MasterDbContext>(options => options.UseNpgsql(builder.ConnectionString));
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Master Microservice", Version = "v1" });
});
services.AddMediatR(typeof(Startup));
RegisterServices(services);
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddAuthorization(options =>
{
options.AddPolicy("MasterCompanyCreatePolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RoleMasterCompanyCreate", "UserMasterCompanyCreate")));
options.AddPolicy("MasterCompanyReadPolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RoleMasterCompanyRead", "UserMasterCompanyRead")));
options.AddPolicy("MasterCompanyUpdatePolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RoleMasterCompanyUpdate", "UserMasterCompanyUpdate")));
options.AddPolicy("MasterCompanyDeletePolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RoleMasterCompanyDelete", "UserMasterCompanyDelete")));
});
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:44396";
options.RequireHttpsMetadata = false;
options.Audience = "masterweb_api";
options.SaveToken = true;
});
}
private bool AuthorizeAccess(AuthorizationHandlerContext context, string roleClaim, string userClaim)
{
return context.User.HasClaim(claim => claim.Type == roleClaim) &&
context.User.HasClaim(claim => claim.Type == userClaim) ||
context.User.IsInRole("SuperAdmin");
}
private void RegisterServices(IServiceCollection services)
{
DependencyContainer.RegisterServices(services);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Master Microservice V1");
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
Mvc Startup.cs
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddAuthorization(options =>
{
options.AddPolicy("MasterCompanyCreatePolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RoleMasterCompanyCreate", "UserMasterCompanyCreate")));
options.AddPolicy("MasterCompanyReadPolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RoleMasterCompanyRead", "UserMasterCompanyRead")));
options.AddPolicy("MasterCompanyUpdatePolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RoleMasterCompanyUpdate", "UserMasterCompanyUpdate")));
options.AddPolicy("MasterCompanyDeletePolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RoleMasterCompanyDelete", "UserMasterCompanyDelete")));
//options.AddPolicy("SaleCancelPolicy", policy => policy.RequireAssertion(context => AuthorizeAccess(context, "RoleSaleCancel", "UserSaleCancel")));
});
services.AddControllersWithViews();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("cookie")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "cookie";
options.Authority = "https://localhost:44396";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.ClientSecret = "Secret";
options.ResponseType = "code id_token";
options.UsePkce = false;
options.Scope.Add("masterweb_api");
options.Scope.Add("profile");
options.Scope.Add("offline_access");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
});
}
private bool AuthorizeAccess(AuthorizationHandlerContext context, string roleClaim, string userClaim)
{
return context.User.HasClaim(claim => claim.Type == roleClaim) &&
context.User.HasClaim(claim => claim.Type == userClaim) ||
context.User.IsInRole("SuperAdmin");
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
RotativaConfiguration.Setup(env.WebRootPath, "Rotativa");
}
}
How to solve this issue. I am totally clueless. Thanks in advance.
You should change the grant type to Code and also enable PKCE, because that is best practice to use when you use the authorization code flow.