I am writing a authentication piece (.Net Core 2.1) where it can make use of either a JWT from a service provider or a token from AzureAD. I have written the following test to understand what is required , but it seems as if the first authentication provider is always hit when I test with Postman and send through a bearer token.
I have put a break point on both bearer providers events, if i call the values controller with the bearer token it hits the "first" one even though i have specified to use the AuthenticationScheme of "second"
Startup.cs
services.AddAuthentication("first")
.AddJwtBearer("first", options =>
{
options.Authority = "https://123";
options.TokenValidationParameters =
new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidAudiences = new[] { "123" }
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var accesToken = context.SecurityToken;
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var accessToken = context.Principal;
return Task.CompletedTask;
},
};
})
.AddAzureADBearer("second",
AzureADDefaults.JwtBearerAuthenticationScheme,
options => Configuration.Bind("AzureAd", options));
// Added
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme,
options =>
{
//Configuration.Bind("AzureAd", options);
//options.Authority += "/v2.0";
options.TokenValidationParameters.ValidAudiences = new[] { options.Audience, $"api://{options.Audience}" };
options.TokenValidationParameters.ValidateIssuer = true;
options.IncludeErrorDetails = true;
options.TokenValidationParameters.ValidateLifetime = false;
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var accesToken = context.SecurityToken;
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var accessToken = context.Principal;
return Task.CompletedTask;
}
};
});
ValuesController.cs
[Route("api/[controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = "second")]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
}
any help will be appreciated.
Try using policies to enable AuthenticationSchemes
We use something like this (didn't try running the code)
services
.AddPolicyScheme("first", "First policy selector", options =>
{
options.ForwardDefaultSelector = context =>
{
return "first"
};
})
.AddPolicyScheme("second", "Second policy selector", options =>
{
options.ForwardDefaultSelector = context =>
{
return "second"
};
})
.AddAuthentication("first")
.AddJwtBearer("first", options =>
{
options.Authority = "https://123";
options.TokenValidationParameters =
new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidAudiences = new[] { "123" }
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var accesToken = context.SecurityToken;
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var accessToken = context.Principal;
return Task.CompletedTask;
},
};
})
.AddAzureADBearer("second",
AzureADDefaults.JwtBearerAuthenticationScheme,
options => Configuration.Bind("AzureAd", options));
// Added
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme,
options =>
{
//Configuration.Bind("AzureAd", options);
//options.Authority += "/v2.0";
options.TokenValidationParameters.ValidAudiences = new[] { options.Audience, $"api://{options.Audience}" };
options.TokenValidationParameters.ValidateIssuer = true;
options.IncludeErrorDetails = true;
options.TokenValidationParameters.ValidateLifetime = false;
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var accesToken = context.SecurityToken;
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var accessToken = context.Principal;
return Task.CompletedTask;
}
};
});
Related
I am trying to fetch user from my backend, when I am send a request to my controller from backend them HttpContext.User is coming with data. But when I am trying to fetch user from frontend with axios, HttpContext.User is null and data is not returning.
Here is my get User method in Controller:
[HttpGet("me")]
public IActionResult GetMe()
{
var claims = HttpContext.User.Claims.Select(c => new { c.Type, c.Value }).ToList();
return Ok(claims);
}
Here is my authentication configuration in Program.cs:
builder.Services.AddAuthentication("cookie").AddCookie("cookie").AddOAuth("Discord", options =>
{
options.SignInScheme = "cookie";
options.ClientId = builder.Configuration.GetValue<string>("Discord:ClientId");
options.ClientSecret = builder.Configuration.GetValue<string>("Discord:ClientSecret");
options.AuthorizationEndpoint = "https://discord.com/oauth2/authorize";
options.TokenEndpoint = "https://discord.com/api/oauth2/token";
options.CallbackPath = "/oauth/discord-cb";
options.Scope.Add("identify");
options.SaveTokens = true;
options.UserInformationEndpoint = "https://discord.com/api/users/#me";
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "username");
options.Events.OnCreatingTicket = async ctx =>
{
var request = new HttpRequestMessage(HttpMethod.Get, ctx.Options.UserInformationEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", ctx.AccessToken);
var response = await ctx.Backchannel.SendAsync(request);
var user = await response.Content.ReadFromJsonAsync<JsonElement>();
ctx.RunClaimActions(user);
};
});
app.MapGet("/", (HttpContext ctx) =>
{
ctx.GetTokenAsync("access_token");
return ctx.User.Claims.Select(x => new { x.Type, x.Value }).ToList();
});
app.MapGet("/login", () =>
{
return Results.Challenge(new AuthenticationProperties
{
RedirectUri = "http://localhost:3000"
}, authenticationSchemes: new List<string>() { "Discord" });
});
I also set Cors Policy, and tried this:
private readonly IHttpContextAccessor _httpContextAccessor;
public AuthController(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
[HttpGet("me")]
public IActionResult GetMe()
{
var claims = _httpContextAccessor.HttpContext.User.Claims.Select(c => new { c.Type, c.Value }).ToList();
return Ok(claims);
}
Backend is running on: https://localhost:5005/ (C# Asp.Net Core)
Fronend is running on: http://localhost:3000/ (Vue.js)
When I am go to this url: https://localhost:5005/api/auth/me this is the result:
Requirement: Here I have MVC Client request IdentityServer for Id & access token using Auth-Code mechanism.
Infra: .NetCore = 3.1 & IdentityServer4 = 3.1.4
For this I have used 2 types of configurations.
Type1 - AlwaysIncludeUserClaimsInIdToken = true
Type2 - AlwaysIncludeUserClaimsInIdToken = false & options.ClaimActions.MapJsonKey() to
map the claims. & GetClaimsFromUserInfoEndpoint = true
When you see the code below, I have seggregated as 3 layers, first one is Type1 & sceond one is Type2 and common Config is for both Type1 & Type2(as it doesn't vary).
Common Configuration.
Identity Server Proj::
Program.cs //Add Claims when user is created.
public static void Main(string[] args)
{
//DI is ready when we call create host builder.
var host = CreateHostBuilder(args).Build();
// use DI to inject/seeding of new users.
using (var scope = host.Services.CreateScope())
{
var userManager = scope.ServiceProvider
.GetRequiredService<UserManager<IdentityUser>>();
userManager.CreateAsync(SeedingUsers.GetUser, "password").GetAwaiter().GetResult();
userManager.AddClaimsAsync(SeedingUsers.GetUser, new List<Claim>
{
new Claim("employee_id", "employee_id") //added employee_id as custom claim
}).GetAwaiter().GetResult();
}
host.Run();
}
startp.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(config =>
{
config.UseInMemoryDatabase("Memory");
});
services.AddIdentity<IdentityUser, IdentityRole>(config =>
{
config.Password.RequiredLength = 4;
config.Password.RequireDigit = false;
config.Password.RequireUppercase = false;
config.Password.RequireNonAlphanumeric = false;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(config =>
{
config.Cookie.Name = "Identity_Cookie"; //It holds logged-in user's data.
config.LoginPath = "/Accounts/Login";
});
services.AddIdentityServer()
.AddAspNetIdentity<IdentityUser>()
.AddInMemoryIdentityResources(MyConfiguration.GetIdentityResources)
.AddInMemoryApiResources(MyConfiguration.GetApiResources)
.AddInMemoryClients(MyConfiguration.GetClients)
.AddDeveloperSigningCredential();
services.AddControllersWithViews();
}
1. Type-1 Configuration.
Identity Server Proj::
MyConfiguration.cs //used to configure Identity.
public static class MyConfiguration
{
public static IEnumerable<Client> GetClients =>
new List<Client>
{
// Registering client for mvc
new Client
{
ClientId = "client_id_mvc",
ClientSecrets = new List<Secret>()
{
new Secret("client_secret_mvc".ToSha256())
},
RedirectUris = { "https://localhost:44362/signin-oidc" },
AllowedGrantTypes = GrantTypes.Code,
AllowedScopes = {
"Aum",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"custom.employee_profile", //custom Id-Scope
},
AlwaysIncludeUserClaimsInIdToken = true,
RequireConsent = false,
}
};
// This is used to craft the Id Token by IdentityServer.
public static IEnumerable<IdentityResource> GetIdentityResources =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource
{
Name = "custom.employee_profile",
UserClaims = {"employee_id"},
}
};
// This is used to craft the access Token by IdentityServer.
public static IEnumerable<ApiResource> GetApiResources =>
new List<ApiResource> {
new ApiResource("Aum")
};
}
MVC Client Proj::
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(config =>
{
config.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
config.DefaultChallengeScheme = "oidc";
config.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:44322/";
options.ClientId = "client_id_mvc";
options.ClientSecret = "client_secret_mvc";
options.SaveTokens = true;
options.ResponseType = "code";
options.GetClaimsFromUserInfoEndpoint = false; // process -1
//custom claims
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("custom.employee_profile"); //Requesting for custom scope.
});
services.AddControllersWithViews();
}
2. Type-2 Configuration.
Identity Server Proj::
MyConfiguration.cs //used to configure Identity.
public static class MyConfiguration
{
public static IEnumerable<Client> GetClients =>
// Registering client for mvc
new Client
{
ClientId = "client_id_mvc",
ClientSecrets = new List<Secret>()
{
new Secret("client_secret_mvc".ToSha256())
},
RedirectUris = { "https://localhost:44362/signin-oidc" },
AllowedGrantTypes = GrantTypes.Code,
AllowedScopes = {
"Aum",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"custom.employee_profile", //custom Id-Scope
},
AlwaysIncludeUserClaimsInIdToken = false, // process -2
RequireConsent = false,
}
};
// This is used to craft the Id Token by IdentityServer.
public static IEnumerable<IdentityResource> GetIdentityResources =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource
{
Name = "custom.employee_profile",
UserClaims = {"employee_id"},
},
};
// This is used to craft the access Token by IdentityServer.
public static IEnumerable<ApiResource> GetApiResources =>
new List<ApiResource> {
new ApiResource("Aum")
};
}
MVC Client Proj::
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(config =>
{
config.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
config.DefaultChallengeScheme = "oidc";
config.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:44322/";
options.ClientId = "client_id_mvc";
options.ClientSecret = "client_secret_mvc";
options.SaveTokens = true;
options.ResponseType = "code";
//used for mapping custom clian in Identity to Client.
options.ClaimActions.DeleteClaim("amr");
options.ClaimActions.DeleteClaim("s_hash");
// process -2 This will map Identity claims to current mvc client client.
options.ClaimActions.MapJsonKey("employee_code", "employee_id");
//used for round trips to the Identity, This will hit /userInfo endpoint to get Id token.
options.GetClaimsFromUserInfoEndpoint = true; // process -2
//custom claims
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("custom.employee_profile");
});
services.AddControllersWithViews();
}
Expected result:
Id_token with scope employee_id claim (scope of custom.employee_profile).
Actual:
Missing employee_id claim in Id_token
I have been trying to getting this fixed for many times already, but have no success so far. I am logging into my page and everything works fine. However every time I am reloading my page I am ending up signed out and redirected to "Login page" and have to sign in again. What is the problem? Is there is something messed up with my Coockie logic?
I have also tried to implement login.RememberMe logic, but it does not work either. I have checked that login.RememberMe returns true, but no effect.
Login method in controller:
[HttpPost]
public async Task<IActionResult> Login([FromBody] LoginModel login)
{
ApplicationUser user = await this.SignInManager.UserManager.FindByEmailAsync(login.Email);
Microsoft.AspNetCore.Identity.SignInResult result =
await this.SignInManager.PasswordSignInAsync(login.Email, login.Password, login.RememberMe, false);
if (!result.Succeeded)
{
List<string> errors = new List<string>();
errors.Add("Email and password are invalid.");
return BadRequest(new LoginResult
{
Successful = false,
Errors = errors,
});
}
IList<string> roles = await this.SignInManager.UserManager.GetRolesAsync(user);
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Name, login.Email)
};
ClaimsIdentity identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
AuthenticationProperties props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMonths(1)
};
// to register the cookie to the browser
this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, props).Wait();
foreach (string role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.Configuration["JwtSecurityKey"]));
SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
DateTime expiry = DateTime.Now.AddDays(Convert.ToInt32(this.Configuration["JwtExpiryInDays"]));
JwtSecurityToken token = new JwtSecurityToken(
this.Configuration["JwtIssuer"],
this.Configuration["JwtAudience"],
claims,
expires: expiry,
signingCredentials: creds
);
return Ok(new LoginResult
{
Successful = true,
Token = new JwtSecurityTokenHandler().WriteToken(token),
});
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSecurityKey"]))
};
})
.AddCookie(options =>
{
options.Cookie.Name = "DashCoockie";
options.LoginPath = "/login";
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = true;
options.EventsType = typeof(CookieAuthEvent);
});
services.AddScoped<CookieAuthEvent>();
services.AddAuthorization(config =>
{
config.AddPolicy(Policies.IsAdmin, Policies.IsAdminPolicy());
config.AddPolicy(Policies.IsUser, Policies.IsUserPolicy());
});
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.AddControllers();
// Instant update on runtime for development purposes
services.AddControllersWithViews().AddRazorRuntimeCompilation();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
app.UseAuthorization();
app.UseCookiePolicy();
}
CookieAuthEvent.cs:
public class CookieAuthEvent : CookieAuthenticationEvents
{
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
context.Request.HttpContext.Items.Add("ExpiresUTC", context.Properties.ExpiresUtc);
}
}
After logging in (Coockie name is the same, just hided first part as it is in the code above):
My project structure is as follows: Client, Server, Shared. I found solution by using LocalStorage and AuthService : IAuthService on Client side. So is it so that in my setup the only solution is to use LocalStorage?
public class AuthService : IAuthService
{
private readonly HttpClient _httpClient;
private readonly AuthenticationStateProvider _authenticationStateProvider;
private readonly ILocalStorageService _localStorage;
public AuthService(HttpClient httpClient,
AuthenticationStateProvider authenticationStateProvider,
ILocalStorageService localStorage)
{
_httpClient = httpClient;
_authenticationStateProvider = authenticationStateProvider;
_localStorage = localStorage;
}
public async Task<RegisterResult> Register(RegisterModel registerModel)
{
var result = await _httpClient.PostJsonAsync<RegisterResult>("api/accounts", registerModel);
return result;
}
public async Task<LoginResult> Login(LoginModel loginModel)
{
var loginAsJson = JsonSerializer.Serialize(loginModel);
var response = await _httpClient.PostAsync("api/Login", new StringContent(loginAsJson, Encoding.UTF8, "application/json"));
var loginResult = JsonSerializer.Deserialize<LoginResult>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (!response.IsSuccessStatusCode)
{
return loginResult;
}
await _localStorage.SetItemAsync("authToken", loginResult.Token);
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(loginModel.Email);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginResult.Token);
return loginResult;
}
public async Task Logout()
{
await _localStorage.RemoveItemAsync("authToken");
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsLoggedOut();
_httpClient.DefaultRequestHeaders.Authorization = null;
}
}
It seems that you didn't set the token in your request header
try add these codes in your startup class:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["access_token"];
return Task.CompletedTask;
}
};
});
In your Login Action :
Response.Cookies.Append("access_token",your JwtSecurityToken)
I have implemented Identity Server for Auth Code Flow.
What is correct way to persist the claims (in OnTicketReceived or OnTicketValidated as shown below), so that in subsequent calls
to Blazor pages, I could receive User aka ClaimPrincipal populated for
my use?
Here is code of middleware of my resource server:
public void ConfigureServices(IServiceCollection services)
{
//....
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
options.SignIn.RequireConfirmedEmail = false;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<SomeContext>()
.AddDefaultTokenProviders();
Middleware integration:
services.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
options.DefaultSignOutScheme = "oidc";
})
.AddCookie("cookie", options =>
{
options.Cookie.Name = "__Host-bff";
options.Cookie.SameSite = SameSiteMode.Strict;
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc.code";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.ResponseMode = "query";
options.GetClaimsFromUserInfoEndpoint = true;
options.MapInboundClaims = false;
options.SaveTokens = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
//Critical Parts
options.TokenValidationParameters = new()
{
NameClaimType = "name",
RoleClaimType = "role"
};
Authenticated callback from identity server is as following:
options.Events.OnTicketReceived = async n =>
{
var serviceProvider = n.HttpContext.RequestServices;
var accountService = serviceProvider.GetService<IAccountService>() ?? throw new ArgumentNullException("serviceProvider.GetService<IAccountService>()");
I tried using BlazoredSessionStorage etc. but it seems too early to
invoke that. We have to wait until OnPrerender or OnInit
I also tried CustomTokenStore. But how does the claim from cookie
come back to server?
var svc = n.HttpContext.RequestServices.GetRequiredService<IUserAccessTokenStore>();
if (n.Principal != null)
{
var userName = n.Principal.FindFirst(x => x.Type == "name")?.Value;
await accountService.UserCreateAsync(new NewAccount
{
Username = userName,
FirstName = userFirstName,
LastName = userLastName,
//ContactId = 100,
TenantId = 1
});
await (authProvider as SomeAuthenticationStateProvider).LoginAsync(new AuthenticationLogin { Username = userName }, 24 * 60);
}
};
public class CustomTokenStore : IUserAccessTokenStore
{
ConcurrentDictionary<string, UserAccessToken> _tokens = new ConcurrentDictionary<string, UserAccessToken>();
public Task ClearTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null)
{
var sub = user.FindFirst("sub").Value;
_tokens.TryRemove(sub, out _);
return Task.CompletedTask;
}
public Task<UserAccessToken> GetTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null)
{
var sub = user.FindFirst("sub").Value;
_tokens.TryGetValue(sub, out var value);
return Task.FromResult(value);
}
public Task StoreTokenAsync(ClaimsPrincipal user, string accessToken, DateTimeOffset expiration, string refreshToken = null, UserAccessTokenParameters parameters = null)
{
var sub = user.FindFirst("sub").Value;
var token = new UserAccessToken
{
AccessToken = accessToken,
Expiration = expiration,
RefreshToken = refreshToken
};
_tokens[sub] = token;
return Task.CompletedTask;
}
}
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.ClaimActions.Add(new CustomClaimsFactory(
"userName",
"XXXXX#outlook.com"
));
options.TokenValidationParameters = new TokenValidationParameters
{
// Instead of using the default validation (validating against a single issuer value, as we do in
// line of business apps), we inject our own multitenant validation logic
ValidateIssuer = false,
// If the app is meant to be accessed by entire organizations, add your issuer validation logic here.
//IssuerValidator = (issuer, securityToken, validationParameters) => {
// if (myIssuerValidationLogic(issuer)) return issuer;
//}
};
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = context =>
{
// If your authentication logic is based on users then add your logic here
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
context.Response.Redirect("/Error");
context.HandleResponse(); // Suppress the exception
return Task.CompletedTask;
},
// If your application needs to do authenticate single users, add your user validation below.
//OnTokenValidated = context =>
//{
// var claims = new List<Claim>
// {
// new Claim(ClaimTypes.Role, "superadmin")
// };
// var appIdentity = new ClaimsIdentity(claims);
// context.Principal.AddIdentity(appIdentity);
// return Task.CompletedTask;
// //return myUserValidationLogic(context.Ticket.Principal);
//}
};
});
While CustomActionFactory class is defined below
public class CustomClaimsFactory : ClaimAction
{
string _ClaimType;
string _ValueType;
public CustomClaimsFactory(string claimType, string valueType) : base(claimType, valueType)
{
_ClaimType = claimType;
_ValueType = valueType;
}
public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
{
identity.AddClaim(new Claim(_ClaimType, _ValueType, issuer));
}
}```
OnTokenValidated has a method to add the new identity you create with the claim:
OnTokenValidated = ctx =>{ ctx.Principal.AddIdentity(myNewClaim); }
I'm trying to implement jwt token in a signalr logger hub.
But somehow I keep getting an Unauthorized response
JS
let url = '/hub/log?token='+getToken();
let http = new this.$signalR.HttpConnection(url, options);
this.connection = new this.$signalR.HubConnection(http);
C# ConfigureServices
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
if (context.Request.Path.ToString().StartsWith("/hub/"))
{
context.Token = context.Request.Query["token"];
}
return Task.CompletedTask;
}
};
options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = settings.JwtValidIssuer,
ValidAudience = settings.JwtValidAudience,
IssuerSigningKey = JwtSecurityKey.Create(settings.JwtSecurityKey)
};
});
The debugger is breaking on
context.Token = context.Request.Query["token"];
and context.Token is set
C# Hub
[Authorize(Roles = "Admin")]
public class LoggerHub : Hub
{
private readonly IServerManager serverManager;
public LoggerHub(IServerManager serverManager)
{
this.serverManager = serverManager;
}
[Authorize(Roles = "Admin")]
public override Task OnConnectedAsync()
{
serverManager.Logger.Log(Shared.Logging.LogLevel.Info, "New websocket connection");
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
serverManager.Logger.Log(Shared.Logging.LogLevel.Info, "a Socket disconnected");
return base.OnDisconnectedAsync(exception);
}
}
What am i doing wrong?
thanks in advance
Personnaly I check the request type like that before set the token
o.Events = new JwtBearerEvents //For signalR
{
OnMessageReceived = context =>
{
if (context.HttpContext.WebSockets.IsWebSocketRequest || context.Request.Headers["Accept"] == "text/event-stream")
{
StringValues accessToken = context.Request.Query["token"];
if (!string.IsNullOrEmpty(accessToken))
context.Token = accessToken;
}
return Task.CompletedTask;
}
};