How to enable front-channel or back-channel logout in identityserver4 - identityserver4

I'm looking at how to disconnect the user currently logged on the mvc client (e.g. http://localhost:5001), when that user performs logout on identity server's deployment (e.g. http://localhost:5000)
I understand there's an implementation of OAuth2 in identityserver4 that does just that (https://openid.net/specs/openid-connect-backchannel-1_0.html and https://openid.net/specs/openid-connect-frontchannel-1_0.html)
Luckily for me, Brock Allen just pushed a change in the samples less than a day ago: https://github.com/IdentityServer/IdentityServer4.Samples/issues/197
However the sample is either incomplete at this point, or I'm missing something.
on my server, I'm setting the value of FrontChannelLogoutUrl to http://localhost:5001/frontchannello, and I added that piece of code to my mvc client (basically stolen from the sample):
[HttpGet("frontChannello")]
public IActionResult FrontChannelLogout(string sid)
{
if (User.Identity.IsAuthenticated)
{
var currentSid = User.FindFirst("sid")?.Value ?? "";
if (string.Equals(currentSid, sid, StringComparison.Ordinal))
{
//await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return new SignOutResult(new[] { "Cookies", "oidc" });
}
}
return NoContent();
}
That code never gets called.
So my question is: should I use backchannel or frontchannel; and, how to implement it

The Identity server 4 documentation describes well how front-channel logout should be implemented. Look for the Quickstart 8_AspnetIdentity as it provides most of the code required for the implementation.
Some highlights of the code required in the identity server :
In the AccountController.cs, the Logout function builds a LoggedOutViewModel and returns a LoggedOut view.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutInputModel model)
{
// build a model so the logged out page knows what to display
var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);
...
return View("LoggedOut", vm);
}
The SignOutIframeUrl iframe is served in the LoggedOut.cshtml.
#model LoggedOutViewModel
<div class="page-header logged-out">
<small>You are now logged out</small>
...
#if (Model.SignOutIframeUrl != null)
{
<iframe width="0" height="0" class="signout" src="#Model.SignOutIframeUrl"></iframe>
}
</div>
What remains to be done is defining the FrontChannelLogoutUri for your each of your clients. That's normally done in the identity server's config.cs
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
// resource owner password grant client
new Client
{
ClientId = "js",
ClientName = "JavaScript Client",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false,
RedirectUris = { "http://localhost:5003/callback.html" },
PostLogoutRedirectUris = { "http://localhost:5003/index.html" },
FrontChannelLogoutUri = "http://localhost:5003/frontChannello"

Ok pretty simple. In your Logout action on the account controller (in idserver), make sure you display the LoggedOut view, which in turn shows the iFrame that calls the callback on the mvc client. Pretty much what the spec are saying.

Related

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.

Overwrite default signed out page with Microsoft Identity

I've implemented Microsoft Identity platform in my Razore Pages application.
Almost everything works, except the redirect url AFTER user logout.
I let you see my configuration.
That is how I add authentication in my project:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(azureADSection)
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { scope })
.AddInMemoryTokenCaches();
An here how I add the authorization:
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
Then I want to override the default behaviour for logout:
Here my Signout button:
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignOut">Sign out</a>
Account is not a control of mine. You can find the controller here.
The logout works. The guide says:
call Signout(), which lets the OpenId connect middleware contact the Microsoft identity platform logout endpoint which:
clears the session cookie from the browser,
and finally calls back the logout URL, which, by default, displays the signed out view page >SignedOut.html also provided as part of ASP.NET Core.
In fact, I am redirected to SignedOut.html.
The guide does not explain how I can override that behavior but it gives me a tip. I have not intercepted the event how it is written in the guide, but I have overriden two properties:
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.SignedOutCallbackPath = "/test";
//options.SignedOutRedirectUri = "/test";
//options.SignedOutRedirectUri = "https://www.google.com";
});
But my solution does not works. It still redirect to default page when I am logged out. How can I customize the after logout url?
Thnak you
Please check if you can try to use custom URL Rewriting Middleware to redirect based on checking the path .Add this before app.UseMvc in startup.cs under you can redirect to your own custom signout page if you wish.
app.UseRewriter(
new RewriteOptions().Add(
context => { if (context.HttpContext.Request.Path == "/MicrosoftIdentity/Account/SignedOut")
{ context.HttpContext.Response.Redirect("/Index"); }
})
);
Or
If controller is present a workaround is to build you own AccountController :
public class AccountController : Controller
{
[HttpGet]
public IActionResult SignIn()
{
var redirectUrl = Url.Action(nameof(HomeController.Index), "Home");
return Challenge(
new AuthenticationProperties { RedirectUri = redirectUrl },
OpenIdConnectDefaults.AuthenticationScheme);
}
[HttpGet]
public IActionResult SignOut()
{
var callbackUrl = Url.Action(nameof(SignedOut), "Account", values: null, protocol: Request.Scheme);
return SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
CookieAuthenticationDefaults.AuthenticationScheme,
OpenIdConnectDefaults.AuthenticationScheme);
}
[HttpGet]
public IActionResult SignedOut()
{
if (User.Identity.IsAuthenticated)
{
// Redirect to home page if the user is authenticated.
return RedirectToAction(nameof(HomeController.Index), "Home");
}
return RedirectToAction(nameof(HomeController.Index), "ThePathtoredirect");
}
References:
customize azure ad sign out page -SO Reference
define signedout page-SO Reference
Above example will work for MicrosoftIdentity if decorated with the right route:
[Area("MicrosoftIdentity")]
[Route("[area]/[controller]/[action]")]

Identity server 4: intercept 302 and replace it with 401

I've got an app which is hosting simultaneously Identity Server 4 and a client app (Vue) which uses a couple of rest services defined in an area for managing the site. The idea is that users associated with a specific role can access the client app and call the rest services for performing the actions.
Currently, my problem is that when the api return 302 when the user doesn't belong to the admin role. I'd like to change this to a 401, but I'm having some problems with it.
If this was a simple aspnet core app, then I'd simply pass a lambda to the OnRedirectToLogin property of the cookie handler that takes care of the request. Unfortunately, IS4 will only allow me to set a couple of basic settings of the cookie (expiration and sliding). The same docs say that I can override the cookie handler. So, I've tried doing the following:
services.AddIdentityServer()
... // other configurations
services.AddAuthentication(sharedOptions => {
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;//IdentityServerConstants.ExternalCookieAuthenticationScheme;
sharedOptions.DefaultChallengeScheme = IdentityServerConstants.SignoutScheme;
})
... //other external providers...
.AddCookie( CookieAuthenticationDefaults.AuthenticationScheme, options => {
options.Events = new CookieAuthenticationEvents {
OnRedirectToLogin = ctx => {
if (ctx.Request.Path.StartsWithSegments("/Admin", StringComparison.OrdinalIgnoreCase)) {
ctx.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
}
return Task.CompletedTask;
};
});
I expected to seem my handler being called whenever a request is redirected to the login page, but it never happens. Can anyone help?
Thanks
EDIT: just to add that I'm also using aspnet identity for managing the user accounts...
Posting the answer here in case anyone is interested...
After some digging, I've found that using identity means that you can't customize the cookie handler by doing what I was doing. Fortunately, the ConfigureAuthenticationEvent that can be configured by the ConfigureApplicationCookie extension method already does the right thing: if it detects that the current request is an AJAX call, it will return 401; if not, it will return 302. And here was the problem: the request made from the vue client wasn't being considered an AJAX request because it wasn't setting the X-Request-With header to XMLHttpRequest.
So, all it was required was to configure axios to set the header in all the calls:
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
I wrote a middleware sometime ago for this exact purpose and never looked back so if you don't find better solution, perhaps the solution can help you as well:
public class RedirectHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RedirectHandlingMiddleware> _logger;
public RedirectHandlingMiddleware(RequestDelegate next, ILogger<RedirectHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
await HandleRedirect(context, ex);
await _next(context);
}
private Task HandleRedirect(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/Admin", StringComparison.OrdinalIgnoreCase) && context.Response.StatusCode == 302)
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
return Task.CompletedTask;
}
}
Just need to register in Startup.cs:
app.UseAuthentication();
app.UseMiddleware<RedirectHandlingMiddleware>();

Authenticating WEB API 2 .net framework 4.x in identity server 4 using OIDC

I know there are similar questions out there, but still it's not very clear,
after reading a bunch of posts related to the subject, this how i "understood" the code should look like, i am still dealing with all the concepts involved in oauth/openid/owin/katana/identityserver etc...
Big picture is: i have an angular application,
where the user register and log in, no consent is needed, once the user is logged in, the SPA will start comunicating with all the api's in the back and the api's should be able to authenticate against the auth server.
So basically,i need my web api to be able to authenticate in identity server 4, through client credentials grant type, with the issued token by the authentication server.
I got this client(web api 2 .net framework 4.5) defined in identiy server 4:
public static IEnumerable<Client> GetClients()
{
//client credentials client
return new List<Client>
{
new Client
{ ClientId = "client2",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "api2" }
},
}
In the .net Api side i have this:
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType =
CookieAuthenticationDefaults.AuthenticationType
});
app.UseOpenIdConnectAuthentication(new
OpenIdConnectAuthenticationOptions
{
ClientId = "client2",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
ResponseType = "id_token",
Scope = "api2",
SignInAsAuthenticationType =
CookieAuthenticationDefaults.AuthenticationType,
}
});
And the controllers are decorated with the Autorize decorator.
These are the versions of the packages im using
id="Microsoft.Owin.Security.OpenIdConnect" version="4.0.0"
id="Microsoft.Owin.Security.OAuth" version="4.0.0"
id="Microsoft.Owin.Security" version="4.0.0"
id="Microsoft.Owin" version="4.0.0"
By the moment i am using one of the demo projects from the offical project site(https://github.com/IdentityServer/IdentityServer4.Samples), i added an extra call in the MVC demo app to call my api.
public async Task<IActionResult> CallApiUsingUserAccessToken2()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var client = new HttpClient();
client.SetBearerToken(accessToken);
var content = await
client.GetStringAsync("http://localhost:17307/api
/Organization/GetOrganizationById/2007");
ViewBag.Json = JArray.Parse(content).ToString();
return View("Json");
}
According to the working demo, there are two ways to do this, but none have worked to me.
public async Task<IActionResult> CallApiUsingClientCredentials2()
{
var tokenClient = new TokenClient("http://localhost:5000/connect/token", "mvc", "secret");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");
var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken);
var content = await client.GetStringAsync("http://localhost:17307/api/Organization/GetOrganizationById/2007");
ViewBag.Json = JArray.Parse(content).ToString();
return View("Json");
}
This is part of the response with the error, i am getting in both scenarios:
<div class="row">
<div class="col-sm-6">
<div class="alert alert-danger">
Sorry, there was an error
<strong>
<em>
: invalid_request
</em>
</strong>
<div>Invalid redirect_uri</div>
</div>
<div class="request-id">Request Id: 0HLIALF7L4N8J:00000001</div>
</div>
</div>
What is missing here or what is wrong, is the redirect_uri mandatory, why is not present in the configuration section for the .net core ?
This is how configuration of the api looks like in .net core and works fine.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
}
Thanks in advance.
Update
After some experimenting, i comfirming the issue i am having is in the api validating the access token using owin middleware.
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
});
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string,
string>
();
app.UseIdentityServerBearerTokenAuthentication
(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://localhost:5000",
RequiredScopes = new[] { "api2" },
});
}
I am using identityserver3.accesstokenvalidation to perform the validation, as it is recomended, but after getting the access token in the client application and pass it to the api request, i am getting a 401 unauthorized error, is this because it is expecting to operate under secure HTTPS ?, i notice for accesstokenvalidation v4 you can set "RequireHttpsMetadata = false" but i dont see this in v3, could be this the reason i am not getting the token validating ?
Try using the correct client_id first on this line from "mvc" to "client2"
var tokenClient = new TokenClient("http://localhost:5000/connect/token", "mvc", "secret");

Custom endpoint for authorized clients on Identity Server 4

I want my Identity Server 4 server to offer an additional service (e.g., "MyAdditionalService") for SOME of the registered clients. That service will be consumed by them through a custom endpoint to be defined on the server.
I am thinking of defining an API for my that service (e.g., named "myAdditionalService") so that the access to such service can be granted to clients according to their configuration. However I am not sure how to restrict the access to the Endpoint (MVC - Action method) allowing only the clients (potentially on behalf of a user) that are allowed to consume the API.
I found out that I can do:
services.AddAuthorization(options =>
{
options.AddPolicy("MyAdditionalServicePolicy",
policy => policy.RequireClaim("scope",
"myAdditionalService"));
});
and use the attribute [Authorize("MyAdditionalServicePolicy")] to decorate the action method that is used to access such service. However, I don't know can the server be the API at the same time or even if it is possible.
How can I implement this? It is confusing that the token service plays the role of the API as well, since it protects access to an action method or endpoint.
Thanks.
UPDATE:
My web app is an IdentityServerWithAspNetIdentity which already use the Authentication mechanism of Asp.net core Identity. For the sake of the example, the additional service my web app if offering to some registered clients is the list of Twitter friends of a user (Modeled on a controller called Twitter, action called ImportFriends) the api is consequently called "TwitterFriends"
As per suggestion in response below, I modified my Configure() method to have app.UseJwtBearerAuthentication(). I already had app.UseIdentity() and app.UseIdentityServer() as shown below:
app.UseIdentity();
app.UseIdentityServer();
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AuthenticationScheme = "Bearer",
Authority = Configuration["BaseUrl"],
Audience = "TwitterFriends",
RequireHttpsMetadata = false //TODO: make true, it is false for development only
});
// Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
app.UseGoogleAuthentication(new GoogleOptions
{
AuthenticationScheme = "Google",
SignInScheme = "Identity.External", // this is the name of the cookie middleware registered by UseIdentity()
And on a dedicated controller:
[Authorize(ActiveAuthenticationSchemes = "Identity.Application,Bearer")]
//[Authorize(ActiveAuthenticationSchemes = "Identity.Application")]
//[Authorize(ActiveAuthenticationSchemes = "Bearer")]
[SecurityHeaders]
public class TwitterController : Controller
{...
but I am getting this in the log:
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware
[7]
Identity.Application was not authenticated. Failure message: Unprotect tic
ket failed
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed for user: (null).
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.A
uthorization.AuthorizeFilter'.
info: Microsoft.AspNetCore.Mvc.ChallengeResult[1]
Executing ChallengeResult with authentication schemes (Identity.Applicatio
n, Bearer).
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware
[12]
AuthenticationScheme: Identity.Application was challenged.
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware[12]
AuthenticationScheme: Bearer was challenged.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action IdentityServerWithAspNetIdentity.Controllers.TwitterContro
ller.ImportFriends (IdentityServerWithAspNetIdentity) in 86.255ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 105.2844ms 401
I have tried different combinations of the attribute but it seems that Identity.Application and Bearer don't get along in this scenario: getting 401.
any help is appreciated.
Thanks..
See this example on how to host an API in the same web app as IdentityServer.
https://github.com/brockallen/IdentityServerAndApi
In essence you need to add the JWT token validation handler:
services.AddAuthentication()
.AddJwtBearer(jwt =>
{
jwt.Authority = "base_address_of_identityserver";
jwt.Audience = "name of api";
});
On the API itself you must select the JWT authentication scheme:
public class TestController : ControllerBase
{
[Route("test")]
[Authorize(AuthenticationSchemes = "Bearer")]
public IActionResult Get()
{
var claims = User.Claims.Select(c => new { c.Type, c.Value }).ToArray();
return Ok(new { message = "Hello API", claims });
}
}
If you want to enforce an additional authorization policy, you can either pass that into the [Authorize] attribute or call it imperatively.
To achieve this, first you have to write some policy. Policy will define the boundry of accessibility of that specific api.
So you will assign the some scope to registered clients. let's say scope name is "ApiOnlyForRegisteredClients".
So we will create the policy as below:
services.AddAuthorization(options =>
{
options.SetRegisteredClientsPolicy();
}
and
private static void RequireScope(this AuthorizationPolicyBuilder authorizationPolicyBuilder, string[] values)
{
authorizationPolicyBuilder.RequireClaim("scope", values);
}
private static void SetRegisteredClientsPolicy(this AuthorizationOptions options)
{
options.AddPolicy(
OpenIdPolicies.Clients.RegisteredClients,
policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser();
policyBuilder.RequireScope(new string[] { "ApiOnlyForRegisteredClients" });
});
}
Once it done, you are done with policy creation.
Make sure while creating the access token, you are put the same value "ApiOnlyForRegisteredClients" in scope claim.
Now we have to add one api and label it with [Authorize] attribute.
[Authorize(AuthenticationSchemes = "Bearer", Policy = OpenIdPolicies.Clients.RegisteredClients)]
public async Task<ActionResult<T>> Post(int userId, [FromBody] List<int> simRoleIds)
{
}
Now we have to add jwt authentication middleware.
.AddJwtBearer("Bearer", options =>
{
options.Authority = configuration["AuthorityAddresses"];
options.RequireHttpsMetadata = Convert.ToBoolean(configuration["RequireHttpsMetadata"]);
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
TokenDecryptionKey = new X509SecurityKey()
ValidAudiences = apiResources.Select(x => x.ResourceName).ToList(),
ValidIssuers = new List<string> { authorityAddressWithHttps.Uri.OriginalString, authorityAddressWithBasePathHttps.Uri.OriginalString, configuration["AuthorityAddresses"] }
};
})

Resources