IdentityServer4 w/AspNetIdentity and Registration API Methods - asp.net-core

I've setup IdentityServer4, a protected API (Core) project, and various clients. Anonymously accessed client pages use the Resource Owner flow to access the APIs and user credentials are used from client pages where login is required. This is all working. My problem is now I want to add registration API methods that are protected.
The new registration methods require the API project to use AspNetIdentity. Specifically they use Identity's UserManager object which is failing to instantiate unless I add this code to my Startup.ConfigureServices:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
But adding this code breaks the normal IDServer4 Bearer authentication. The Authorize tag on the API controller now sends the requesters to the login page. Is there a way to create a good userManager component without the chunk of code above so Identity authentication does not come into play?
Without the code above I get the following error:
Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager`1[TestAPICore.Models.ApplicationUser]' while attempting to activate *controller*
Here is my ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
//// ADDING THIS CAUSES API requests to send back the login screen
//services.AddIdentity<ApplicationUser, IdentityRole>()
// .AddEntityFrameworkStores<ApplicationDbContext>()
// .AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}
Ideas for making this work?
Update:
Reading more, it looks like calling services.AddIdentityCore<ApplicationUser>(cfg => {}); is the way to go. I've tried it both before and after the .AddAuthentication code but I still get nearly the same error:
System.InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore`1[TestAPICore.Models.ApplicationUser]' while attempting to activate 'Microsoft.AspNetCore.Identity.UserManager`1[TestAPICore.Models.ApplicationUser]'.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites...
...which is slightly different since it no longer references my controller.
Solved!
Here's what worked...
IdentityBuilder builder = services.AddIdentityCore<ApplicationUser>(options => { });
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder.AddEntityFrameworkStores<ApplicationDbContext>();
Thanks,Kirk, for pointing me in the right direction!

So that others may benefit, here's my whole ConfigureServices method for my API project which can manage users but still authenticates against IdentityServer:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
services.AddCors(o => o.AddPolicy("MyPolicy", bld =>
{
bld.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
// *** The Fix ***
IdentityBuilder builder = services.AddIdentityCore<ApplicationUser>(options => { });
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder.AddEntityFrameworkStores<ApplicationDbContext>();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}

Related

AzureADDefaults is obsolete

I have following code for Azure AD authentication:
services
.AddAuthorization(options =>
{
options.AddPolicy(name, builder =>
{
builder
.AddAuthenticationSchemes(AzureADDefaults.AuthenticationScheme)
.RequireAuthenticatedUser();
});
})
.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options =>
{
configuration.Bind("AzureAd", options);
});
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
...
}
AzureADDefaults.AuthenticationScheme and AzureADDefaults.OpenIdScheme are now obsolete with message "Use Microsoft.Identity.Web instead. See https://aka.ms/ms-identity-web.". However I can't find any clear documentation how to upgrade following code to use Identity.Web instead of those obsolete constants.
Does anyone have instructions how to remove this obsolete code?
This blog shows you the differences between Identity Platform and Identity.Web.
For Identity.Web, we use Microsoft.Identity.Web and Microsoft.Identity.Web.UI. Try to see this sample, and it uses AddMicrosoftIdentityWebAppAuthentication to sign in users.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
// Handling SameSite cookie according to https://learn.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1
options.HandleSameSiteCookieCompatibility();
});
// Sign-in users with the Microsoft identity platform
services.AddMicrosoftIdentityWebAppAuthentication(Configuration);
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
services.AddRazorPages();
}

Authentication Tokens in Blazor Server

I have an ASP.net core WEB API that I have secured using a local user account (used the WASM blazor template to create it). I can successfully authenticate and call the webapi using the WASM client.
I have a separate Server side Blazor app that attempts to call this WebAPI which again has been secured using a local user account and the same identity database).
In the wasm (webapi) app, I configure security as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("https://localhost:44339");
});
});
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
In the server side app, I configure security as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options => { options.SignIn.RequireConfirmedAccount = true; })
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddScoped<AuthenticationStateProvider,
RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddHttpClient();
In the server side app, I then try to get tokens in the _Host.cshtml file so that I can add these to the client header, but they always come back null.
AccessToken = await HttpContext.GetTokenAsync("access_token"),
RefreshToken = await HttpContext.GetTokenAsync("refresh_token")
I'm a complete newbie on this security side, so appreciate I may have this completely wrong. Could anyone give me a pointer as to whether I'm doing the wrong thing, or if there's something else I need to do to get these tokens?
Many thanks in advance
Gordon

ASP.Net Core Identity with JwtBearer AuthenticationScheme map claims to context User object

I have a React Front end using the msal lib to authenticate the user client side with our Azure AD. This works great and authentication has no issues. I also have an ASP.Net Core WebApi to provide data to the client. I am using the JwtTokens to pass the Bearer token in the request. The WebApi is able to validate the token and all is well... I thought, however, when the WebApi method is invoked the only way I can get the User's email or name is to query the User.Claims with Linq.
this.User.Claims.Where(c=> c.Type == "preferred_username").FirstOrDefault().Value
I was about to go down the road of mapping these linq queries to an object which could be injected into the WebApi's controller, but that seems wrong.
I am obviously missing something in my Startup.cs for the WebApi, Any help or suggestions would be great!:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
//add authentication JwtBearer Scheme
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Audience = Configuration["JwtSettings:Audience"];
options.Authority = Configuration["JwtSettings:Authority"];
options.Events = new JwtBearerEvents
{
OnTokenValidated = ctx =>
{
//log
return Task.CompletedTask;
},
OnAuthenticationFailed = ctx =>
{
//log
return Task.CompletedTask;
}
};
options.SaveToken = true;
});
services.AddAuthorization();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}

AzureAD and AzureADBearer in asp.net core app, gives invalid signature error

I am using AzureAD in asp.net core 2 app. I want to use cookie and bearer authentication both. I have following code in startup file:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
//options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options))
.AddAzureADBearer(options => Configuration.Bind("AzureAdClient", options));
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), sqlServerOptions => sqlServerOptions.CommandTimeout(120)));
//services.AddMvc();
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
//options.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
I have added authorized attribute as:
[Authorize(AuthenticationSchemes = "AzureADBearer")]
Now when hitting from postman, i can get the bearer token, but when i am using that token to access this API, i am getting signature invalid error:
WWW-Authenticate →Bearer error="invalid_token", error_description="The signature is invalid"
Any Ideas?
Try something like below , It should work.
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultChallengeScheme = AzureADDefaults.AuthenticationScheme;
sharedOptions.DefaultAuthenticateScheme = AzureADDefaults.AuthenticationScheme;
})
.AddAzureAD(options => Configuration.Bind("AzureAd", options))
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
and in controller application, you can set the schema like this:
[HttpGet]
[Authorize(AuthenticationSchemes = "AzureADBearer")]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Hope it helps.
Well, I solved this. Now in asp.net core web + API project, I am using only the API specific AzureAD setting. For Postman and mobile application, I have created a new app registration and added scope for earlier app registration (API app registration), which have user impersonation and access as user permissions.

Embedded IdentityServer 4 with Aspnet Identity and resource owner

I am trying to use IdentityServer4 with resource owner flow + aspnet identity and embed the api in the same project.
I tested the Sample here on github and it's working fine. I am able to retrieve a token for a registered user in the database and use this token to get protected resources from the api.
The sample the api is separated from the identity server, once both are merged into one project, im still able to get a token, BUT I get 401 Unauthorized while trying to access the protected resource. somehow the embedded api is no longer validating the token.
here's the Startup.cs code :
// 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")));
//(1)
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services
.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
//(2)
.AddAspNetIdentity<ApplicationUser>();
//.AddTestUsers(Config.GetUsers());
var corsBuilder = new CorsPolicyBuilder();
corsBuilder.AllowAnyHeader();
corsBuilder.AllowAnyMethod();
corsBuilder.AllowAnyOrigin();
corsBuilder.AllowCredentials();
corsBuilder.WithExposedHeaders("Location");
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", corsBuilder.Build());
});
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:51318";
options.RequireHttpsMetadata = false;
options.ApiName = "api";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseCors("CorsPolicy");
app.UseIdentityServer();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Note that if we swith to in memory TestUser instead of persisted ApplicationUser by commenting the code in (1) and changing the code in (2) to :
//(2)
//.AddAspNetIdentity<ApplicationUser>();
.AddTestUsers(Config.GetUsers());
the whole system works and the embedded api is authenticating the user normally.
Is there something missing in this code ? In real life scenarios the api will almost always be embedded with the identity server because of cost efficiency, is there any example I can use to make it work ?
Thank you.
After digging into AspNet Identity source code, I realized that the AddIdentity extension was doing some extra work that prevents from validating the token, but without it and the AddEntityFrameworkStores method the identity managers were not set by dependency injection.
So we need to replace :
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
by a piece of code that does only dependency injection like that :
services.TryAddScoped<IUserValidator<ApplicationUser>, UserValidator<ApplicationUser>>();
services.TryAddScoped<IPasswordValidator<ApplicationUser>, PasswordValidator<ApplicationUser>>();
services.TryAddScoped<IPasswordHasher<ApplicationUser>, PasswordHasher<ApplicationUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IRoleValidator<IdentityRole>, RoleValidator<IdentityRole>>();
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<ApplicationUser>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>();
services.TryAddScoped<UserManager<ApplicationUser>, AspNetUserManager<ApplicationUser>>();
services.TryAddScoped<SignInManager<ApplicationUser>, SignInManager<ApplicationUser>>();
services.TryAddScoped<RoleManager<IdentityRole>, AspNetRoleManager<IdentityRole>>();
services.TryAddScoped<IRoleStore<IdentityRole>, RoleStore<IdentityRole>>();
services.TryAddScoped<DbContext, ApplicationDbContext>();
services.TryAddScoped<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>();
by doing this, the final result is a working identity server embedded in the api with AspNet Identity.