I've been trying to set an expiration time to my session cookie. So at first, I've configured my Cookie handler in this way:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.Cookie.Expiration = TimeSpan.FromSeconds(3600);
})
.AddOpenIdConnect(...
Although it seemed simple it is not. The cookie was always created as session (not persisted).
I've been exploring the CookieAuthenticationHandler and I found this:
protected async override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
properties = properties ?? new AuthenticationProperties();
_signInCalled = true;
// Process the request cookie to initialize members like _sessionKey.
await EnsureCookieTicket();
var cookieOptions = BuildCookieOptions();
...
}
The surprising thing is that in the method BuildCookieOptions sets to null the expiration that I have configured in the startup:
private CookieOptions BuildCookieOptions()
{
var cookieOptions = Options.Cookie.Build(Context);
// ignore the 'Expires' value as this will be computed elsewhere
cookieOptions.Expires = null;
return cookieOptions;
}
It says that the 'Expires' value will be computed elsewhere.
Finally, I have seen in other post that when using the RemoteAuthenticationHandler like the one used by OpenId, the handler triggers an OnTicketRecived event where the expiration can be set by implementing this event in the configuration of the OpenId handler:
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.ClientId = "client";
options.Events.OnTicketReceived = async (context) =>
{
context.Properties.ExpiresUtc = DateTime.UtcNow.AddSeconds(3600);
context.Properties.IsPersistent = true;
};
But, why this Expiration value is absolutely ignored? Why CookieAuthenticationOptions let me set this property but the first thing the handler does is set it to null? Does this property any use?
Or simply am I missing something?
Related
I am working with Identity and JWT auth in .NET Core 3.1. I will be adding Open Id in the future. The problem I am facing now is that I have too little control over the authentication pipeline. The pipeline makes some decisions for you, such as when to re-direct to the login. For example, for Identity authentication, when you emit a 401 status it will attempt to re-direct to /login - even if you specified a different page in the cookie configuration. Surprisingly, JWT auth has the same problem - even when the only policy supplied is the Bearer policy and the controller is tagged with the ApiController attribute.
[Authorize(AuthenticationSchemes = Schemes.Bearer)]
I want full control over the pipeline and the response. What is the best way to achieve this by utilizing as much of the built in tools as possible? I do not wish to write my own JWT validating code, rather use the validation as I configured it.
This is how I configure JWT. OnAuthenticationFailed. The callback to set the headers manually never runs.
//piranha
services.AddPiranha(options =>
{
options.UseFileStorage(naming: Piranha.Local.FileStorageNaming.UniqueFolderNames);
options.UseImageSharp();
options.UseManager();
options.UseTinyMCE();
options.UseMemoryCache();
options.UseEF<SQLiteDb>(db =>
db.UseSqlite("Filename=./Data/emurse-piranha.db"));
options.UseIdentityWithSeed<IdentitySQLiteDb>(db =>
db.UseSqlite("Filename=./Data/emurse-piranha.db"),
identityOptions =>
{
// Password settings
identityOptions.Password.RequireDigit = false;
identityOptions.Password.RequiredLength = 6;
identityOptions.Password.RequireNonAlphanumeric = false;
identityOptions.Password.RequireUppercase = false;
identityOptions.Password.RequireLowercase = false;
identityOptions.Password.RequiredUniqueChars = 1;
// Lockout settings
identityOptions.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
identityOptions.Lockout.MaxFailedAccessAttempts = 10;
identityOptions.Lockout.AllowedForNewUsers = true;
// User settings
identityOptions.User.RequireUniqueEmail = true;
},
cookieOptions =>
{
cookieOptions.Cookie.HttpOnly = true;
cookieOptions.ExpireTimeSpan = TimeSpan.FromMinutes(30);
cookieOptions.LoginPath = "/manager/login";
cookieOptions.AccessDeniedPath = "/manager/login";
cookieOptions.SlidingExpiration = true;
var defaultAction = cookieOptions.Events.OnRedirectToLogin;
cookieOptions.Events.OnRedirectToLogin = (context) =>
{
if (context.Request.Path.Value.StartsWith("/api"))
{
response.StatusCode = 401;
response.BodyWriter.WriteAsync(new ReadOnlyMemory<byte>
(Encoding.ASCII.GetBytes("unauthorized.")));
return Task.CompletedTask;
}
else
{
return defaultAction(context);
}
};
});
//turn off for prod
options.AddRazorRuntimeCompilation=true;
});
//JWT
services.AddAuthentication(Schemes.Bearer)
.AddApplicationJwt(Configuration);
public static AuthenticationBuilder AddApplicationJwt(this AuthenticationBuilder builder, IConfiguration config)
{
builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
var issuer = config["Jwt:Issuer"];
var key = config["Jwt:Key"];
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = issuer,
ValidAudience = issuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
};
//Never executes
options.Events= new JwtBearerEvents()
{
OnAuthenticationFailed = ((context) => {
response.StatusCode = 401;
response.BodyWriter.WriteAsync(new ReadOnlyMemory<byte>
(Encoding.ASCII.GetBytes("unauthorized.")));
return Task.CompletedTask;
}),
};
});
return;
}
The app is then configured using mostly default options:
app.UseAuthorization();
app.UsePiranha(options =>
{
options.UseTinyMCE();
//default Auth middleware
options.UseIdentity();
app.UseCors(x => x
.AllowAnyMethod()
.AllowAnyHeader()
.SetIsOriginAllowed(origin => true) //TODO: remove for production
.AllowCredentials());
//use Piranha manager
options.UseManager();
});
Thanks to #Xerillio, I've figured out the root cause of my problem. After digging into the source code, I had a better understanding of what the extension methods are doing under the hood.
UseAuthentication adds the Authentication middleware. But UseIdentity also does this, and UseIdentity is obsolete. Infact, ALL UseIdenitity does is call UseAuthentication.
app.UseStaticFiles();
app.UsePiranha();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UsePiranhaIdentity();
//TODO: remove for production
app.UseCors(x => x
.AllowAnyMethod()
.AllowAnyHeader()
.SetIsOriginAllowed(origin => true)
.AllowCredentials());
app.UsePiranhaManager();
app.UsePiranhaTinyMCE();
Okay, so I am working on creating an OIDC client that will also handle refresh tokens. I have made some progress, but have some questions.
Here is my ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/Login/Index";
options.Events.OnValidatePrincipal = async context => await OnValidatePrincipalAsync(context);
})
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = Configuration["auth:oidc:authority"];
options.ClientId = Configuration["auth:oidc:clientid"];
options.ClientSecret = Configuration["auth:oidc:clientsecret"];
options.ResponseType = OpenIdConnectResponseType.Code;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.UseTokenLifetime = true;
options.SignedOutRedirectUri = "https://contoso.com";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidIssuer = Configuration["auth:oidc:authority"],
ValidAudience = Configuration["auth:oidc:clientid"],
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.FromSeconds(3)
};
});
services.AddAccessTokenManagement();
services.Configure<OidcOptions>(Configuration.GetSection("oidc"));
}
Here is my OnValidatePrincipalAsync(context)
private async Task OnValidatePrincipalAsync(CookieValidatePrincipalContext context)
{
const string AccessTokenName = "access_token";
const string RefreshTokenName = "refresh_token";
const string ExpirationTokenName = "expires_at";
if (context.Principal.Identity.IsAuthenticated)
{
var exp = context.Properties.GetTokenValue(ExpirationTokenName);
var expires = DateTime.Parse(exp, CultureInfo.InvariantCulture).ToUniversalTime();
if (expires < DateTime.UtcNow)
{
// If we don't have the refresh token, then check if this client has set the
// "AllowOfflineAccess" property set in Identity Server and if we have requested
// the "OpenIdConnectScope.OfflineAccess" scope when requesting an access token.
var refreshToken = context.Properties.GetTokenValue(RefreshTokenName);
if (refreshToken == null)
{
context.RejectPrincipal();
return;
}
var cancellationToken = context.HttpContext.RequestAborted;
// Obtain the OpenIdConnect options that have been registered with the
// "AddOpenIdConnect" call. Make sure we get the same scheme that has
// been passed to the "AddOpenIdConnect" call.
//
// TODO: Cache the token client options
// The OpenId Connect configuration will not change, unless there has
// been a change to the client's settings. In that case, it is a good
// idea not to refresh and make sure the user does re-authenticate.
var serviceProvider = context.HttpContext.RequestServices;
var openIdConnectOptions = serviceProvider.GetRequiredService<IOptionsSnapshot<OpenIdConnectOptions>>().Get("OpenIdConnect");
openIdConnectOptions.Scope.Clear();
openIdConnectOptions.Scope.Add("email");
openIdConnectOptions.Scope.Add("profile");
openIdConnectOptions.Scope.Add("offline_access");
var configuration = openIdConnectOptions.Configuration ?? await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(cancellationToken).ConfigureAwait(false);
// Set the proper token client options
var tokenClientOptions = new TokenClientOptions
{
Address = configuration.TokenEndpoint,
ClientId = openIdConnectOptions.ClientId,
ClientSecret = openIdConnectOptions.ClientSecret,
};
var httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
using var httpClient = httpClientFactory.CreateClient();
var tokenClient = new TokenClient(httpClient, tokenClientOptions);
var tokenResponse = await tokenClient.RequestRefreshTokenAsync(refreshToken, cancellationToken: cancellationToken).ConfigureAwait(false);
if (tokenResponse.IsError)
{
context.RejectPrincipal();
return;
}
// Update the tokens
var expirationValue = DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn).ToString("o", CultureInfo.InvariantCulture);
context.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = RefreshTokenName, Value = tokenResponse.RefreshToken },
new AuthenticationToken { Name = AccessTokenName, Value = tokenResponse.AccessToken },
new AuthenticationToken { Name = ExpirationTokenName, Value = expirationValue }
});
// Update the cookie with the new tokens
context.ShouldRenew = true;
}
}
}
I've done some experimenting which includes not using the Configuration to get the OpenIdConnectOptions in my OnValidatePrincipal and just create a new OpenIdConnectOptions object , and I still have not been able to understand my issue.
Here are my Current Issues
First Issue
I seem to be able to successfully send a request to the token endpoint after my desired period of time (every 2 minutes and five seconds). I notice that my client application is making a request to the ?authorize endpoint of my authorization server, even though I don't believe I have it configured to do so in my OnValidatePrincipalContext fucntion. I created an all new OpenIdConnectOptions object because I thought the current configuration was triggering it.
First Question
When is this signin-oidc request triggered? I think that's what's triggering the request to my authN server's authorize endpoint. I should not have to query this endpoint if I'm doing silent refresh?
Second Issue
My authorization server is picking up the openid scope when my client makes this request:
POST https://<authorization-server>/oauth/oidc/token HTTP/1.1
Accept: application/json
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=<refresh-token>&client_id=<client-id>&client_secret=<client-secret>
But, in my OnValidatePrincipalContext function I explicitly remove the openid scope by calling
openIdConnectOptions.Scope.Clear();
openIdConnectOptions.Scope.Add("email");
openIdConnectOptions.Scope.Add("profile");
openIdConnectOptions.Scope.Add("offline_access");
Second Question
How do I properly handle the Oidc configuration middleware so that when I go to request a new refresh token the correct request is built and sent to my authN server? Am I doint the wrong kind of authentication scheme (i.e cookie vs bearer)? If I am, how can I tell?
Thank you.
When is this signin-oidc request triggered?
Its triggered by the authorization server when the user have successfully authenticated and given consent to the requested scopes. It will ask the browser to post the authorization code to this endpoint. Its typically performed done by using a self-submitting HTML form that will create a post request to this endpoint.
You should always ask for the openid scope, otherwise it won't work.
A picture showing the flow for the endpoint is:
For the second question one alternative is to take a look at the IdentityModel.AspNetCore library. This library can automatically handle the automatic renewal of the access token using the refresh token.
See also this blog post
I'm using ASP.NET Core with Identity Server and Open Id Connect as described here. I need to change the time of authentication cookie expiration when the Remember Me option is set (14 days by default). I can see that the cookie named ".AspNetCore.Identity.Application" is responsible for that. I'm trying to set the expiration like this:
.AddCookie(options =>
{
options.Cookie.Expiration = TimeSpan.FromDays(1);
options.ExpireTimeSpan = TimeSpan.FromDays(1);
})
But it affects another cookie named ".AspNetCore.Cookies" (containing the same token value), which has Session expiration and doesn't seem to do anything. All the ways to change expiration that I found modify only the ".AspNetCore.Cookies" cookie, I couldn't find any way to modify the ".AspNetCore.Identity.Application" cookie. (By the way, the services.ConfigureApplicationCookie method isn't triggered for me at all for some reason).
Could anyone please explain what is the difference between these two cookies and how can I modify the ".AspNetCore.Identity.Application" expiration?
My code in Startup.ConfigureServices
services.AddMvc(options =>
{
// ...
})
services.AddAuthorization(options =>
{
options.AddPolicy(PolicyNames.UserPolicy, policyBuilder =>
{
// ...
});
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(options =>
{
options.AccessDeniedPath = "/AccessDenied";
options.SlidingExpiration = true;
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "<authority>";
options.RequireHttpsMetadata = false;
options.ClientId = "<id>";
options.ClientSecret = "<secret>";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
// ...
});
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = "MyCookie";
options.Cookie.Expiration = TimeSpan.FromDays(1);
options.ExpireTimeSpan = TimeSpan.FromDays(1);
});
As Kirk Larkin said ".AspNetCore.Identity.Application" cookie is probably set by the Identity Server application that make use of Asp.Net Identity.
So if you want to manage the user session on the IS4 app you need to configure it there.
IS4 application: ".AspNetCore.Identity.Application" cookie.
If you use Identity to configure the cookie as persistent you need to set the expiration when you sign in the user.
var props = new AuthenticationProperties {
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
await HttpContext.SignInAsync(userId, userName, props);
If you don't set IsPersistent=true then the cookie has session lifetime and you can set the contained authentication ticket expiration like this:
.AddCookie(options => {
options.Cookie.Name = "idsrv_identity";
options.ExpireTimeSpan = TimeSpan.FromHours(8);
options.SlidingExpiration = true;
});
Your client application: : ".AspNetCore.Cookies" cookie.
services.ConfigureApplicationCookie isn't called because if you use .AddCookie(...) this takes the precedence. The options are the same.
This set the app cookie as session.
.AddCookie(options => {
options.Cookie.Name = "myappcookie";
options.ExpireTimeSpan = TimeSpan.FromHours(8);
options.SlidingExpiration = true;
});
A way to make the app cookie persistent using OIDC is to set the expiration in the OnSigningIn event in AddCookie.
options.Events.OnSigningIn = (context) =>
{
context.CookieOptions.Expires = DateTimeOffset.UtcNow.AddDays(30);
return Task.CompletedTask;
};
A note about user session.
Every situation is different, so there isn't a best solution, but remember that you have to take care of two user session. One on the IS4 app and one on your client app. These can go out of sync. You need to think if a persistent user session on your client app make sense. You don't want that your user remains logged in your client app when the central SSO (single sign-on) session is expired.
After scrambled through the both AspNetCore 3.1 & IdentityServer 4.0.4 repo,
I found the working way to set default authentication cookie option .
TD;LR:
// in Startup.ConfigureService(IServiceCollection services)
services.PostConfigure<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme, option =>
{
option.Cookie.Name = "Hello"; // change cookie name
option.ExpireTimeSpan = TimeSpan.FromSeconds(30); // change cookie expire time span
});
Full Setup:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
// cookie policy to deal with temporary browser incompatibilities
services.AddSameSiteCookiePolicy();
services.AddDefaultAllowAllCors();
// setting up dbcontext for stores;
services.AddDbContext<ApplicationDbContext>(ConfigureDbContext);
services
.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
// read clients from https://stackoverflow.com/a/54892390/4927172
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseSuccessEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.UserInteraction.LoginUrl = "/identity/account/login";
options.IssuerUri = _configuration.GetValue<string>("IdentityServer:IssuerUri");
})
.AddAspNetIdentity<ApplicationUser>()
.AddDeveloperSigningCredential()
.AddConfigurationStore<ApplicationConfigurationDbContext>(option => option.ConfigureDbContext = ConfigureDbContext)
.AddOperationalStore<ApplicationPersistedGrantDbContext>(option => { option.ConfigureDbContext = ConfigureDbContext; })
.AddJwtBearerClientAuthentication()
.AddProfileService<ApplicationUserProfileService>();
services.PostConfigure<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme, option =>
{
option.Cookie.Name = "Hello";
option.ExpireTimeSpan = TimeSpan.FromSeconds(30);
});
services.AddScoped<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender, EmailSender>();
services.Configure<SmsOption>(_configuration);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// use this for persisted grants store
InitializeDatabase(app);
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseDefaultAllowAllCors();
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseStatusCodePages(async context =>
{
var response = context.HttpContext.Response;
if (response.StatusCode == StatusCodes.Status401Unauthorized ||
response.StatusCode == StatusCodes.Status403Forbidden)
response.Redirect("/identity/account/login");
if (context.HttpContext.Request.Method == "Get" && response.StatusCode == StatusCodes.Status404NotFound)
{
response.Redirect("/index");
}
});
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapRazorPages();
});
}
Adding this line before services.AddAuthentication is what worked for me eventually with IS4, taken from this github issue:
services.ConfigureApplicationCookie(x =>
{
x.ExpireTimeSpan = TimeSpan.FromDays(1);
});
I followed the sample AuthSamples.Cookies of the Github aspnetcore sources.
public void ConfigureServices(IServiceCollection services)
{
...
// Example of how to customize a particular instance of cookie options and
// is able to also use other services.
services.AddSingleton<IConfigureOptions<CookieAuthenticationOptions>, ConfigureMyCookie>();
}
internal class ConfigureMyCookie : IConfigureNamedOptions<CookieAuthenticationOptions>
{
// You can inject services here
public ConfigureMyCookie()
{
}
public void Configure(string name, CookieAuthenticationOptions options)
{
// Identityserver comes with two cookies:
// Identity.Application
// Identity.External
// you can change the options here
{
options.ExpireTimeSpan = TimeSpan.FromHours(8);
}
}
public void Configure(CookieAuthenticationOptions options)
=> Configure(Options.DefaultName, options);
}
I've integrated OKTA to my ASP.Net Core application from the following tutorial:
https://developer.okta.com/quickstart/#/okta-sign-in-page/dotnet/aspnetcore
It works fine if a user is assigned to my OKTA application, but if not I get an unhandled exception in the authorization callback:
OpenIdConnectProtocolException: Message contains error:
'access_denied', error_description: 'User is not assigned to the
client application.', error_uri: 'error_uri is null'.
I would like to catch this exception and handle it gracefully.
With OpenId, you could create an OnRemoteFailure event that to deal with this, but I can't figure out how to do it with the Okta.AspNetCore library.
The default schema of Okta.AspNetCore is OpenIdConnectDefaults.AuthenticationScheme :
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
namespace Okta.AspNetCore
{
public static class OktaDefaults
{
public const string MvcAuthenticationScheme = OpenIdConnectDefaults.AuthenticationScheme;
public const string ApiAuthenticationScheme = JwtBearerDefaults.AuthenticationScheme;
public const string CallbackPath = "/authorization-code/callback";
public const string SignOutCallbackPath = "/signout/callback";
public static readonly string[] Scope = new string[] { "openid", "profile" };
}
}
So you can configure the OpenIdConnectOptions by using the scheme names from above, including access to OpenIdConnectEvents:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OktaDefaults.MvcAuthenticationScheme;
})
.AddCookie()
.AddOktaMvc(new OktaMvcOptions
{
// Replace these values with your Okta configuration
OktaDomain = Configuration.GetValue<string>("Okta:OktaDomain"),
ClientId = Configuration.GetValue<string>("Okta:ClientId"),
ClientSecret = Configuration.GetValue<string>("Okta:ClientSecret"),
});
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = async ctxt =>
{
// Invoked before redirecting to the identity provider to authenticate. This can be used to set ProtocolMessage.State
// that will be persisted through the authentication process. The ProtocolMessage can also be used to add or customize
// parameters sent to the identity provider.
await Task.Yield();
},
OnMessageReceived = async ctxt =>
{
// Invoked when a protocol message is first received.
await Task.Yield();
},
OnTicketReceived = async ctxt =>
{
// Invoked after the remote ticket has been received.
// Can be used to modify the Principal before it is passed to the Cookie scheme for sign-in.
// This example removes all 'groups' claims from the Principal (assuming the AAD app has been configured
// with "groupMembershipClaims": "SecurityGroup"). Group memberships can be checked here and turned into
// roles, to be persisted in the cookie.
await Task.Yield();
},
OnRemoteFailure = context =>
{
..........
context.HandleResponse();
context.Response.Redirect("AccessDenied?error=" + context.Failure.Message);
return Task.FromResult(0);
},
};
});
cfg.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/sas")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
in my hub i tried to get claims like that
Context.User.Claims
but theyre empty
so is there any option to get my JWT claims in my signalr hub?
I had a similar issue and I've solved it by replacing this:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
with this:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
For some reason, I had to explicitly define the DefaultAuthenticateScheme. I still don't know why the DefaultAuthenticateScheme didn't fallback automatically to the DefaultScheme as it should according to the documentation. If I find out the reason, I'll update the answer.