fbpx

04. PART 1 IdentityServer4 ASP.NET Core Identity

You can find the project here.

What is ASP.NET Core Identity

The official explanation from Microsoft docs is: “ASP.NET Core Identity is a membership system that adds login functionality to ASP.NET Core apps. Users can create an account with the login information stored in Identity or they can use an external login provider.” and “Identity can be configured using a SQL Server database to store user names, passwords, and profile data.”.

So it is a membership system that takes care of members (another word for users) and it is used by ASP.NET Core apps (what we are building). Users can create local accounts stored in Identity (another name for user store) or can use any external provider like Google, Okta, Microsoft, Facebook, etc. User data can be persisted to a standard SQL database (which we already have). ASP.NET Identity standardizes user store with structure (tables) and methods to manipulate the store. For example to create a new user account, just invoke a method. It will validate the data and store it in a database. To log in, just invoke a login method, it will validate the password (for a local login) and return a valid response.

To set or change the password, just invoke a method and the password will get hashed and stored in the database. So a user store is a set of business logic contained in the methods that operate on data storage. The data structure of user tables is standardized so one day when ASP.NET Core is updated it will be easy to reuse existing tables or run a migration to another storage if needed.

This is opposite to a custom user store where the data structure is custom so manual migration must be performed. Sometimes migration is not even possible because the password hashing algorithm used in the old and new store is different and there is no way to extract the plain text password from hashes and re-hash it. Business logic contained in methods for the ASP.NET Identity user store is tested, security standards are validated and are pretty much stable and secure as you can get it. It doesn’t cover all scenarios but it’s really easy to extend it as shown in one of my next tutorials where we will add a custom property to a user.

 

Why do we even need ASP.NET Core Identity?

We need it because IdentityServer4 doesn’t care about the users. It handles token generation, token endpoints, discovery endpoint, OAuth2 and OIDC protocols, clients, scopes, all the important bits except for the users. In order to get our Identity Server to start caring about the users (local and external), we should provide it with a user store.

ASP.NET Identity is a good match as it’s a mature system for user management that is used by all ASP.NET applications, .net core and .net framework. You can use ASP.NET Core Identity without IdentityServer4 to authenticate single application but you lose the ability to create an Identity Provider (IdP) which is a whole point of these tutorials to have an SSO provider for all apps and not redo the auth for each app individually.

 

Code changes in order to implement ASP.NET Core Identity

Note: I will continue on the previous “02. IdentityServer4 EntityFramework” tutorial using the MSSQL server. For good people following the PostgreSQL tutorial all the steps that will be laid out in this tutorial are pretty much the same except you will use “UseNpgsql” instead of “UseSqlServer” extension method.

Note 2: When we start doing scaffolding some CSS styles are going to be overwritten that will make IdentityServer4 look a bit weird and out of place. I suggest keeping a copy of “wwwroot/css” folder so you can overwrite the changes later on. Specific files that we need are “site.less”, “site.css” and “site.min.css”.

 

Let’s start by adding a NuGet package for IdentityServer4 ASP.NET Core Identity support. The package name is “IdentityServer4.AspNetIdentity”.

 

Now we will add the ApplicationUser class that will inherit from the IdentityUser class. The reason for this is to be able to extend IdentityUser in the future (add additional properties to the user entity). In future tutorials, I will add an example of how to add custom user properties. For now, we will just create a new folder called “Models” and add “ApplicationUser.cs” file to hold the class definition like so

using Microsoft.AspNetCore.Identity;

namespace IdentityServer.Models
{
	public class ApplicationUser : IdentityUser
	{
	
	}
}

 

The next step will scaffold MVC controllers and views for ASP.NET Identity. This step is not mandatory as the same functionality can be obtained by just using the NuGet package for ASP.NET Core Identity (“Microsoft.AspNetCore.Identity”). The difference is that scaffolding these resources in our project directly will allow for easy modification of the look and feel of ASP.NET Core Identity.

 

In Solution Explorer right-click on “Identity Server” project → Add → New Scaffolded Item

 

The “Add Scaffold” dialog should pop-up, select “Identity” and click “Add”

 

The “Add Identity” dialog will pop-up. Select an existing Layout.cshtml file, in our project the location is “~/Views/Shared/_Layout.cshtml”. Click on “Override all files” checkbox to select all files. We need to create a new data context class, let’s call it “IdentityServer.Models.IdentityDbContext”. We will also specify the user class, so let’s use the “ApplicationUser” we added in the previous step.

 

Wait a bit for Visual Studio to do its magic and you should see a new folder called “Areas” containing all juicy ASP.NET Core views and controllers for us to play with.

 

Let’s just reorganize a bit for easier maintenance in the future. Move (drag and drop) “IdentityDbContext.cs” file from “Areas/Identity/Data” into the “Data” folder in the project root. You can now delete the “Areas/Identity/Data” folder.

We will also simplify lambda expression in “Areas/Identity/IdentityHostingStartup.cs” as we will move that configuration into our project’s Startup in the next step. Better to keep all config as visible as possible and in one place. So open up “Areas/Identity/IdentityHostingStartup.cs” and leave only the Configure method like so

using Microsoft.AspNetCore.Hosting;
 
[assembly: HostingStartup(typeof(IdentityServer.Areas.Identity.IdentityHostingStartup))]

namespace IdentityServer.Areas.Identity
{
	public class IdentityHostingStartup : IHostingStartup
	{
		public void Configure(IWebHostBuilder builder)
		{
			builder.ConfigureServices((context, services) => {
			
			});
		}
	}
}

 

Last thing to remove is “Quickstart/TestUsers.cs” file that contains TestUsers class that initializes dummy users. We don’t need it anymore as when we finish all code changes the users will come from user store. Feel free to delete the “Quickstart/TestUsers.cs” file now.

Refactoring time. Open “Startup.cs” to add missing bits for ASP.NET Core Identity.

Add missing “using” directives

using IdentityServer.Models;
using Microsoft.AspNetCore.Identity;

 

Add code snippet for IdentityDbContext and ASP.NET Core Identity service (add it above the IdentityServer service configuration, above the “AddIdentityServer” extension method)

services.AddDbContext<IdentityDbContext>(options =>
	options.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly))
);

services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
	options.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<IdentityDbContext>()
.AddDefaultTokenProviders();

 

Last thing to add in Startup is to add ASP.NET Core Identity to IdentityService service configuration (after the “AddOperationStore”)

.AddAspNetIdentity<ApplicationUser>();

 

This is the complete ConfigureServices method body with ASP.NET Core Identity changes from above

public void ConfigureServices(IServiceCollection services)
{
	// uncomment, if you wan to add an MVC-based UI
	services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1);

	string connectionString = Configuration.GetConnectionString("DefaultConnection");
	var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

	services.AddDbContext<IdentityDbContext>(options =>
		options.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly))
	);

	services.AddIdentity<ApplicationUser, IdentityRole>(options =>
	{
		options.SignIn.RequireConfirmedEmail = true;
	})
	.AddEntityFrameworkStores<IdentityDbContext>()
	.AddDefaultTokenProviders();

	var builder = services.AddIdentityServer(options =>
	{
		options.Events.RaiseErrorEvents = true;
		options.Events.RaiseInformationEvents = true;
		options.Events.RaiseFailureEvents = true;
		options.Events.RaiseSuccessEvents = true;
		options.UserInteraction.LoginUrl = "/Account/Login";
		options.UserInteraction.LogoutUrl = "/Account/Logout";
		options.Authentication = new AuthenticationOptions()
		{
			CookieLifetime = TimeSpan.FromHours(10), // ID server cookie timeout set to 10 hours
			CookieSlidingExpiration = true
		};
	})
	.AddConfigurationStore(options =>
	{
		options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
	})
	.AddOperationalStore(options =>
	{
		options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
		options.EnableTokenCleanup = true;
	})
	.AddAspNetIdentity<ApplicationUser>();

	if (Environment.IsDevelopment())
	{
		builder.AddDeveloperSigningCredential();
	}
	else
	{
		throw new Exception("need to configure key material");
	}
}

 

If you try to build the solution now you will get two errors one in “AccountController” and another in “ExternalController”. This is because we removed the “TestUsers” class and we need to update local login and external login controllers to use the ASP.NET Core Identity user store.

“Quickstart/Account/AccountController.cs” changes

Remove obsolete “using” directive

using IdentityServer4.Test;

 

Add missing “using” directives

using IdentityServer.Models;
using Microsoft.AspNetCore.Identity;

 

Remove obsolete variable

private readonly TestUserStore _users;

 

Add private variables to hold injected references of UserManager and SignInManager

private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;

 

Replace the constructor with code below

public AccountController(
	UserManager<ApplicationUser> userManager,
	SignInManager<ApplicationUser> signInManager,
	IIdentityServerInteractionService interaction,
	IClientStore clientStore,
	IAuthenticationSchemeProvider schemeProvider,
	IEventService events)
{
	_userManager = userManager;
	_signInManager = signInManager;
	_interaction = interaction;
	_clientStore = clientStore;
	_schemeProvider = schemeProvider;
	_events = events;
}

 

Replace second “Login” method with code below. This change will remove code that used the “TestUsers” class and use UserManager and SignInManager instead.

public async Task<IActionResult> Login(LoginInputModel model, string button)
{
	// check if we are in the context of an authorization request
	var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);

	// the user clicked the "cancel" button
	if (button != "login")
	{
		if (context != null)
		{
			// if the user cancels, send a result back into IdentityServer as if they 
			// denied the consent (even if this client does not require consent).
			// this will send back an access denied OIDC error response to the client.
			await _interaction.GrantConsentAsync(context, ConsentResponse.Denied);

			// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
			if (await _clientStore.IsPkceClientAsync(context.ClientId))
			{
				// if the client is PKCE then we assume it's native, so this change in how to
				// return the response is for better UX for the end user.
				return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
			}

			return Redirect(model.ReturnUrl);
		}
		else
		{
			// since we don't have a valid context, then we just go back to the home page
			return Redirect("~/");
		}
	}

	if (ModelState.IsValid)
	{
		var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberLogin, lockoutOnFailure: true);
		if (result.Succeeded)
		{
			var user = await _userManager.FindByNameAsync(model.Username);
			await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName));

			if (context != null)
			{
				if (await _clientStore.IsPkceClientAsync(context.ClientId))
				{
					// if the client is PKCE then we assume it's native, so this change in how to
					// return the response is for better UX for the end user.
					return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
				}

				// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
				return Redirect(model.ReturnUrl);
			}

			// request for a local page
			if (Url.IsLocalUrl(model.ReturnUrl))
			{
				return Redirect(model.ReturnUrl);
			}
			else if (string.IsNullOrEmpty(model.ReturnUrl))
			{
				return Redirect("~/");
			}
			else
			{
				// user might have clicked on a malicious link - should be logged
				throw new Exception("invalid return URL");
			}
		}

		await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials"));
		ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
	}

	// something went wrong, show form with error
	var vm = await BuildLoginViewModelAsync(model);
	return View(vm);
}

 

Replace the second “Logout” method with code below. This change will use SignInManager for logout.

public async Task<IActionResult> Logout(LogoutInputModel model)
{
	// build a model so the logged out page knows what to display
	var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);

	if (User?.Identity.IsAuthenticated == true)
	{
		// delete local authentication cookie
		await _signInManager.SignOutAsync();

		// raise the logout event
		await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
	}

	// check if we need to trigger sign-out at an upstream identity provider
	if (vm.TriggerExternalSignout)
	{
		// build a return URL so the upstream provider will redirect back
		// to us after the user has logged out. this allows us to then
		// complete our single sign-out processing.
		string url = Url.Action("Logout", new { logoutId = vm.LogoutId });

		// this triggers a redirect to the external provider for sign-out
		return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
	}

	return View("LoggedOut", vm);
}

 

Last change in AccountController is to replace the “BuildLoginViewModelAsync” with the code below.

private async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl)
{
	var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
	if (context?.IdP != null)
	{
		// this is meant to short circuit the UI and only trigger the one external IdP
		return new LoginViewModel
		{
			EnableLocalLogin = false,
			ReturnUrl = returnUrl,
			Username = context?.LoginHint,
			ExternalProviders = new ExternalProvider[] { new ExternalProvider { AuthenticationScheme = context.IdP } }
		};
	}

	var schemes = await _schemeProvider.GetAllSchemesAsync();

	var providers = schemes
		.Where(x => x.DisplayName != null ||
					(x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase))
		)
		.Select(x => new ExternalProvider
		{
			DisplayName = x.DisplayName,
			AuthenticationScheme = x.Name
		}).ToList();

	var allowLocal = true;
	if (context?.ClientId != null)
	{
		var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId);
		if (client != null)
		{
			allowLocal = client.EnableLocalLogin;

			if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any())
			{
				providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
			}
		}
	}

	return new LoginViewModel
	{
		AllowRememberLogin = AccountOptions.AllowRememberLogin,
		EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin,
		ReturnUrl = returnUrl,
		Username = context?.LoginHint,
		ExternalProviders = providers.ToArray()
	};
}

 

AccountController is done. This will take care of the local login to use the ASP.NET Core Identity user store. Let’s work on the ExternalController now. An external controller handles external logins (from Okta, Google, Azure AD, etc.)

“Quickstart/Account/ExternalController.cs” changes

Remove obsolete “using” directive

using IdentityServer4.Test;

 

Add missing “using” directives

using IdentityServer.Models;
using Microsoft.AspNetCore.Identity;

 

Remove obsolete variable

private readonly TestUserStore _users;

 

Add private variables to hold injected references of UserManager and SignInManager

private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;

 

Replace the constructor with code below

public ExternalController(
	UserManager<ApplicationUser> userManager,
	SignInManager<ApplicationUser> signInManager,
	IIdentityServerInteractionService interaction,
	IClientStore clientStore,
	IEventService events)
{
	_userManager = userManager;
	_signInManager = signInManager;
	_interaction = interaction;
	_clientStore = clientStore;
	_events = events;
}

 

Replace the “Callback” method with code below. This will help us split up functionality across several methods.

public async Task<IActionResult> Callback()
{
	// read external identity from the temporary cookie
	var result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);
	if (result?.Succeeded != true)
	{
		throw new Exception("External authentication error");
	}

	// lookup our user and external provider info
	var (user, provider, providerUserId, claims) = await FindUserFromExternalProviderAsync(result);
	if (user == null)
	{
		// this might be where you might initiate a custom workflow for user registration
		// in this sample we don't show how that would be done, as our sample implementation
		// simply auto-provisions new external user
		user = await AutoProvisionUserAsync(provider, providerUserId, claims);
	}

	// this allows us to collect any additonal claims or properties
	// for the specific prtotocols used and store them in the local auth cookie.
	// this is typically used to store data needed for signout from those protocols.
	var additionalLocalClaims = new List<Claim>();
	var localSignInProps = new AuthenticationProperties();
	ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps);
	ProcessLoginCallbackForWsFed(result, additionalLocalClaims, localSignInProps);
	ProcessLoginCallbackForSaml2p(result, additionalLocalClaims, localSignInProps);

	// issue authentication cookie for user
	// we must issue the cookie maually, and can't use the SignInManager because
	// it doesn't expose an API to issue additional claims from the login workflow
	var principal = await _signInManager.CreateUserPrincipalAsync(user);
	additionalLocalClaims.AddRange(principal.Claims);
	var name = principal.FindFirst(JwtClaimTypes.Name)?.Value ?? user.Id;
	await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.Id, name));
	await HttpContext.SignInAsync(user.Id, name, provider, localSignInProps, additionalLocalClaims.ToArray());

	// delete temporary cookie used during external authentication
	await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

	// validate return URL and redirect back to authorization endpoint or a local page
	var returnUrl = result.Properties.Items["returnUrl"];
	if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl))
	{
		return Redirect(returnUrl);
	}

	return Redirect("~/");
}

 

Replace the “FindUserFromExternalProviderAsync” method with code below. It will add couple more ways to match local user with the external user using different claims like name, email, preferred name.

private async Task<(ApplicationUser user, string provider, string providerUserId, IEnumerable<Claim> claims)>
	FindUserFromExternalProviderAsync(AuthenticateResult result)
{
	var externalUser = result.Principal;

	// try to determine the unique id of the external user (issued by the provider)
	// the most common claim type for that are the sub claim and the NameIdentifier
	// depending on the external provider, some other claim type might be used
	var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ??
					  externalUser.FindFirst(ClaimTypes.NameIdentifier) ??
					  throw new Exception("Unknown userid");

	// remove the user id claim so we don't include it as an extra claim if/when we provision the user
	var claims = externalUser.Claims.ToList();
	claims.Remove(userIdClaim);

	var provider = result.Properties.Items["scheme"];
	var providerUserId = userIdClaim.Value;

	// find external user
	var user = await _userManager.FindByLoginAsync(provider, providerUserId);

	// try to find user by name and/or email
	if (user == null)
	{
		var name = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Name)?.Value ?? claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value;
		if (name != null)
		{
			user = await _userManager.FindByNameAsync(name);
		}
		if (user == null)
		{
			var prefname = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.PreferredUserName)?.Value;
			if (prefname != null)
			{
				user = await _userManager.FindByNameAsync(prefname);
			}
		}
		if (user == null)
		{
			var email = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Email)?.Value ?? claims.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value;
			if (email != null)
			{
				user = await _userManager.FindByEmailAsync(email);
			}
		}
		if (user != null)
		{
			var identityResult = await _userManager.AddLoginAsync(user, new UserLoginInfo(provider, providerUserId, provider));
			if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);
		}
	}

	return (user, provider, providerUserId, claims);
}

 

Finally, the last change is to replace the “AutoProvisionUser” method with the code below. This method is in charge of creating a new local user for the external user login if the local user couldn’t be found in the local user store.

private async Task<ApplicationUser> AutoProvisionUserAsync(string provider, string providerUserId, IEnumerable<Claim> claims)
{
	// create a list of claims that we want to transfer into our store
	var filtered = new List<Claim>();

	// user's display name
	var name = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Name)?.Value ??
		claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value;
	if (name != null)
	{
		filtered.Add(new Claim(JwtClaimTypes.Name, name));
	}
	else
	{
		var first = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.GivenName)?.Value ??
			claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value;
		var last = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.FamilyName)?.Value ??
			claims.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value;
		if (first != null && last != null)
		{
			filtered.Add(new Claim(JwtClaimTypes.Name, first + " " + last));
		}
		else if (first != null)
		{
			filtered.Add(new Claim(JwtClaimTypes.Name, first));
		}
		else if (last != null)
		{
			filtered.Add(new Claim(JwtClaimTypes.Name, last));
		}
	}

	// email
	var email = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Email)?.Value ??
	   claims.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value;
	if (email != null)
	{
		filtered.Add(new Claim(JwtClaimTypes.Email, email));
	}

	var user = new ApplicationUser
	{
		UserName = Guid.NewGuid().ToString(),
	};
	var identityResult = await _userManager.CreateAsync(user);
	if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);

	if (filtered.Any())
	{
		identityResult = await _userManager.AddClaimsAsync(user, filtered);
		if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);
	}

	identityResult = await _userManager.AddLoginAsync(user, new UserLoginInfo(provider, providerUserId, provider));
	if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);

	return user;
}

 

Phew. That was quite a load of work to do. ExternalController is done. The project should be able to build now as we removed all references to the “TestUsers” class.

 

More details

Let’s recap. So we removed the “TestUsers” class that contained hardcoded users. We added NuGet packages for ASP.NET Core Identity. We scaffolded views and controllers for ASP.NET Core Identity that will allow us to modify them in the future. They are responsible for a password reset, 2FA, user registration, etc so it’s a good thing to have them in solution ready for any modification needed. One important thing to notice is that we will not be using Login and Logout controller/view from ASP.NET Core Identity because we will use those provided by IdentityServer4. We actually specified this in Startup

 

options.UserInteraction.LoginUrl = "/Account/Login";
options.UserInteraction.LogoutUrl = "/Account/Logout";

 

After we scaffolded the ASP.NET Core Identity views and controllers we modified the AccountController and ExternalController for local login and external login to use ASP.NET Core instead of the hardcoded “TestUsers”. We added the service configuration needed for ASP.NET Core Identity in Startup so it will use the SQL server to store the users. In one of the future tutorials, I will show you how to extend the “ApplicationUser” and add custom properties to the user.

If you kept a copy of “wwwroot/css” folder you can now overwrite the files “site.less”, “site.css” and “site.min.css” with your backup copy to get them to the previous version. This will make IdentityServer4 look good (you might notice some weird header issues after scaffolding).

This was a PART 1 of ASP.NET Core Identity tutorial. In PART 2 we will create database migrations, run the migrations to create the database tables and explain each table, similar as we did for the IdentityServer4 but this time for ASP.NET Core Identity.

You can find the project here.

Support

For direct assistance schedule a technical meeting with Ivan to talk about your requirements. For a general overview of our services and a live demo schedule a meeting with Maja.

Comments
  • Søren says:

    The updated templates in .Net Core 3.0 fails when tying to Scaffold Identity:
    “There was en error running the selected code generator:
    ‘There was an error running the template C:\Users\xxx\.nuget\packages\microsoft.visualstudio.web.codegenerators.mvc\3.0.0\Templates\IdentityVersioned\Bootstrap3\Pages\Account\Account.ConfirmEmailChange.cs.cshtml: Template Processing failed:The explicit expression block is missing a closing “)” character. Make sure you have a matching “)” character for all the “(” characters within this block, and that none of the”)” characters are being interpreted as markup.
    The explicit expression block is missing a closing “)” character. Make sure you have a matching “)” character for all the “(” characters within this block, and that none of the”)” characters are being interpreted as markup.”

    • deblokt says:

      I would suggest trying .NET Core 3.1 LTS release and update Visual Studio 2019 to the latest version and try again. Scaffolding templates are sometimes broken out of the box 🙁

      • Frank says:

        Even with VS 2019 updated to latest version, scaffolding doesn’t work. Is there any workaround to avoid this error?

        • deblokt says:

          Yes, we just verified there is an issue with the scaffolding template in .NET Core 3.1. A workaround that works at the moment is to uncheck “Account\ConfirmEmailChange” razor page when doing scaffolding.

  • Gigante says:

    You say to replace the FindUserFromExternalProviderAsync method, but there is no such method, only FindUserFromExternalProvider. I replaced that one, and it seems to work. I’m using .NET Core 3 if that’s what causes the confusion?

  • El'dar says:

    Hm, i have scaffolded identity and complete all eight manuals and i see the cshtml forms are not equal my real interface. So the UI from libraries is still used.

    • deblokt says:

      Please compare your code to our example on GitHub (link on top and bottom of each tutorial). Seems like you missed to register something in the pipeline.

  • dharmaturtle says:

    Thanks for the explanation of the difference between ASP.NET Identity and IdentityServer 4.

  • Sándor Hatvani says:

    I’m glad to read such a comprehensive tutorial. Thank you.
    However I have a problem with the code and I cannot figure out the problem.
    Could you help me, please!
    External authentication error
    IdentityServer4Srv.ExternalController.Callback() in ExternalController.cs line 101

    As I see this is false: await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);
    I want Windows SSo or Basic authentication and authorization therefore I have put this in the Startup.cs file:
    services.Configure(iis =>
    {
    iis.AuthenticationDisplayName = “Windows”;
    iis.AutomaticAuthentication = false;
    });

    services.Configure(iis =>
    {
    iis.AuthenticationDisplayName = “Windows”;
    iis.AutomaticAuthentication = false;
    });
    Thank you in advance.

Comments are closed.