Identity Server 4 Authorization Flow - asp.net-core

Identity server 4 and ASP.Net Core 3 noob here, what I'm trying to do is kinda simple: a client should do an HTTP Get request to a remote controller and retrieve some data after being authorized by the Identity Server.
I'm using a code flow as GrantType.
Identity Server services configuration:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
// Identity server database
string connectionString = Config.GetConnectionString("IdentityServerDatabase");
services.AddDbContext<IdentityServerDatabaseContext>(config =>
{
config.UseSqlServer(connectionString);
});
// Easy password for testing
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.Password.RequiredLength = 4;
config.Password.RequireDigit = false;
config.Password.RequireNonAlphanumeric = false;
config.Password.RequireUppercase = false;
config.SignIn.RequireConfirmedAccount = true;
config.User.RequireUniqueEmail = true;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<IdentityServerDatabaseContext>()
.AddDefaultTokenProviders();
services.AddMvc();
services.AddIdentityServer()
// Identity User extension
.AddAspNetIdentity<ApplicationUser>()
.AddDeveloperSigningCredential()
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
});
services.ConfigureApplicationCookie(config =>
{
config.Cookie.Name = "Server.Cookie";
config.LoginPath = "/Home/Login";
config.LogoutPath = "/Home/Logout";
});
services.Configure<DataProtectionTokenProviderOptions>(opt =>
opt.TokenLifespan = TimeSpan.FromHours(12));
}
//This is the IdentityServer jwt bearer, I'm using this because my IdentityServer is also an API that needs authorization, which I use for retrieving my users data:
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", config =>
{
// Identity Server authority
config.Authority = "https://localhost:44300/";
config.Audience = "IdentityAPI";
});
This is the Identity Server configuration for accepted clients:
public class Configuration
{
public static IEnumerable<IdentityResource> GetIdentityResources() =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
public static IEnumerable<ApiScope> GetApiScopes() =>
new List<ApiScope> {
new ApiScope("IdentityAPI"),
new ApiScope("ClientGateway"),
};
public static IEnumerable<ApiResource> GetApiResources() =>
new List<ApiResource> {
new ApiResource("IdentityAPI")
{
Scopes = { "IdentityAPI" }
},
new ApiResource("ClientGateway")
{
Scopes = { "ClientGateway" }
},
};
public static IEnumerable<Client> GetClients() =>
new List<Client> {
// Client for my IdentityServer
new Client {
ClientId = "IdentityAPI",
ClientSecrets = { new Secret("secret_IdentityAPI".ToSha256()) },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = {
"IdentityAPI",
},
};
// My Client configuration, this client is trying to retrieve users data from the IdentityServer
new Client {
ClientId = "ClientGateway",
ClientSecrets = { new Secret("secret_ClientGateway".ToSha256()) },
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
RequirePkce = true,
// AllowedGrantTypes = GrantTypes.ClientCredentials,
// Necessary for authorization code flow type
// where to redirect to after login
RedirectUris = { "https://localhost:44400/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:44400/Home/Index" },
RequireConsent = false,
AllowedScopes = {
"IdentityAPI",
"ClientGateway",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
},
}
This is the client Startup:
public class Startup
{
private readonly IConfiguration Config;
private readonly IWebHostEnvironment Env;
public Startup(IConfiguration config, IWebHostEnvironment env)
{
Config = config;
Env = env;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(config =>
{
config.DefaultScheme = "Cookie";
config.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookie")
.AddOpenIdConnect("oidc", config =>
{
config.SignInScheme = "Cookie";
config.ClientId = "ClientGateway";
config.ClientSecret = "secret_ClientGateway";
config.SaveTokens = true;
config.Authority = "https://localhost:44300/";
config.ResponseType = "code";
config.SignedOutCallbackPath = "/Home/Index";
});
services.AddHttpClient();
services.AddControllersWithViews();
}
}
This is the Client Controller that is trying to authenticate and retrieve the data:
[Route("api/[controller]")]
[ApiController]
public class IdentityController : ControllerBase
{
private static readonly string IdentityAPIUrl = "https://localhost:44300";
private readonly IHttpClientFactory _httpClientFactory;
private readonly IHttpContextAccessor _httpContextAccessor;
public IdentityController(IHttpClientFactory httpClientFactory, IHttpContextAccessor httpContextAccessor)
{
_httpClientFactory = httpClientFactory;
_httpContextAccessor = httpContextAccessor;
}
public static async Task<HttpClient> NewClientGatewayHttpClient(IHttpClientFactory _httpClientFactory)
{
var serverClient = _httpClientFactory.CreateClient();
var discoveryDocument = await serverClient.GetDiscoveryDocumentAsync(IdentityAPIUrl);
var tokenResponse = await serverClient.RequestClientCredentialsTokenAsync(
new ClientCredentialsTokenRequest
{
Address = discoveryDocument.TokenEndpoint,
ClientId = "ClientGateway",
ClientSecret = "secret_ClientGateway",
Scope = "IdentityAPI",
});
var apiClient = _httpClientFactory.CreateClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);
return apiClient;
}
// I'm calling this function to retrieve users data from my IdentityServer
[HttpGet]
[Route("IDUsers")]
public async Task<List<ApplicationUser>> GetIDUsers()
{
HttpClient apiClient = ModisIDController.NewClientGatewayHttpClient(_httpClientFactory).Result;
// This response return a StatusCode: 200
var response = await apiClient.GetAsync(IdentityAPIUrl + "/api/get/users");
// In jsonResult I'm expecting my users data, but instead I'm receiving my view Login.cshtml as a string
var jsonResult = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<List<ApplicationUser>>(jsonResult);
}
This is the IdentityServer function that the Client Controller is trying to call:
[Authorize]
[Route("api/[controller]")]
[ApiController]
// Use this controller for Read API functions
public class GetController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
private readonly IdentityServerDatabaseContext _context;
public GetController(UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
IdentityServerDatabaseContext context)
{
_userManager = userManager;
_roleManager = roleManager;
_context = context;
}
[HttpGet]
[Route("Users")]
public IActionResult GetUsers()
{
return Ok(_context.Users.ToList());
}
}
When ApiController tries to retrieve the data it gets correctly redirected to my Login view by the Identity Server, after login the user I get this output from my IdentityServer:
info: IdentityServer4.Hosting.IdentityServerMiddleware[0]
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryEndpoint for /.well-known/openid-configuration
info: IdentityServer4.Hosting.IdentityServerMiddleware[0]
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryKeyEndpoint for /.well-known/openid-configuration/jwks
info: IdentityServer4.Hosting.IdentityServerMiddleware[0]
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.TokenEndpoint for /connect/token
info: IdentityServer4.Validation.TokenRequestValidator[0]
Token request validation success, {
"ClientId": "ClientGateway",
"GrantType": "client_credentials",
"Scopes": "IdentityAPI",
"Raw": {
"grant_type": "client_credentials",
"scope": "IdentityAPI",
"client_id": "ClientGateway",
"client_secret": "***REDACTED***"
}
}
But the response that I retrieve in var content is HTML of the Login page and not the expected data. It seems like that the Identity Server is trying to redirect me again. I don't undestard why this happens because I'm using client credentials flow.

Related

calling graph from asp .net core signalr hub

I am trying to get groups from AzureAD by calling graph in the hub in the OnConnectedAsync() to add the current user to groups.
When calling my graph service from a controller it works just fine and returns me the data I want, but when calling it from inside the hub I get an error:
IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent. No account or login hint was passed to the AcquireTokenSilent call.
Any idea?
Here is my graph service :
public class GraphService : IGraphService
{
private readonly ITokenAcquisition _tokenAcquisition;
private readonly IHttpClientFactory _httpClientFactory;
public GraphService(ITokenAcquisition tokenAcquisition,
IHttpClientFactory clientFactory)
{
_httpClientFactory = clientFactory;
_tokenAcquisition = tokenAcquisition;
}
public async Task<IEnumerable<Group>> GetUserGroupsAsync()
{
var graphClient = await GetGraphClient();
var memberShipCollection = await graphClient
.Me
.MemberOf
.Request()
.GetAsync();
return memberShipCollection
.OfType<Group>();
}
private async Task<GraphServiceClient> GetGraphClient()
{
var token = await _tokenAcquisition
.GetAccessTokenForUserAsync(new string[] { "https://graph.microsoft.com/.default" });
var client = _httpClientFactory.CreateClient();
client.BaseAddress = new Uri("https://graph.microsoft.com/v1.0");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var graphClient = new GraphServiceClient(client)
{
AuthenticationProvider = new DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
})
};
graphClient.BaseUrl = "https://graph.microsoft.com/v1.0";
return graphClient;
}
}
The hub :
[Authorize]
public class NotificationHub : Hub
{
ILogger<NotificationHub> _logger;
private readonly IGraphService _graphService;
public NotificationHub(ILogger<NotificationHub> logger,
IGraphService graphService)
{
_logger = logger;
_graphService = graphService;
}
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
var userDynamicGroups = await GetUserDynamicGroupsAsync();
//foreach (var group in userDynamicGroups)
// await Groups.AddToGroupAsync(Context.ConnectionId, group.DisplayName);
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await base.OnDisconnectedAsync(exception);
//var userDynamicGroups = await GetUserDynamicGroupsAsync();
//foreach (var group in userDynamicGroups)
// await Groups.RemoveFromGroupAsync(Context.ConnectionId, group.DisplayName);
}
private async Task<IEnumerable<Group>> GetUserDynamicGroupsAsync()
{
var AllUserGroups = await _graphService.GetUserGroupsAsync();
return AllUserGroups.Where(g => g.DisplayName.Contains("ONE_"));
}
}
The part related to auth in my startup:
public static IServiceCollection AddInternalAuthentification(this IServiceCollection services, IConfiguration configuration)
{
services.AddMicrosoftIdentityWebApiAuthentication(configuration, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
Func<MessageReceivedContext, Task> existingOnMessageReceivedHandler = options.Events.OnMessageReceived;
options.Events.OnMessageReceived = async context =>
{
await existingOnMessageReceivedHandler(context);
StringValues accessToken = context.Request.Query["access_token"];
PathString path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/Notify"))
context.Token = accessToken;
};
});
return services;
}

How to get identity values from google external provider in ASP.NET Core Web API

[ApiController]
[Route("[controller]")]
public class AuthController : ControllerBase
{
private readonly SignInManager<IdentityUser> _signInManager;
public AuthController(SignInManager<IdentityUser> signInManager)
{
_signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager));
}
[HttpGet("token")]
public ChallengeResult Token()
{
var properties = new GoogleChallengeProperties
{
RedirectUri = "/auth/retrieve",
AllowRefresh = true,
};
return Challenge(properties, "Google");
}
[HttpGet("[action]")]
public async Task Retrieve()
{
var token = await HttpContext.GetTokenAsync("access_token");
var externalLoginInfoAsync = await _signInManager.GetExternalLoginInfoAsync();
var identityName = User?.Identity?.Name;
var authenticateResult = await HttpContext.AuthenticateAsync();
}
}
I direct the user to /auth/token, where he is redirected to the Google Oauth Page, if successful, he is redirected to /auth/retrieve, where I expect the user data, but token, externalLoginInfoAsync, identityName, authenticateResult is null
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("Default")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication()
.AddCookie()
.AddGoogle(options =>
{
options.Scope.Add("https://www.googleapis.com/auth/gmail.settings.basic");
options.AccessType = "offline";
options.SaveTokens = true;
options.SignInScheme = IdentityConstants.ExternalScheme;
options.Events.OnCreatingTicket = ctx =>
{
var identityName = ctx.Identity.Name;
return Task.CompletedTask;
};
options.ClientId = "SMTH_VALUE";
options.ClientSecret = "SMTH_VALUE";
});
services.AddControllers();
}
I debug the google provider and found the user values in the Events - identityName is not null.
How i can get this value in the controller?
You could refer the following code to configure Google authentication in Startup.ConfigureServices method:
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.AddAuthentication()
.AddGoogle(opt =>
{
opt.ClientId = "620831551062-rcvu44q4rhr5d8ossu3m0163jqbjdji0.apps.googleusercontent.com";
opt.ClientSecret = "GXFN0cHBbUlZ6nYLD7a7-cT8";
opt.SignInScheme = IdentityConstants.ExternalScheme;
});
services.AddControllersWithViews();
services.AddRazorPages();
}
Then, use the following sample to login using Google and get user information:
[Authorize]
public class AccountController : Controller
{
private UserManager<ApplicationUser> userManager;
private SignInManager<ApplicationUser> signInManager;
public AccountController(UserManager<ApplicationUser> userMgr, SignInManager<ApplicationUser> signinMgr)
{
userManager = userMgr;
signInManager = signinMgr;
}
// other methods
public IActionResult AccessDenied()
{
return View();
}
[AllowAnonymous]
public IActionResult GoogleLogin()
{
string redirectUrl = Url.Action("GoogleResponse", "Account");
var properties = signInManager.ConfigureExternalAuthenticationProperties("Google", redirectUrl);
return new ChallengeResult("Google", properties);
}
public IActionResult Login()
{
return View();
}
[AllowAnonymous]
public async Task<IActionResult> GoogleResponse()
{
ExternalLoginInfo info = await signInManager.GetExternalLoginInfoAsync();
if (info == null)
return RedirectToAction(nameof(Login));
var result = await signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, false);
string[] userInfo = { info.Principal.FindFirst(ClaimTypes.Name).Value, info.Principal.FindFirst(ClaimTypes.Email).Value };
if (result.Succeeded)
return View(userInfo);
else
{
ApplicationUser user = new ApplicationUser
{
Email = info.Principal.FindFirst(ClaimTypes.Email).Value,
UserName = info.Principal.FindFirst(ClaimTypes.Email).Value
};
IdentityResult identResult = await userManager.CreateAsync(user);
if (identResult.Succeeded)
{
identResult = await userManager.AddLoginAsync(user, info);
if (identResult.Succeeded)
{
await signInManager.SignInAsync(user, false);
return View(userInfo);
}
}
return AccessDenied();
}
}
}
The result like this:
More detail information, see How to integrate Google login feature in ASP.NET Core Identity and Google external login setup in ASP.NET Core

OAuth .NET Core - to many redirects after sign in with custom options - Multi Tenant

I'm trying to implement OAuth per tenant. Each tenant has their own OAuthOptions.
I overwritten OAuthOptions and with IOptionsMonitor i resolve the OAuthOptions every time. Based on this answer: openid connect - identifying tenant during login
But right now after log in on external, the callback to signin ends up in to many redirects.
Signin successfull -> redirect to app -> app says not authenticated -> redirect to external login -> repeat.
The code:
My Startup.ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddAuthentication(options =>
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "OAuth";
})
.AddCookie("OAuth.Cookie", options =>
{
options.Cookie.Name = "OAuth-cookiename";
options.Cookie.SameSite = SameSiteMode.None;
options.LoginPath = "/account/login";
options.AccessDeniedPath = "/account/login";
})
.AddOAuth<MyOptions, OAuthHandler<MyOptions>>("OAuth", options =>
{
// All options are set at runtime by tenant settings
});
services.AddScoped<IOptionsMonitor<MyOptions>, MyOptionsMonitor>();
services.AddScoped<IConfigureOptions<MyOptions>, ConfigureMyOptions>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
Startup.Configure:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
ConfigureMyOptions.cs
public class ConfigureMyOptions : IConfigureNamedOptions<MyOptions>
{
private HttpContext _httpContext;
private IDataProtectionProvider _dataProtectionProvider;
private MyOptions myCurrentOptions;
public ConfigureMyOptions(IHttpContextAccessor contextAccessor, IDataProtectionProvider dataProtectionProvider)
{
_httpContext = contextAccessor.HttpContext;
_dataProtectionProvider = dataProtectionProvider;
}
public void Configure(string name, MyOptions options)
{
//var tenant = _httpContext.ResolveTenant();
// in my code i use tenant.Settings for these:
options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
options.TokenEndpoint = "https://github.com/login/oauth/access_token";
options.UserInformationEndpoint = "https://api.github.com/user";
options.ClientId = "redacted";
options.ClientSecret = "redacted";
options.Scope.Add("openid");
options.Scope.Add("write:gpg_key");
options.Scope.Add("repo");
options.Scope.Add("read:user");
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
options.ClaimActions.MapJsonKey("External", "id");
options.SignInScheme = "OAuth.Cookie";
options.CallbackPath = new PathString("/signin");
options.SaveTokens = true;
options.Events = new OAuthEvents
{
OnCreatingTicket = _onCreatingTicket,
OnTicketReceived = _onTicketReceived
};
myCurrentOptions = options;
}
public void Configure(MyOptions options) => Configure(Options.DefaultName, options);
private static async Task _onCreatingTicket(OAuthCreatingTicketContext context)
{
// Get the external user id and set it as a claim
using (var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint))
{
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
using (var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted))
{
response.EnsureSuccessStatusCode();
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
context.RunClaimActions(user);
}
}
}
private static Task _onTicketReceived(TicketReceivedContext context)
{
context.Properties.IsPersistent = true;
context.Properties.AllowRefresh = true;
context.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(30);
return Task.CompletedTask;
}
}
MyOptions:
// Overwritten to by pass validate
public class MyOptions : OAuthOptions
{
public override void Validate()
{
return;
}
public override void Validate(string scheme)
{
return;
}
}
MyOptionsMonitor:
// TODO caching
public class MyOptionsMonitor : IOptionsMonitor<MyOptions>
{
// private readonly TenantContext<Tenant> _tenantContext;
private readonly IOptionsFactory<MyOptions> _optionsFactory;
public MyOptionsMonitor(
// TenantContext<Tenant> tenantContext,
IOptionsFactory<MyOptions> optionsFactory)
{
// _tenantContext = tenantContext;
_optionsFactory = optionsFactory;
}
public MyOptions CurrentValue => Get(Options.DefaultName);
public MyOptions Get(string name)
{
return _optionsFactory.Create($"{name}");
}
public IDisposable OnChange(Action<MyOptions, string> listener)
{
return null;
}
}

blazor webassembly System.Net.Http.HttpRequestException: Response status code does not indicate success: 400 (Bad Request)

I have a blazor project that is dotnet core hosted. I am working on blazor authentication following this tutorial. Everything is ok on the server side because I was able to use Postman to create users successfully. I have tried different suggestions online but non work for me. Some suggested CORS which I fixed, some route which I amended but problem still persist.
I have been having issue debugging as I cant get break point in the browser console. I have debugged some blazor project I could set break points and view local variables but I couldnt with the current project. I dont know if its a bug.
server startup.cs
namespace EssentialShopCoreBlazor.Server
{
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration _configuration)
{
Configuration = _configuration;
}
readonly string AllowSpecificOrigins = "allowSpecificOrigins";
// 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.AddDbContext<DbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUsersModel, IdentityRole>()
.AddEntityFrameworkStores<DbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSecurityKey"]))
};
});
services.AddCors(options =>
{
options.AddPolicy(AllowSpecificOrigins,
builder =>
{
builder.WithOrigins("https://localhost:44365", "https://localhost:44398")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
services.AddScoped<AccountAuthController>();
services.AddMvc().AddNewtonsoftJson();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseResponseCompression();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBlazorDebugging();
}
app.UseCors(AllowSpecificOrigins);
app.UseStaticFiles();
app.UseClientSideBlazorFiles<Client.Startup>();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapFallbackToClientSideBlazor<Client.Startup>("index.html");
});
}
}
}
controller
namespace EssentialShopCoreBlazor.Server.Controllers
{
[Route("essential/users/")]
public class AccountAuthController : ControllerBase
{
private static UserModel LoggedOutUser = new UserModel { IsAuthenticated = false };
private readonly UserManager<IdentityUsersModel> userManager;
private readonly IConfiguration configuration;
private readonly SignInManager<IdentityUsersModel> signInManager;
public AccountAuthController(UserManager<IdentityUsersModel> _userManager, SignInManager<IdentityUsersModel> _signInManager, IConfiguration _configuration)
{
userManager = _userManager;
signInManager = _signInManager;
configuration = _configuration;
}
[HttpPost]
[Route("create")]
public async Task<ActionResult<ApplicationUserModel>> CreateUser([FromBody] ApplicationUserModel model)
{
var NewUser = new IdentityUsersModel
{
UserName = model.UserName,
BusinessName = model.BusinessName,
Email = model.Email,
PhoneNumber = model.PhoneNumber
};
var result = await userManager.CreateAsync(NewUser, model.Password);
if (!result.Succeeded)
{
var errors = result.Errors.Select(x => x.Description);
return BadRequest(new RegistrationResult { Successful = false, Errors = errors });
}
return Ok(new RegistrationResult { Successful = true });
}
}}
client service
public class AuthService : IAuthService
{
private readonly HttpClient _httpClient;
private readonly AuthenticationStateProvider _authenticationStateProvider;
private readonly ILocalStorageService _localStorage;
string BaseUrl = "https://localhost:44398/essential/users/";
public AuthService(HttpClient httpClient,
AuthenticationStateProvider authenticationStateProvider,
ILocalStorageService localStorage)
{
_httpClient = httpClient;
_authenticationStateProvider = authenticationStateProvider;
_localStorage = localStorage;
}
public async Task<RegistrationResult> Register(ApplicationUserModel registerModel)
{
var result = await _httpClient.PostJsonAsync<RegistrationResult>(BaseUrl + "create", registerModel);
return result;
}
}

Unable to resolve service for type at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider

I am attempting to receive data from the server controller, stocks.
I get this error:
"System.InvalidOperationException: Unable to resolve service for type myBackEnd.Models.StockContext' while attempting to activate 'myBackEnd.Controllers.StockController'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService
(IServiceProvider sp, Type type, Type requiredBy, Boolean
isDefaultParameterRequired"
Here is my stocks controller code:
namespace myBackEnd.Controllers
{
[Route("api/stock")]
[Produces("application/json")]
public class StockController : ControllerBase
{
private readonly int fastEmaPeriod = 10;
private readonly IHttpClientFactory _httpClientFactory;
private readonly Models.StockContext _context;
public StockController(Models.StockContext context, IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
_context = context;
}
// POST api/values
[HttpPost]
public async Task<IActionResult> Post([FromBody]Models.Stock stock)
{
_context.Stocks.Add(stock);
await _context.SaveChangesAsync();
return Ok(stock);
}
This is the startup.cs code:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o => o.AddPolicy("MyPolicy", corsBuilder =>
{
corsBuilder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
}));
services.AddDbContext<DataContext>(x => x.UseInMemoryDatabase("TestDb"));
services.AddHttpClient();
services.AddAutoMapper();
// configure strongly typed settings objects
var appSettingsSection = Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
// configure jwt authentication
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
var userId = int.Parse(context.Principal.Identity.Name);
var user = userService.GetById(userId);
if (user == null)
{
// return unauthorized if user no longer exists
context.Fail("Unauthorized");
}
return Task.CompletedTask;
}
};
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
// configure DI for application services
services.AddScoped<IUserService, UserService>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
This worked before I added the registration, login and
// configure DI for application services
services.AddScoped();
The problem was the DB context was not registered for dependency injection.
Adding:
services.AddDbContext<Models.StockContext>(opt => opt.UseInMemoryDatabase("item"));
fixed the problem.