ASP.NET Core 6.0 Tutorial
This tutorial demonstrates how to add user login, logout, and profile to an ASP.NET Core application.
This tutorial shows how to use PlusAuth with ASP.NET Core and Microsoft OpenIdConnect
. If you do not have a PlusAuth account, register from here.
Create PlusAuth Client
After you sign up or log in to PlusAuth, you need to create a client to get the necessary configuration keys in the dashboard.
Go to Clients and create a client with the type of Regular Web Application
Configure Client
Get Client Properties
You will need your Client Id
and Client Secret
for interacting with PlusAuth. You can retrieve them from the created client's details.
Configure Redirect and Logout URIs
When PlusAuth authenticates a user, it needs a URI to redirect back. That URI must be in your client's Redirect URI
list. If your application uses a redirect URI that is not white-listed in your PlusAuth Client, you will receive an error.
The same thing applies to the logout URIs. After the user logs out, you need a URI to be redirected.
https://localhost:7200/callback
and
the Logout URL you need to add to the Post Logout Redirect URIs field is https://localhost:7200/
.Configure ASP.NET Core to use PlusAuth
Create an ASP.NET Core application or download the sample project from the link on the top of the page.
Add Dependencies
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.1" />
</ItemGroup>
Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation
provides hot reload support for .cshtml
files.Configure ASP.NET Core Application to use PlusAuth
We will be using appsettings.Development.json
files for maintaining providing some constant values. If you are following the sample project,
rename appsettings.json
to appsettings.Development.json
and replace the values accordingly.
// appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Plusauth": {
"ClientId": "{YOUR-PLUSAUTH-CLIENT-ID}",
"ClientSecret": "{YOUR-PLUSAUTH-CLIENT-SECRET}",
"AuthUrl": "https://{YOUR-PLUSAUTH-TENANT-ID}.plusauth.com"
}
}
appsettings.Development.json
file into source control. Otherwise, your history will contain references to your client's secret.Add Authentication and OpenIdConnect Services
To enable authentication with PlusAuth, edit Program.cs
in the root and add the following section to your builder.
//Program.cs
// Add services to the container.
// Add runtime compilation for hot reload
builder.Services.AddControllersWithViews().AddRazorRuntimeCompilation();
builder.Services.AddAuthentication(options =>
{
// If an authentication cookie is present, use it to get authentication information
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie() // cookie authentication middleware first
.AddOpenIdConnect("PlusAuth", options =>
{
options.ClientId = builder.Configuration["Plusauth:ClientId"];
options.ClientSecret = builder.Configuration["Plusauth:ClientSecret"];
options.Authority = builder.Configuration["Plusauth:AuthUrl"];
options.ResponseType = "code";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("email");
options.Scope.Add("profile");
// Get user info after authentication
options.GetClaimsFromUserInfoEndpoint = true;
// This is important to get user information
// Map All user information into User Claims
ClaimActionCollectionMapExtensions.MapAll(options.ClaimActions);
// Set Authentication Issuer
options.ClaimsIssuer = "PlusAuth";
options.CallbackPath = new PathString("/callback");
options.Events = new OpenIdConnectEvents
{
// Configure post logout redirect
// This section provides logout action for PlusAuth
OnRedirectToIdentityProviderForSignOut = (context) =>
{
// Set PlusAuth logout endpoint
var logoutUri = $"{builder.Configuration["Plusauth:AuthUrl"]}/oidc/logout?client_id={builder.Configuration["Plusauth:ClientId"]}";
var postLogoutUri = context.Properties.RedirectUri;
if (!string.IsNullOrEmpty(postLogoutUri))
{
if (postLogoutUri.StartsWith("/"))
{
postLogoutUri = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase + postLogoutUri;
}
// Return index page after logout
logoutUri += $"&post_logout_redirect_uri={ Uri.EscapeDataString(postLogoutUri)}";
}
context.Response.Redirect(logoutUri);
context.HandleResponse();
return Task.CompletedTask;
}
};
});
var app = builder.Build();
//... rest of the code
app.UseAuthentication();
// Use authorization to protect endpoints
app.UseAuthorization();
app.UseCookiePolicy();
//... rest of the code
app.Run();
Authentication
and OpenIdConnect
services must be added before builder.Build()
section.OnRedirectToIdentityProviderForSignOut
event enables logout
from both PlusAuth
and application. Removing event handler disables logout from PlusAuth
.Implement login, user profile, and logout
Add Controllers
Add AccountController
to your application to enable Login
and Logout
actions.
// Controllers/AccountController.cs
public class AccountController : Controller
{
public async Task Login(string returnUrl = "/")
{
await HttpContext.ChallengeAsync("PlusAuth", new AuthenticationProperties() { RedirectUri = returnUrl });
}
[Authorize]
public async Task Logout()
{
await HttpContext.SignOutAsync("PlusAuth", new AuthenticationProperties
{
// Indicate that PlusAuth should redirect the user after logout.
RedirectUri = "/"
});
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
Add HomeController
to your application to serve HTML. Add the logged-in user's profile information to the model in the controller.
// Controllers/HomeController.cs
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
[Authorize]
public IActionResult Profile()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Create HTML Pages
Shared
Create _Layout.cshtml
under Views/Shared
folder.
<!-- Views/Shared/_Layout.cshtml -->
<body>
<header>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<div class="container-fluid">
<a class="navbar-brand" href="/">Plusauth Starter</a>
<ul class="nav navbar-nav navbar-right">
@if (User.Identity != null && User.Identity.IsAuthenticated) {
<li class="nav-item navbar-nav">
<a class="nav-link" asp-controller="Home" asp-action="Profile">
Logged in as: @User.FindFirst("username").Value
</a>
</li>
<a class="btn btn-link" asp-controller="Account" asp-action="Logout">
Logout
</a>
} else {
<li class="nav-item navbar-nav">
<a class="btn btn-link" asp-controller="Account" asp-action="Login">
Login
</a>
</li>
}
</ul>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">@RenderBody()</main>
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
Homepage
Create Index.cshtml
under Views/Home
folder.
<!-- Views/Home/Index.cshtml -->
@{ ViewData["Title"] = "Home Page"; }
<div class="jumbotron">
<div class="container">
<h1 class="display-3">Hello, world!</h1>
<p>This is a template for a simple login/register system. It includes a simple
OpenID Connect Implicit Flow. To view Profile page please login.</p>
<p>
@if (User.Identity != null && User.Identity.IsAuthenticated) {
<a class="btn btn-success btn-lg" asp-controller="Home" asp-action="Profile" role="button">
View Profile »
</a>
} else {
<a class="btn btn-primary btn-lg" asp-controller="Account" asp-action="Login" role="button">
Login/Register »
</a>
}
Login
button will be shown, else the View Profile
button is.User Profile
Create Profile.cshtml
under Views/Home
folder.
<!-- Views/Home/Profile.cshtml -->
@{
ViewData["Title"] = "Profile";
}
<div class="container">
<h3>Welcome @User.FindFirst("username").Value!</h3>
<h4>User claims:</h4>
<table style="max-width: 600px">
@foreach (var claim in User.Claims)
{
<tr>
<td style="padding: 8px">@claim.Type</td>
<td style="padding: 8px">@claim.Value</td>
</tr>
}
</table>
</div>
See it in action
That's it. Start your app and point your browser to https://localhost:7200. Follow the Log In link to log in or sign up to your PlusAuth tenant. Upon successful login or signup, you should be redirected back to the application.