Does IdentityServer4.AccessTokenValidation support validating tokens from multiple authorities? - identityserver4

Does IdentityServer4.AccessTokenValidation support authentication of multiple authorities?
Normally, I setup the trust for my own single authority like so:
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "TestApi";
});
I need to auth tokens from my own authority as well as one other trusted external authority. Not sure how to do it.

Related

Azure Active Directory with IdentityServer4 (Microsoft.AspNetCore.Identity.UI) - Step by Step Guide?

I have a .NET Core app which uses identityserver4 to authenticate users. I have integrated it with ASP.NET Identity (Microsoft.AspNetCore.Identity.UI) and this works fine. It uses the AspNetUser tables etc. to store users. etc etc and all the options work.
I would like to add the option to use Azure Active Directory users. So I add the following code to my startup class (previously there was just services.AddAuthentication();):
services.AddAuthentication()
.AddOpenIdConnect("aad", "Azure AD", options =>
{
options.Authority = "https://login.windows.net/<My Azure Tenant Guid>";
options.TokenValidationParameters =
new TokenValidationParameters { ValidateIssuer = true };
options.ClientId = "<My Azure App Client Id>";
options.CallbackPath = "/signin-aad";
options.SignedOutCallbackPath = "/signout-callback-aad";
options.RemoteSignOutPath = "/signout-aad";
options.ResponseType = OpenIdConnectResponseType.Code;
options.ClientSecret = "<My Azure App Client Secret>";
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.RequireHttpsMetadata = true;
})
;
This makes a button available to add your Azure AD account... Which doesn't work - it gets as far as asking for permission, then comes up with "Unexpected error occurred loading external login info".
Any ideas, or does anyone have a link to a good tutorial?

Using a blazor server with signalR as a relay server

The goal is to use a Blazor server as a relay server using signalR.
I have little to no experience with blazor servers before this.
The Idea would be to connect a Winform/Xamarin client to this server, target the recipient using a name/id from an existing database, and relay the necessary info.
Hub:
[Authorize]
public class ChatHub : Hub
{
public Task SendMessageAsync(string user, string message)
{
//Context.UserIdentifier
Debug.WriteLine(Context.UserIdentifier);
Debug.WriteLine(Context?.User?.Claims.FirstOrDefault());
return Clients.All.SendAsync("ReceiveMessage", user, message); ;
}
public Task DirectMessage(string user, string message)
{
return Clients.User(user).SendAsync("ReceiveMessage", user, message);
}
}
As per documentation I'm trying to set the Context.UserIdentifier, I do however struggle with the authentication part. My program.cs looks like this:
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
services.AddTransient<IUserIdProvider, MyUserIdProvider>();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
//var accessToken = context.Request.Query["access_token"];
var accessToken = context.Request.Headers["Authorization"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/chathub"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSignalR();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/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.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.MapBlazorHub();
app.MapHub<ChatHub>("/chathub");
app.MapFallbackToPage("/_Host");
app.Run();
As for my Client (a winform test client) I tried something like this:
HubConnection chatHubConnection;
chatHubConnection = new HubConnectionBuilder()
.WithUrl("https://localhost:7109/chathub", options =>
{
options.AccessTokenProvider = () => Task.FromResult(token);
})
.WithAutomaticReconnect()
.Build();
private async void HubConBtn_Click(object sender, EventArgs e)
{
chatHubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
this.Invoke(() =>
{
var newMessage = $"{user}: {message}";
MessagesLB.Items.Add(newMessage);
});
});
try
{
await chatHubConnection.StartAsync();
MessagesLB.Items.Add("Connected!");
HubConBtn.Enabled = false;
SendMessageBtn.Enabled = true;
}
catch (Exception ex)
{
MessagesLB.Items.Add(ex.Message);
}
}
As a first step I'm just trying to authenticate a user/check that it's in the live database, if so connect and fill out: Context.UserIdentifier so I can use this within the Hub. I understand that I probably need a middleware however I don't really know exactly how to test a connectionId/Jwt token or similar to get the user/connection.
Any nudge in the right direction would be appreciated.
If I understand your question you don't know where and how to generate a JWT token.
For me the JWT token should be generated from the server, your hub.
POST api/auth and in the playload you give login + SHA256 password and returns JWT token.
Once you checked the user auth is correct in you DB you can issue the token.
To generate a JWT token I use this piece of code.
public string GenerateToken(IConfiguration Config, DateTime? expire)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userName),
new Claim(JwtRegisteredClaimNames.Jti, _id),
new Claim(ClaimsIdentity.DefaultRoleClaimType, role)
};
// ClaimsIdentity.DefaultRoleClaimType
var bytes = Encoding.UTF8.GetBytes(Config["jwt:Secret"]);
var key = new SymmetricSecurityKey(bytes);
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
//Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
var token = new JwtSecurityToken(
//Config.GetValue<string>("jwt:Issuer"),
//Config.GetValue<string>("jwt:Issuer") + "/ressources",
claims: claims,
expires: DateTime.Now.AddMinutes(Config.GetValue<int>("jwt:ExpireMinute")),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
#edit
Look here to allow JWT for SignalR
https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-6.0
I also added this.
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
The easiest solution would be to use something like IdentityServer to handle the authentication. It's a free solution, also .NET based which takes very little configuration effort to offer you simple client credentials authentication and generate the token for you.
I did basically exactly what you're asking here: A WinForms application connecting to my signalR hub application on a remote server, using Bearer token - but I also have OIDC/OAUTH implemented with third party user account login.
IdentityServer offers a great repository of full examples that showing you all the flow - and with just a few lines of code changed, you have a fullblown authentication system, which can be enhanced easily.
With IdentityServer you get everything, even the corresponding extension methods that enable your signalR hub application to create the claims principal (aka user) from the claims included within your token.
Here you'll find all the examples and docs:
https://github.com/IdentityServer/IdentityServer4
If you hit any walls, just reply here and I'll try to help.

How to set cookie expiration times for identity server, web api and client app?

I am new to identity server and know very little about cookie management.
I have configured a web api and a client app with IDS4. below is my startup.cs in identity server.
public void ConfigureServices(IServiceCollection services)
{
//...
var builder = services.AddIdentityServer(options =>
{
options.EmitStaticAudienceClaim = true;
options.IssuerUri = "https://localhost:5001";
})
//...
}
and here is my extension method to add authentication in web api
public static void AddCustomAuthentication(this IServiceCollection services, IConfiguration Configuration)
{
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
// tell the system base address of identity server
options.Authority = Configuration["IdentityServerUri"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
}
and here is my extension method to add authentication in client app
public static void AddCustomAuthentication(this IServiceCollection services, IConfiguration Configuration)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(options =>
{
options.Cookie.Name = "CorpLense.HR.WebClient";
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = Configuration["IdentityServerUri"];
options.SignInScheme = "Cookies";
options.ClientId = "CorpLense.HR.WebClient";
options.ClientSecret = "***";
options.ResponseType = "code";
options.SaveTokens = true;
// get claims
// request access to scopes
});
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
}
and below is a screenshot of the cookies
OBSERVATIONS
I have noticed that my client app stays logged-in all the time. it does not ask me to login even the next day. however, after a every few hours my web API throws UNAUTHORIZED response codes. And when I logout and login again from my client app, the api starts working fine again. some thing tells me that perhaps the bearer token gets expired.
OBJECTIVE
I just want to know how to have total control on cookies. I want the client app to automatically logout when the cookie on the server side has expired, or the cookie on web api side has expired, or the cookie on the client app has expired.
I also want to know how to control cookie expirations times. and how to refresh bearer tokens if they expire.
If I am not wrong, the default lifetime for the access token is 1 hour and you need to implement support for refresh token to renew the token when the access token is about to expire.
You can also configure the client cookie lifetime in AddCookie(...).
Does this help you?
see https://docs.duendesoftware.com/identityserver/v5/bff/extensibility/tokens/

How can I determine in the resource server which grant type was used by the client?

I have my Web API configured to use Identity Server 4 in a way that it uses the client claims and the user claims to implement authorization on endpoints.
The problem here is that I need to know which grant type was used by the client app to customize that authorization mechanism. For example, if the client used client credentials, then the user claims will not be available.
Is there any way to determine the grant type used by the client app, programmatically when the request hits the Web API endpoint?
The Web API setups Identity Server as follows:
services
.AddAuthorization(
(options) =>
{
options.AddPolicy(
Policies.Monitoring,
(policy) =>
{
policy.RequireClaim("scope", Policies.Scopes.Monitoring);
});
options.AddPolicy(
Policies.VatNumber,
(policy) =>
{
policy.RequireClaim("scope", Policies.Scopes.VatNumber);
policy.Requirements.Add(new ClientSubscriptionRequirement());
});
});
services.AddSingleton<IAuthorizationHandler, ClientSubscriptionAuthorizationHandler>();
services
.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(
(options) =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "datalookup";
});
I need to know the client app grant type in the ClientSubscriptionAuthorizationHandler.

Asp.net core token based claims authentication with OpenIdConnect and angularjs: Bearer was forbidden

I'm using Asp.net core rc2 with OpenIdConnectServer. I'm using angular 1.x with augular-oauth2. After a few days, my error has digressed to
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:54275/api/Account/Username
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware:Information: Successfully validated the token.
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware:Information: HttpContext.User merged via AutomaticAuthentication from authenticationScheme: Bearer.
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware:Information: AuthenticationScheme: Bearer was successfully authenticated.
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed for user: .
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Warning: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult:Information: Executing ChallengeResult with authentication schemes (Bearer).
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware:Information: AuthenticationScheme: Bearer was forbidden.
My ConfigureServices consists of
services.AddAuthorization(options =>
{
options.AddPolicy("UsersOnly", policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim("role");
});
});
My configure has
app.UseWhen(context => context.Request.Path.StartsWithSegments(new PathString("/api")), branch =>
{
branch.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
RequireHttpsMetadata = false,
Audience = "http://localhost:54275/",
Authority = "http://localhost:54275/",
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = "client1",
//ValidAudiences = new List<string> { "", "empty", "null"}
}
});
});
app.UseOpenIdConnectServer(options =>
{
options.AuthenticationScheme = OpenIdConnectServerDefaults.AuthenticationScheme;
options.Provider = new SimpleAuthorizationServerProvider();
options.AccessTokenHandler = new JwtSecurityTokenHandler();
options.ApplicationCanDisplayErrors = true;
options.AllowInsecureHttp = true;
options.TokenEndpointPath = new PathString("/oauth2/token");
options.LogoutEndpointPath = new PathString("/oauth2/logout");
options.RevocationEndpointPath = new PathString("/oauth2/revoke");
options.UseJwtTokens();
//options.AccessTokenLifetime = TimeSpan.FromHours(1);
});
My authorize attribute is defined on the Controller as
[Authorize(Policy = "UsersOnly", ActiveAuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme), Route("api/Account")]
I store the token as a cookie and attach it to requests using an http interceptor in angular.
I generate the token with
public override async Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context)
{
// validate user credentials (demo mode)
// should be stored securely (salted, hashed, iterated)
using (var con = new SqlConnection(ConnectionManager.GetDefaultConnectionString()))
{
if (!Hashing.ValidatePassword(context.Password, await con.ExecuteScalarAsync<string>("SELECT PassHash FROM dbo.Users WHERE Username = #UserName", new { context.UserName })))
{
context.Reject(
error: "bad_userpass",
description: "UserName/Password combination was invalid."
);
return;
}
// create identity
var id = new ClaimsIdentity(context.Options.AuthenticationScheme);
id.AddClaim(new Claim("sub", context.UserName));
id.AddClaim(new Claim("role", "user"));
// create metadata to pass on to refresh token provider
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{"as:client_id", context.ClientId}
});
var ticket = new AuthenticationTicket(new ClaimsPrincipal(id), props,
context.Options.AuthenticationScheme);
ticket.SetAudiences("client1");
//ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.Profile, "api-resource-controller");
context.Validate(ticket);
}
}
I've spent the last three days on this problem and I realize that at this point I'm probably missing something obvious due to lack of sleep. Any help would be appreciated.
The error you're seeing is likely caused by 2 factors:
You're not attaching an explicit destination to your custom role claim so it will never be serialized in the access token. You can find more information about this security feature on this other SO post.
policy.RequireClaim("role"); might not work OTB, as IdentityModel uses an internal mapping that converts well-known JWT claims to their ClaimTypes equivalent: here, role will be likely replaced by http://schemas.microsoft.com/ws/2008/06/identity/claims/role (ClaimTypes.Role). I'd recommend using policy.RequireRole("user") instead.
It's also worth noting that manually storing the client_id is not necessary as it's already done for you by the OpenID Connect server middleware.
You can retrieve it using ticket.GetPresenters(), that returns the list of authorized presenters (here, the client identifier). Note that it also automatically ensures a refresh token issued to a client A can't be used by a client B, so you don't have to do this check in your own code.

Resources