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
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;
}
[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
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;
}
}
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;
}
}
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.