InProc Session for ASP.NET CORE MVC not working on IIS - session

I am looking for help and advice regarding session state(InProc) is null when asp.net core mvc 6.0 is hosted on IIS.
I have read a number of blogs but those solutions suggested do not seem to be working to me.
Session state working fine when my web app is launched with ipaddress + port(other than 80) but broken when browse with registered domain name.
What mistake I have done?
I have attach my codes for your reference. Do let me know if you need further info.
// Program.cs
builder.Services.ConfigureAntiforgeryOptions();
builder.Services.AddControllersWithViews()
.AddCookieTempDataProvider();
builder.Services.ConfigureIocScanOptions();
builder.Services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(10);
options.Cookie.Name = ".WebApp.Session";
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseSession();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Auth}/{action=Index}/{id?}");
app.Run();
// Extension to set and get session variable value
public static class SessionExtensions
{
public static void Set<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonSerializer.Serialize(value));
}
public static T? Get<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default : JsonSerializer.Deserialize<T>(value);
}
}

Related

What am I missing? ASP.NET Core 6 keycloak integration, authentication fails after successful login

Here is what I did: using my local keycloak server (thru docker), I created a realm, users, role and client with this setup :
I set up credentials and got secret key and stuff and that's it, I haven't set anything, no mappers, client scope, etc.
I did this as our other applications that is using other languages such as PHP or nodejs have similar settings.
services.AddAuthentication(options =>
{
//Sets cookie authentication scheme
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(cookie =>
{
//Sets the cookie name and maxage, so the cookie is invalidated.
cookie.Cookie.Name = "keycloak.cookie";
cookie.Cookie.MaxAge = TimeSpan.FromMinutes(60);
cookie.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
cookie.SlidingExpiration = true;
})
.AddOpenIdConnect(options =>
{
//Use default signin scheme
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
//Keycloak server
options.Authority = Configuration.GetSection("Keycloak")["ServerRealm"];
//Keycloak client ID
options.ClientId = Configuration.GetSection("Keycloak")["ClientId"];
//Keycloak client secret
options.ClientSecret = Configuration.GetSection("Keycloak")["ClientSecret"];
//Keycloak .wellknown config origin to fetch config
// options.MetadataAddress = Configuration.GetSection("Keycloak")["Metadata"];
//Require keycloak to use SSL
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
//Save the token
options.SaveTokens = true;
//Token response type, will sometimes need to be changed to IdToken, depending on config.
options.ResponseType = OpenIdConnectResponseType.Code;
//SameSite is needed for Chrome/Firefox, as they will give http error 500 back, if not set to unspecified.
options.NonceCookie.SameSite = SameSiteMode.None;
options.CorrelationCookie.SameSite = SameSiteMode.None;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "https://schemas.scopic.com/roles"
};
Configuration.Bind("<Json Config Filter>", options);
options.Events.OnRedirectToIdentityProvider = async context =>
{
context.ProtocolMessage.RedirectUri = "http://localhost:13636/home";
await Task.FromResult(0);
};
});
Then I created a fresh ASP.NET Core MVC application and setup the OpenId options like so
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
My HomeController looks like this:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
bool value = User.Identity.IsAuthenticated;
return View();
}
[Authorize]
public IActionResult Privacy()
{
return View();
}
}
When I access localhost:13636/Privacy to test, the Keycloak login page is triggered which is correct, but after successful login and a redirect to /home, User.Identity.IsAuthenticated is false and it seems like the application doesn't know that authentication has been successful.
What needs to be done after this?
Or am I missing some configuration/settings/options?
Summary of what I did
Setup keycloak dashboard (created Realm, client, user and roles)
Setup a simple ASP.NET Core MVC application, pass openid options and controller.
The keycloak login page is triggered but authentication fail during test
Try to add to the AddCookie handler the following setting:
options.Cookie.SameSite = SameSiteMode.None;
To make sure the cookies are set with SameSite=none.

SignOut does not redirect to site home page

I'm trying to setup an ASP.net Core 3 MVC app that uses OIDC to connect to my company's SSO portal (OpenAM).
I used Visual Studio 2019 project generator to create a basic app with no authentication and then I added the OIDC client capabilities following the steps at http://docs.identityserver.io/en/latest/quickstarts/2_interactive_aspnetcore.html#creating-an-mvc-client .
Logging in works great with minimal changes to the Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// Setup Identity Server client
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://mycompany.com/ssoservice/oauth2";
options.RequireHttpsMetadata = false;
options.ClientId = "openIdClient";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.ProtocolValidator.RequireNonce = false;
options.SaveTokens = true;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
IdentityModelEventSource.ShowPII = true;
}
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.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
// endpoints.MapDefaultControllerRoute();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
I also set up a Logout controller action:
[Authorize]
public IActionResult Logout()
{
return SignOut("Cookies", "oidc");
}
The action actually works, i.e. when activated the cookie is deleted and the user is logged out from the SSO portal, but when the browser redirects to the /signout-callback-oidc endpoint it receives an HTTP 200 response without any content. I would have expected to have it automatically redirect to the site home page "/", which is the default value of the OpenIdConnectOptions.SignedOutRedirectUri property.
What am I missing?
Ok, after fiddling some more time, I found out this is the result of a missing draft implementation in the latest community OpenAM release (and also in the current paid ForgeRock AM, but they are working on it: https://bugster.forgerock.org/jira/browse/OPENAM-13831). Basically, the .net core handler for /signout-callback-oidc relies on having the state parameter available in order to redirect, like Ruard van Elburg mentioned in the comments:
https://github.com/aspnet/AspNetCore/blob/4fa5a228cfeb52926b30a2741b99112a64454b36/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs#L312-L315
OpenAM does not send back the state parameter, as reported in my logs. Therefore, we need to perform the redirect ourselves - the most straightforward way seems to be using the OnSignedOutCallbackRedirect event:
Startup.cs
services.AddAuthentication(...)
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
...
options.Events.OnSignedOutCallbackRedirect += context =>
{
context.Response.Redirect(context.Options.SignedOutRedirectUri);
context.HandleResponse();
return Task.CompletedTask;
};
...
});
Thanks to all the users that replied to the discussion, your contributions allowed me to find the clues to the correct solution.
you return SignOut,
instead, SignOut user and return RedirectToAction("Home","Index")

Browser back button does not execute the controller method

I am working in asp.net core. I am facing an issue which is when I am returning to last visited web page through the browser back button, my controller action method is not being executed.
When we press the back button, the browser fetches data from the cache. So, if we want to execute the controller action method, we need to prevent the browser from caching that page.
I googled a lot about this. Through this, I found a lot of solution based on the cache in ASP.NET MVC. Like, disabling cache.
I checked this site and tried also. https://learn.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-2.2
. It's not working.
We are performing some actions based on the cookies. So disabling cache, should not clear this also.
Is there any another way in ASP.NET Core to execute the controller action method when press browser back button?
Thanks in advance.
You should be careful while using no-cache. For Caching, it plays import role in performance.
If you want to set specific controller action with no-cache, you could follow:
configure CacheProfiles in Startup.cs
services.AddMvc(options =>
{
options.CacheProfiles.Add("Never",
new CacheProfile()
{
Location = ResponseCacheLocation.None,
NoStore = true
});
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Useage
[ResponseCache(CacheProfileName = "Never")]
public IActionResult Index()
{
return View();
}
If you insist on no cache for all requests, try middleware.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Use(async (context, next) =>
{
context.Response.OnStarting(() =>
{
if (context.Response.Headers.ContainsKey("Cache-Control"))
{
context.Response.Headers["Cache-Control"] = "no-cache,no-store";
}
else
{
context.Response.Headers.Add("Cache-Control", "no-cache,no-store");
}
if (context.Response.Headers.ContainsKey("Pragma"))
{
context.Response.Headers["Pragma"] = "no-cache";
}
else
{
context.Response.Headers.Add("Pragma", "no-cache");
}
return Task.FromResult(0);
});
await next.Invoke();
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}

Asp.Net Core 2.1 WebApi returns 400 when sending request from Angular 6 WebApp to get access_token

I've got an application that is represented by asp.net core 2.1 web api on the server side and angular 6 on the client side. OpenIddict is used on the server side to support token authentication. The main problem is that when a request is sent from angular app to the server to generate or refresh access_token for a client, the server responds with the 400 (Bad Request), though when it is send from Postman everything works just fine. The Cors policy is added to allow corss-origin requests as client and server sides are placed on different ports so simple requests from angular to the server passes fine.
Here is the Startup class:
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
hostingEnvironment = env;
}
public IConfiguration Configuration { get; }
private IHostingEnvironment hostingEnvironment { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<HospitalContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
options.UseOpenIddict();
});
services.AddCors(options => options.AddPolicy("AllowLocalhost4200", builder =>
{
builder
.WithOrigins("http://localhost:4200")
.WithHeaders("Authorization", "Content-type")
.WithMethods("Get", "Post", "Put", "Delete");
}));
services.AddCustomIdentity();
services.AddCustomOpenIddict(hostingEnvironment);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseCors("AllowLocalhost4200");
app.UseAuthentication();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseMvc();
app.InitilizeDb();
}
}
and AddCustomOpenIddict method which is in ConfigureServices method if someone needs to see the configuration:
public static IServiceCollection AddCustomOpenIddict(this IServiceCollection services,
IHostingEnvironment env)
{
services.AddOpenIddict(options =>
{
options.AddEntityFrameworkCoreStores<HospitalContext>();
options.AddMvcBinders();
options.EnableTokenEndpoint("/connect/token");
options.EnableAuthorizationEndpoint("/connect/authorize");
options.AllowRefreshTokenFlow()
.AllowImplicitFlow();
options.SetAccessTokenLifetime(TimeSpan.FromMinutes(30));
options.SetIdentityTokenLifetime(TimeSpan.FromMinutes(30));
options.SetRefreshTokenLifetime(TimeSpan.FromMinutes(60));
if (env.IsDevelopment())
{
options.DisableHttpsRequirement();
}
options.AddEphemeralSigningKey();
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddOAuthValidation();
return services;
}
The Angular method that sends a request is:
public authorize(model: ILoginModel): Observable<Response> {
return this.http.post(`http://localhost:58300/connect/token`,
this.authService.authFormBody(model),
{headers: this.authService.authHeaders()});
}
with this.authService.authFormBody and this.authService.authHeaders:
authHeaders(): Headers {
const headers = new Headers(
{
'Content-Type': 'application/x-www-form-urlencoded'
});
return headers;
}
authFormBody(model: ILoginModel): string {
let body = '';
body += 'grant_type=password&';
body += 'username=' + model.email + '&';
body += 'password=' + model.password + '&';
body += 'scope=OpenId profile OfflineAccess Roles';
return body;
}
I'm actually new to token based authetication, so maybe there is a problem in configurations or something. Would appreciate any offers to solve a problem.
I found an error, it was just really that I removed AddPasswordFlow from my configs and left AllowRefreshTokenFlow() and AllowImplicitFlow() and was sending grant_type=password to the server that was not cofigured to accept such a grant, it was my mistake there. It is supposed to be:
services.AddOpenIddict(options =>
{
//some configs
options.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.AllowImplicitFlow();
//some configs
});
For the begining, try allowing all headers:
https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-2.1#set-the-allowed-request-headers
Later, to be precise, look at all headers your angular app is sending and allow all of them in your cors policy. To start with, allow application-x-www-form-urlencoded
There seems to be a typo in your authFormBody method:
body += 'grant_type=password$';
This should be written as:
body += 'grant_type=password&';

Openiddict multiple refresh tokens

How do you create custom provider for openiddict in Asp.net core to allow multiple refresh tokens? This way if the user logs in from their computer and then goes home and logs in on their phone, they don't have to login each time they get on to a different device. The app.UseOAuthValidation() runs in the background before the authorize controller ever gets called so there is no handle to verify if more than 1 refresh token matches. Another issue is that I am using this:
services.AddDbContext<ApplicationDbContext>(options => {
options.UseMySql(Configuration.GetConnectionString("DefaultConnection"))
.UseOpenIddict();
});
So I do not have access to the openiddict tables via DbContext to do this manually.
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using DPInventoryPOAPI.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using OpenIddict.Core;
using OpenIddict.Models;
using System.Threading;
using System.Linq;
namespace DPInventoryPOAPI
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials() );
});
services.AddMvc();
services.AddDbContext<ApplicationDbContext>(options => {
options.UseMySql(Configuration.GetConnectionString("DefaultConnection"))
.UseOpenIddict();
});
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddOpenIddict()
.AddEntityFrameworkCoreStores<ApplicationDbContext>()
.AddMvcBinders()
.EnableTokenEndpoint("/token")
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.DisableHttpsRequirement()
.AddEphemeralSigningKey();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
//app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseCors("CorsPolicy");
app.UseIdentity();
app.UseOpenIddict();
app.UseOAuthValidation();
app.UseMvcWithDefaultRoute();
//SeedDatabase(app);
}
}
}
And authorize controller
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using AuthorizationServer.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Core;
using OpenIddict.Models;
// For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
namespace AuthorizationServer.Controllers {
public class AuthorizationController : Controller {
private readonly OpenIddictApplicationManager<OpenIddictApplication> _applicationManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
public AuthorizationController(
OpenIddictApplicationManager<OpenIddictApplication> applicationManager,
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager) {
_applicationManager = applicationManager;
_signInManager = signInManager;
_userManager = userManager;
}
[HttpPost("~/connect/token"), Produces("application/json")]
public async Task<IActionResult> Exchange(OpenIdConnectRequest request) {
Debug.Assert(request.IsTokenRequest(),
"The OpenIddict binder for ASP.NET Core MVC is not registered. " +
"Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");
if (request.IsPasswordGrantType()) {
var user = await _userManager.FindByNameAsync(request.Username);
if (user == null) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid."
});
}
// Ensure the user is allowed to sign in.
if (!await _signInManager.CanSignInAsync(user)) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The specified user is not allowed to sign in."
});
}
// Reject the token request if two-factor authentication has been enabled by the user.
if (_userManager.SupportsUserTwoFactor && await _userManager.GetTwoFactorEnabledAsync(user)) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The specified user is not allowed to sign in."
});
}
// Ensure the user is not already locked out.
if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user)) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid."
});
}
// Ensure the password is valid.
if (!await _userManager.CheckPasswordAsync(user, request.Password)) {
if (_userManager.SupportsUserLockout) {
await _userManager.AccessFailedAsync(user);
}
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The username/password couple is invalid."
});
}
if (_userManager.SupportsUserLockout) {
await _userManager.ResetAccessFailedCountAsync(user);
}
// Create a new authentication ticket.
var ticket = await CreateTicketAsync(request, user);
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
else if (request.IsRefreshTokenGrantType()) {
// Retrieve the claims principal stored in the refresh token.
var info = await HttpContext.Authentication.GetAuthenticateInfoAsync(
OpenIdConnectServerDefaults.AuthenticationScheme);
// Retrieve the user profile corresponding to the refresh token.
var user = await _userManager.GetUserAsync(info.Principal);
if (user == null) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The refresh token is no longer valid."
});
}
// Ensure the user is still allowed to sign in.
if (!await _signInManager.CanSignInAsync(user)) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The user is no longer allowed to sign in."
});
}
// Create a new authentication ticket, but reuse the properties stored
// in the refresh token, including the scopes originally granted.
var ticket = await CreateTicketAsync(request, user, info.Properties);
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
ErrorDescription = "The specified grant type is not supported."
});
}
private async Task<AuthenticationTicket> CreateTicketAsync(
OpenIdConnectRequest request, ApplicationUser user,
AuthenticationProperties properties = null) {
// Create a new ClaimsPrincipal containing the claims that
// will be used to create an id_token, a token or a code.
var principal = await _signInManager.CreateUserPrincipalAsync(user);
// Note: by default, claims are NOT automatically included in the access and identity tokens.
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
// whether they should be included in access tokens, in identity tokens or in both.
foreach (var claim in principal.Claims) {
// In this sample, every claim is serialized in both the access and the identity tokens.
// In a real world application, you'd probably want to exclude confidential claims
// or apply a claims policy based on the scopes requested by the client application.
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(principal, properties,
OpenIdConnectServerDefaults.AuthenticationScheme);
if (!request.IsRefreshTokenGrantType()) {
// Set the list of scopes granted to the client application.
// Note: the offline_access scope must be granted
// to allow OpenIddict to return a refresh token.
ticket.SetScopes(new[] {
OpenIdConnectConstants.Scopes.OpenId,
OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess,
OpenIddictConstants.Scopes.Roles
}.Intersect(request.GetScopes()));
}
return ticket;
}
}
}
How do you create custom provider for openiddict in Asp.net core to allow multiple refresh tokens? This way if the user logs in from their computer and then goes home and logs in on their phone, they don't have to login each time they get on to a different device.
OTB, OpenIddict allows you to retrieve multiple (independent) refresh tokens as long as they are requested using different grant_type=password requests. In your case, if the token retrieved by the mobile app is revoked (e.g manually or because it was already used), the refresh token used by the desktop app can still be used to retrieve new access/refresh tokens.
The app.UseOAuthValidation() runs in the background before the authorize controller ever gets called so there is no handle to verify if more than 1 refresh token matches.
The validation middleware never deals with refresh tokens, as it's only responsible of validating access tokens.
So I do not have access to the openiddict tables via DbContext to do this manually.
You can add a DbSet<OpenIddictToken> property in your DbContext or retrieve the DbSet<OpenIddictToken> via context.Set<OpenIddictToken>().

Resources