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;
}
Related
[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
Trying to set-up unit / integration tests for some extensions I am writing for the OdataQueryOptions class. I am using .net core 3.1.
In order to create the SUT instance - I need a HttpRequest. Which I creating using the WebApplicationFactory
public class TestingWebApplicationFactoryFixture : WebApplicationFactory<TestStartUp>
{
protected override IHostBuilder CreateHostBuilder()
{
var builder = Host.CreateDefaultBuilder();
builder.ConfigureWebHost(hostBuilder =>
{
hostBuilder.ConfigureServices(services =>
{
services.AddMvc(options => options.EnableEndpointRouting = false);
services.AddOData();
}).Configure(app =>
{
app.UseMvc(routeBuilder =>
{
routeBuilder.EnableDependencyInjection();
routeBuilder.Select().Expand().OrderBy().Filter().MaxTop(int.MaxValue);
});
});
});
return builder;
}
I arrange the test to use the TestServer to produce the HttpContext. The OdataQueryContext and HttpRequest is then used to instantiate the OdataQueryOptions object.
const string path = "/?$filter=SalesOrderID eq 43659";
var httpContext = await _testingWebApplicationFactoryFixture.Server.SendAsync(context =>
{
context.Request.Method = HttpMethods.Get;
context.Request.Path = path;
});
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.AddEntityType(typeof(Customer));
var model = modelBuilder.GetEdmModel();
var odataQueryContext = new ODataQueryContext(model, typeof(Customer), new ODataPath());
var sut = new ODataQueryOptions<Customer>(odataQueryContext, httpContext.Request);
I am getting an exception during the instantiation of the object:
System.ArgumentNullException
Value cannot be null. (Parameter 'provider')
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T]
(IServiceProvider provider)
at Microsoft.AspNet.OData.Extensions.HttpRequestExtensions.CreateRequestScope(HttpRequest request,
String routeName)
at Microsoft.AspNet.OData.Extensions.HttpRequestExtensions.CreateRequestContainer(HttpRequest
request, String routeName)
at Microsoft.AspNet.OData.Extensions.HttpRequestExtensions.GetRequestContainer(HttpRequest request)
at Microsoft.AspNet.OData.Query.ODataQueryOptions..ctor(ODataQueryContext context, HttpRequest
request)
at Microsoft.AspNet.OData.Query.ODataQueryOptions`1..ctor(ODataQueryContext context, HttpRequest
request)
Digging into the actual method that is throwing - it is because the IServiceProvider is null. Shouldn't this be handled by the host?
UPDATE:
I modified the test method a bit so that I eliminate the WebApplicationFactory class.
Instead I create a TestServer with an IWebHostBuilder:
private IWebHostBuilder GetBuilder()
{
var webHostBuilder = new WebHostBuilder();
webHostBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddMvc(options => options.EnableEndpointRouting = false);
services.AddOData();
}).Configure(app =>
{
app.UseMvc(routeBuilder =>
{
routeBuilder.EnableDependencyInjection();
routeBuilder.Select().Expand().OrderBy().Filter().MaxTop(int.MaxValue);
});
});
return webHostBuilder;
}
And then create the TestServer:
[Fact]
public async Task QueryGenerator_Generate_SomeExpress_ShouldProduce()
{
const string path = "/?$filter=SalesOrderID eq 43659";
var testServer = new TestServer(GetBuilder());
var httpContext = await testServer.SendAsync(context =>
{
context.Request.Method = HttpMethods.Get;
context.Request.Path = path;
});
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.AddEntityType(typeof(Customer));
var model = modelBuilder.GetEdmModel();
var odataQueryContext = new ODataQueryContext(model, typeof(Customer), new ODataPath());
var sut = new ODataQueryOptions<Customer>(odataQueryContext, httpContext.Request);
}
I get the same exception. Why is the IServiceProvider null?
Never got a solution to using TestServer, but I found a work around. At the end of the day I needed the OdataQueryOptions class generated by the framework. So I created an IClassFixture<> in Xunit to manually get it created.
public class OdataQueryOptionFixture
{
public IServiceProvider Provider { get; private set; }
private IEdmModel _edmModel;
public OdataQueryOptionFixture()
{
SetupFixture();
}
public ODataQueryOptions<T> CreateODataQueryOptions<T>(HttpRequest request)
where T : class
{
var odataQueryContext = CreateOdataQueryContext<T>();
var odataQueryOptions = new ODataQueryOptions<T>(odataQueryContext, request);
return odataQueryOptions;
}
private ODataQueryContext CreateOdataQueryContext<T>()
where T : class
{
var odataQueryContext = new ODataQueryContext(_edmModel, typeof(T), new ODataPath());
return odataQueryContext;
}
private void SetupFixture()
{
var collection = new ServiceCollection();
collection.AddOData();
collection.AddTransient<ODataUriResolver>();
collection.AddTransient<ODataQueryValidator>();
Provider = collection.BuildServiceProvider();
ConfigureRoutes();
BuildModel();
}
private void ConfigureRoutes()
{
var routeBuilder = new RouteBuilder(Mock.Of<IApplicationBuilder>(x => x.ApplicationServices == Provider));
routeBuilder.Select().Expand().OrderBy().Filter().MaxTop(int.MaxValue).Count();
routeBuilder.EnableDependencyInjection();
}
private void BuildModel()
{
var edmContext = new AdventureWorksEdmContext();
_edmModel = edmContext.BuildModel();
}
Using the class fixture in a test class to construct the OdataQueryOptions
private QueryOptionsBuilder<Customer> GetSut(HttpRequest request)
{
var odataQueryOptions = _odataQueryOptionFixture.CreateODataQueryOptions<Customer>(request);
var odataQuerySettings = new ODataQuerySettings();
var odataValidationSettings = new ODataValidationSettings();
var customerExpandBinder = new CustomerExpandBinder(odataValidationSettings, odataQueryOptions.SelectExpand);
var customerOrderByBinder = new CustomerOrderByBinder(odataValidationSettings, odataQueryOptions.OrderBy);
var customerSelectBinder = new CustomerSelectBinder(odataValidationSettings, odataQueryOptions.SelectExpand);
var customerCompositeBinder = new CustomerCompositeBinder(customerExpandBinder, customerOrderByBinder, customerSelectBinder);
return new QueryOptionsBuilder<Customer>(customerCompositeBinder, odataQuerySettings);
}
TestServer would have been easier - but this gets the job done.
So I want to setup a User Middleware which works for SignalR Hubs and Controllers.
It works fine with normal requests but with signalr it gets called but doesnt add to context.
Is it even possible? If so how can i do it?
namespace PortalCore.Middleware
{
public class JwtMiddleware
{
private readonly RequestDelegate _next;
public JwtMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, AuthService authService)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (token != null)
{
AttachUserToContext(context, authService, token);
}
await _next(context);
}
private async void AttachUserToContext(HttpContext context, AuthService authService, string token)
{
User user = null;
var tokenHandler = new JwtSecurityTokenHandler();
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey =
new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(authService.SecretKey)),
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
user = await authService.GetUserByUid(jwtToken.Claims.FirstOrDefault()?.Value);
}
catch (Exception e)
{
}
context.Items["User"] = user;
}
}
}
if you want to check auth of signalR hub then you can do it with query string.you can send token with signalR client url.After take token from query string and set to context.
Hub Code:
[Authorize]
public class ChatHub : Hub
you can add token in Context :
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrWhiteSpace(accessToken) &&
(path.StartsWithSegments("/api/hubs/chatHub")))
{
context.Token = accessToken;
}
return Task.CompletedTask;
},
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.
Below is my seed class..
public static class DataInitializer
{
public static async void SeedRolesAsync(RoleManager<Role> roleManager)
{
if (roleManager.RoleExistsAsync("Administrator").Result)
return;
var role = new Role
{
Name = "Administrator",
Description = "Perform all the operations."
};
await roleManager.CreateAsync(role);
}
public static async void SeedRoleClaimsAsync(RoleManager<Role> roleManager)
{
var role = await roleManager.FindByNameAsync("Administrator");
var roleClaims = await roleManager.GetClaimsAsync(role);
foreach (var claimString in AllClaims.GetList())
{
var newClaim = new Claim(claimString, "");
if (!roleClaims.Any(rc => rc.Type.ToString() == claimString))
{
await roleManager.AddClaimAsync(role, newClaim);
}
}
}
public static async void SeedUsersAsync(UserManager<User> userManager)
{
var user = new User
{
UserName = "admin#example.com",
Email = "admin#example.com",
FirstName = "Admin",
LastName = "User",
Enabled = true
};
var result = await userManager.CreateAsync(user, "Admin#123");
if (result.Succeeded)
{
await userManager.AddToRoleAsync(user, "Administrator");
}
}
public static void SeedData(UserManager<User> userManager, RoleManager<Role> roleManager)
{
SeedRolesAsync(roleManager);
SeedRoleClaimsAsync(roleManager);
SeedUsersAsync(userManager);
}
}
calling this method in startup class DataInitializer.SeedData(userManager, roleManager);
Am getting error below error while seeding.. i am using ef core 3 for postgresql.. Am getting
System.ObjectDisposedException: 'Cannot access a disposed object.
Object name: 'MyUserManager'.'
Try using tasks all the way - if you don't return anything to wait on chances are the code will continue with disposal even when something hasn't completed yet.
public static async Task SeedData(UserManager<User> userManager, RoleManager<Role> roleManager)
{
await SeedRolesAsync(roleManager);
await SeedRoleClaimsAsync(roleManager);
await SeedUsersAsync(userManager);
}
public static async Task SeedRolesAsync(RoleManager<Role> roleManager)
{
⋮
}
public static async Task SeedRoleClaimsAsync(RoleManager<Role> roleManager)
{
⋮
}
public static async Task SeedUsersAsync(UserManager<User> userManager)
{
⋮
}
You can also move this call into the Main method of your Program.cs because that method can be made async.
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
using (var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>())
using (var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<Role>>())
{
await DataInitializer.SeedData(userManager, roleManager).ConfigureAwait(false);
}
await host.RunAsync().ConfigureAwait(false);
}