Azure Function ClaimsPrincipal locally - c#

I'm using Azure AD Authenticated Azure Function. Everything work fine when deployed on Azure.
public static async Task<IActionResult> ExportT(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req, ClaimsPrincipal principal,
ILogger log)
{
var c = principal.Claims.Select(x => x.Value);
return new OkObjectResult(c);
}
Return :
[ "38a1e83b-c1d3-4fd7-bdc6-ef2447", "https://sts.windows.net/635e38ef-108e-4a26-b718-bbd532/", "1564501257", "1564501257", "1564505157", "42FgYFCuNWnL3ROWG9x3p4EzN3+c/6u8Gh5jV7sT0A", "pwd", "test", "test", "92.169.93", "test", "36fbab-425b-9d65-b2425ef3d9bf", "a1c1ee35-67ab-4f3a-2877c5580b1e", "ES_SALARIED", "XboeusxsxyvnjhCT_vJHkzncPE2JBU58Q50", "635e38ef-26-b718-bbd960991532", "testdedev#ins.coop", "testdedev#ins.coop", "PJ0vEo70o0G__HrwX8ghAA", "1.0" ]
But when executing locally with VS2019 I get :
[ "Admin" ]
Any idea ?
Regards,

The closest I could find to getting this to work was this using these examples from this repo (commit at the time of writing).
In the example AuthenticationService I added context.User = claimsPrincipal; after validating the claim.
From there, I can do the following on my HTTP Triggers:
[Authorize]
[FunctionName("AuthTest")]
public async Task<IActionResult> AuthTest(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "test")] HttpRequest req)
{
// Current user identity returned
// (quickly thrown together as you get a circular reference error when converting to JSON):
return new OkObjectResult(
req.HttpContext.User.Identities.Select(x =>
new {
Claims = x.Claims.Select(y => new
{
y.Type,
y.Value,
y.Issuer,
y.Properties
}),
x.Name,
x.Actor,
x.AuthenticationType,
x.NameClaimType
})
);
}
Coming in Cold?
Providing you have an access token, you can then do a GET: http://localhost:7071/api/test with a JWT token in your header.
E.g:
You will also need to
Register an
app
with Azure AD
Use the MSAL client
on your front-end to get a token
Caveats
This isn't using "easy auth". I opted for this approach as I wanted to use MSAL.NET which supports AAD V2. I found AAD V2 isn't yet supported:
At this time, AAD V2 (including MSAL) is not supported for Azure App Services and Azure Functions. Please check back for updates.
Ref
I had a real fight getting this far. Easy auth doesn't seem like an option if you need to test your apps before pushing to production. Even after this answer, I have no idea how you can make authorization as simple as a normal Web API policy approach.

Related

MsalUiRequiredException when Microsoft Graph used inside ClaimsTransformation

I'm creating a ASP.NET Core 5 MVC project and i'm using Openid-Connect with Microsoft to allow users in our organization to login this webservice.
This all works fine, users can authenticate with their microsoft work account and get signed in. Now i would like to add their profile picture to the claim.
However, I can't seem to get my IClaimsTransformation to work. I've made the UserPhotoClaims to try and add the profile picture for the logged in user. If i use the same graph code to get the picture inside a controller, then it works fine.
If i do it inside a ClaimsTransformation then it fails with the following message:
MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call.
Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
MicrosoftIdentityWebChallengeUserException: IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent.
Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable scopes, string authenticationScheme, string tenantId, string userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
My ClaimsTransformation looks like this:
public class UserPhotoClaims : IClaimsTransformation
{
private readonly GraphServiceClient _graph;
public UserPhotoClaims(GraphServiceClient graphServiceClient)
{
_graph = graphServiceClient;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if(!principal.HasClaim(x => x.Type == "photo"))
{
ClaimsIdentity id = new ClaimsIdentity();
Stream photo = await _graph.Me.Photo.Content.Request().GetAsync();
string ps = Convert.ToBase64String((photo as MemoryStream).ToArray());
id.AddClaim(new Claim("photo", ps));
principal.AddIdentity(id);
}
return principal;
}
}
And i've added the transformation in my ConfigureServices in startup.cs:
services.AddTransient<IClaimsTransformation, UserPhotoClaims>();
Any help much appreciated!
This issue may arise due to permissions not being consented ,as they are already done and token cache is not called again.
Possible workarounds:
Please check if the permissions in the portal are granted consent and try again after adding other scopes in both code and portal and consent them .
Clear the cookies , sign out and retry.
Try by adding [AuthorizeForScopes(Scopes = new[] { "<TheScopeThatYouArePassingOnAcquireTokenMethod>" })] to your action in controller .It processes the MSAL.NET MsalUiRequiredException to handle conditional access and incremental consent.
Please check this for more details
You may try by Handling the exception using try-catch >see this
In public client applications ,the design of MSAL.NET is such that AcquireTokenInteractive never looks at the cache. you may need to call AcquireTokenSilent first
try
{
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
6. Please check this transforming claims in ASP.NET Core | Microsoft Docs
and compare the code.
Please check this tutorial that can guide in detail
References:
msal-net-acquire-token-silently | Microsoft docs
msal-error-handling | Microsoft docs
get-microsoft-account-profile-photo-after-login | SO reference
You may raise a support ticket if workarounds doesn't resolve the issue.

Azure B2C Custom Extension Attribute Not Updating

I am trying to add/update a value to a custom extension attribute for a user in Azure AD B2C. The attribute is an AccountNumber with the type as string. I have two different Azure environments I'm working out of. One is for my local/staging environments, and the other is client's production Azure environment. My local and staging are working fine, but I cannot seem to get this attribute to get updated through the production instance, which is leading me to think I'm missing some sort of permission/configuration within the Azure instance itself rather than code, but let's see.
Here are the steps I've taken in Azure:
Within the B2C I've setup my application. For the API Access section I have 2 selections
Acquire an id_token for users (openid)
Acquire a refresh_token for users (offline_access)
I have two user flows both of which are returning AccountNumber as a claim.
Within App registrations (NOT legacy), I've added my application as well. It does have a warning about not being supported yet in B2C, but I have this in my staging instance as well. I have the following for API Permissions that have been selected for this application. I've gone back and forth adding the offline_access, openid, and profile. (All are uder Microsoft Graph Delegated)
User.Read
User.ReadWrite
offline_access
openid
profile
In App registrations/Authentication tab I've enabled implicit grant flow and checked both Access tokens and ID tokens
Ive taken the b2c-extensions-app app ID and saved it within my code for using to update the extensions attribute (dashes removed)
Web.config
<add key="ida:NonAdminScopes" value="User.Read User.ReadWrite" />
<add key="ida:AdminScopes" value="Directory.AccessAsUser.All User.ReadWrite.All Group.ReadWrite.All" />
Here is where I'm building and making the request. Yes, I realize I'm doing this a more manual way, but I'm also working with Sitecore which has required me to keep some older dlls and this is where I've landed after days of frustration.
private async Task<string> SendGraphPatchRequest(string json, string objectId)
{
var graphEndpoint = new Uri(string.Format("https://graph.microsoft.com/v1.0/{0}/users/{1}", Tenant, objectId));
HttpResponseMessage response;
using (var httpClient = new HttpClient { BaseAddress = graphEndpoint })
{
using (var requestMessage = new HttpRequestMessage(new HttpMethod("PATCH"), graphEndpoint))
{
var token = await GetOrCreateAccessToken();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
if (!string.IsNullOrEmpty(json))
{
requestMessage.Content = new StringContent(json, Encoding.UTF8, "application/json");
}
response = await httpClient.SendAsync(requestMessage).ConfigureAwait(continueOnCapturedContext: false);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
Logger.Error(string.Format("Error -> RequestMessage: {0}", error));
object formatted = JsonConvert.DeserializeObject(error);
throw new WebException("Error Calling the Graph API: \n" + JsonConvert.SerializeObject(formatted, Formatting.Indented));
}
}
}
return response.Content.ReadAsStringAsync().Result;
}
The line that is creating the access token does use ClientCredentials
_accessToken = await _authContext.AcquireTokenAsync("https://graph.microsoft.com/", _credentials);
An example of the request body content
{"extension_[extensionAppId]_AccountNumber":"123456"}
And when I try to make this request with Postman (I had a line of code where I was logging the token that gets created from the above code snippet - not sure if that will actually work or not), here is the response I get back:
{
"error": {
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the operation.",
"innerError": {
"request-id": "####",
"date": "2019-08-21T15:06:45"
}
}
}
Incorrect permissions on the App Reg for MS Graph API.
Probably you have consented to other permissions in your dev tenants then deselected them later, it doesn’t remove consent.
Add read/write directory under Application permissions since you use client credentials in code,delegated permissions won’t do anything here. Also click Grant Permissions once you save the new permissions.

MSAL Error message AADSTS65005 when trying to get token for accessing custom api

I downloaded the example below to get an access token from MS Graph and it worked fine. Now I changed the code to get a token from a custom web API. On apps.dev.microsoft.com I registered a client application and an the API.
Client and server registration in AD
private static async Task<AuthenticationResult> GetToken()
{
const string clientId = "185adc28-7e72-4f07-a052-651755513825";
var clientApp = new PublicClientApplication(clientId);
AuthenticationResult result = null;
string[] scopes = new string[] { "api://f69953b0-2d7f-4523-a8df-01f216b55200/Test" };
try
{
result = await clientApp.AcquireTokenAsync(scopes, "", UIBehavior.SelectAccount, string.Empty);
}
catch (Exception x)
{
if (x.Message == "User canceled authentication")
{
}
return null;
}
return result;
}
When I run the code I login to AD via the dialog en get the following exception in the debugger:
Error: Invalid client Message = "AADSTS65005: The application
'CoreWebAPIAzureADClient' asked for scope 'offline_access' that
doesn't exist on the resource. Contact the app vendor.\r\nTrace ID:
56a4b5ad-8ca1-4c41-b961-c74d84911300\r\nCorrelation ID:
a4350378-b802-4364-8464-c6fdf105cbf1\r...
Error message
Help appreciated trying for days...
For anyone still striking this problem, please read this:
https://www.andrew-best.com/posts/please-sir-can-i-have-some-auth/
You'll feel better after this guy reflects all of your frustrations, except that he works it out...
If using adal.js, for your scope you need to use
const tokenRequest = {
scopes: ["https://management.azure.com/user_impersonation"]
};
I spent a week using
const tokenRequest = {
scopes: ["user_impersonation"]
};
.. since that is the format that the graph API scopes took
As of today, the V2 Endpoint does not support API access other than the Microsoft Graph. See the limitations of the V2 app model here.
Standalone Web APIs
You can use the v2.0 endpoint to build a Web API that is secured with
OAuth 2.0. However, that Web API can receive tokens only from an
application that has the same Application ID. You cannot access a Web
API from a client that has a different Application ID. The client
won't be able to request or obtain permissions to your Web API.
For the specific scenario that you are trying to accomplish, you need to use the V1 App Model (register apps on https://portal.azure.com).
In the very near future, V2 apps will be enabled to call other APIs other than Microsoft Graph, so your scenario will be supported, but that is just not the case today. You should keep an eye out on our documentation for this update.
In your (server) application registration in AAD, you need to specify your scopes in the oauth2Permissions element.
You may already have a user_impersonation scope set. Copy that as a baseline, give it a unique GUID and value, and then AAD will let your client request an access token with your new scope.

Google Data API Authorization Redirect URI Mismatch

Background
I am wanting to write a small, personal web app in .NET Core 1.1 to interact with YouTube and make some things easier for me to do and I am following the tutorials/samples in Google's YouTube documentation. Sounds simple enough, right? ;)
Authenticating with Google's APIs seems impossible! I have done the following:
Created an account in the Google Developer Console
Created a new project in the Google Developer Console
Created a Web Application OAuth Client ID and added my Web App debug URI to the list of approved redirect URIs
Saved the json file provided after generating the OAuth Client ID to my system
In my application, my debug server url is set (and when my application launches in debug, it's using the url I set which is http://127.0.0.1:60077).
However, when I attempt to authenticate with Google's APIs, I recieve the following error:
That’s an error.
Error: redirect_uri_mismatch
The redirect URI in the request, http://127.0.0.1:63354/authorize/,
does not match the ones authorized for the OAuth client.
Problem
So now, for the problem. The only thing I can find when searching for a solution for this is people that say
just put the redirect URI in your approved redirect URIs
Unfortunately, the issue is that every single time my code attempts to authenticate with Google's APIs, the redirect URI it is using changes (the port changes even though I set a static port in the project's properties). I cannot seem to find a way to get it to use a static port. Any help or information would be awesome!
NOTE: Please don't say things like "why don't you just do it this other way that doesn't answer your question at all".
The code
client_id.json
{
"web": {
"client_id": "[MY_CLIENT_ID]",
"project_id": "[MY_PROJECT_ID]",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "[MY_CLIENT_SECRET]",
"redirect_uris": [
"http://127.0.0.1:60077/authorize/"
]
}
}
Method That Is Attempting to Use API
public async Task<IActionResult> Test()
{
string ClientIdPath = #"C:\Path\To\My\client_id.json";
UserCredential credential;
using (var stream = new FileStream(ClientIdPath, FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { YouTubeService.Scope.YoutubeReadonly },
"user",
CancellationToken.None,
new FileDataStore(this.GetType().ToString())
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = this.GetType().ToString()
});
var channelsListRequest = youtubeService.Channels.List("contentDetails");
channelsListRequest.Mine = true;
// Retrieve the contentDetails part of the channel resource for the authenticated user's channel.
var channelsListResponse = await channelsListRequest.ExecuteAsync();
return Ok(channelsListResponse);
}
Project Properties
The Original Answer works, but it is NOT the best way to do this for an ASP.NET Web Application. See the update below for a better way to handle the flow for an ASP.NET Web Application.
Original Answer
So, I figured this out. The issue is that Google thinks of a web app as a JavaScript based web application and NOT a web app with server side processing. Thus, you CANNOT create a Web Application OAuth Client ID in the Google Developer Console for a server based web application.
The solution is to select the type Other when creating an OAuth Client ID in the Google Developer Console. This will have Google treat it as an installed application and NOT a JavaScript application, thus not requiring a redirect URI to handle the callback.
It's somewhat confusing as Google's documentation for .NET tells you to create a Web App OAuth Client ID.
Feb 16, 2018 Updated Better Answer:
I wanted to provide an update to this answer. Though, what I said above works, this is NOT the best way to implement the OAuth workflow for a ASP.NET solution. There is a better way which actually uses a proper OAuth 2.0 flow. Google's documentation is terrible in regards to this (especially for .NET), so I'll provide a simple implementation example here. The sample is using ASP.NET core, but it's easily adapted to the full .NET framework :)
Note: Google does have a Google.Apis.Auth.MVC package to help simplifiy this OAuth 2.0 flow, but unfortunately it's coupled to a specific MVC implementation and does not work for ASP.NET Core or Web API. So, I wouldn't use it. The example I'll be giving will work for ALL ASP.NET applications. This same code flow can be used for any of the Google APIs you've enabled as it's dependent on the scopes you are requesting.
Also, I am assuming you have your application set up in your Google Developer dashboard. That is to say that you have created an application, enabled the necessary YouTube APIs, created a Web Application Client, and set your allowed redirect urls properly.
The flow will work like this:
The user clicks a button (e.g. Add YouTube)
The View calls a method on the Controller to obtain an Authorization URL
On the controller method, we ask Google to give us an Authorization URL based on our client credentials (the ones created in the Google Developer Dashboard) and provide Google with a Redirect URL for our application (this Redirect URL must be in your list of accepted Redirect URLs for your Google Application)
Google gives us back an Authorization URL
We redirect the user to that Authorization URL
User grants our application access
Google gives our application back a special access code using the Redirect URL we provided Google on the request
We use that access code to get the Oauth tokens for the user
We save the Oauth tokens for the user
You need the following NuGet Packages
Google.Apis
Google.Apis.Auth
Google.Apis.Core
Google.apis.YouTube.v3
The Model
public class ExampleModel
{
public bool UserHasYoutubeToken { get; set; }
}
The Controller
public class ExampleController : Controller
{
// I'm assuming you have some sort of service that can read users from and update users to your database
private IUserService userService;
public ExampleController(IUserService userService)
{
this.userService = userService;
}
public async Task<IActionResult> Index()
{
var userId = // Get your user's ID however you get it
// I'm assuming you have some way of knowing if a user has an access token for YouTube or not
var userHasToken = this.userService.UserHasYoutubeToken(userId);
var model = new ExampleModel { UserHasYoutubeToken = userHasToken }
return View(model);
}
// This is a method we'll use to obtain the authorization code flow
private AuthorizationCodeFlow GetGoogleAuthorizationCodeFlow(params string[] scopes)
{
var clientIdPath = #"C:\Path\To\My\client_id.json";
using (var fileStream = new FileStream(clientIdPath, FileMode.Open, FileAccess.Read))
{
var clientSecrets = GoogleClientSecrets.Load(stream).Secrets;
var initializer = new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = clientSecrets, Scopes = scopes };
var googleAuthorizationCodeFlow = new GoogleAuthorizationCodeFlow(initializer);
return googleAuthorizationCodeFlow;
}
}
// This is a route that your View will call (we'll call it using JQuery)
[HttpPost]
public async Task<string> GetAuthorizationUrl()
{
// First, we need to build a redirect url that Google will use to redirect back to the application after the user grants access
var protocol = Request.IsHttps ? "https" : "http";
var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";
// Next, let's define the scopes we'll be accessing. We are requesting YouTubeForceSsl so we can manage a user's YouTube account.
var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };
// Now, let's grab the AuthorizationCodeFlow that will generate a unique authorization URL to redirect our user to
var googleAuthorizationCodeFlow = this.GetGoogleAuthorizationCodeFlow(scopes);
var codeRequestUrl = googleAuthorizationCodeFlow.CreateAuthorizationCodeRequest(redirectUrl);
codeRequestUrl.ResponseType = "code";
// Build the url
var authorizationUrl = codeRequestUrl.Build();
// Give it back to our caller for the redirect
return authorizationUrl;
}
public async Task<IActionResult> GetYoutubeAuthenticationToken([FromQuery] string code)
{
if(string.IsNullOrEmpty(code))
{
/*
This means the user canceled and did not grant us access. In this case, there will be a query parameter
on the request URL called 'error' that will have the error message. You can handle this case however.
Here, we'll just not do anything, but you should write code to handle this case however your application
needs to.
*/
}
// The userId is the ID of the user as it relates to YOUR application (NOT their Youtube Id).
// This is the User ID that you assigned them whenever they signed up or however you uniquely identify people using your application
var userId = // Get your user's ID however you do (whether it's on a claim or you have it stored in session or somewhere else)
// We need to build the same redirect url again. Google uses this for validaiton I think...? Not sure what it's used for
// at this stage, I just know we need it :)
var protocol = Request.IsHttps ? "https" : "http";
var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";
// Now, let's ask Youtube for our OAuth token that will let us do awesome things for the user
var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };
var googleAuthorizationCodeFlow = this.GetYoutubeAuthorizationCodeFlow(scopes);
var token = await googleAuthorizationCodeFlow.ExchangeCodeForTokenAsync(userId, code, redirectUrl, CancellationToken.None);
// Now, you need to store this token in rlation to your user. So, however you save your user data, just make sure you
// save the token for your user. This is the token you'll use to build up the UserCredentials needed to act on behalf
// of the user.
var tokenJson = JsonConvert.SerializeObject(token);
await this.userService.SaveUserToken(userId, tokenJson);
// Now that we've got access to the user's YouTube account, let's get back
// to our application :)
return RedirectToAction(nameof(this.Index));
}
}
The View
#using YourApplication.Controllers
#model YourApplication.Models.ExampleModel
<div>
#if(Model.UserHasYoutubeToken)
{
<p>YAY! We have access to your YouTube account!</p>
}
else
{
<button id="addYoutube">Add YouTube</button>
}
</div>
<script>
$(document).ready(function () {
var addYoutubeUrl = '#Url.Action(nameof(ExampleController.GetAuthorizationUrl))';
// When the user clicks the 'Add YouTube' button, we'll call the server
// to get the Authorization URL Google built for us, then redirect the
// user to it.
$('#addYoutube').click(function () {
$.post(addYoutubeUrl, function (result) {
if (result) {
window.location.href = result;
}
});
});
});
</script>
As referred here, you need to specify a fix port for the ASP.NET development server like How to fix a port number in asp.NET development server and add this url with the fix port to the allowed urls. Also as stated in this thread, when your browser redirects the user to Google's oAuth page, you should be passing as a parameter the redirect URI you want Google's server to return to with the token response.
I noticed that there is easy non-programmatic way around.
If you have typical monotlith application built in typical MS convention(so not compatible with 12factor and typical DDD) there is an option to tell your Proxy WWW server to rewrite all requests from HTTP to HTTPS so even if you have set up Web App on http://localhost:5000 and then added in Google API url like: http://your.domain.net/sigin-google, it will work perfectly and it is not that bas because it is much safer to set up main WWW to rewrite all to HTTPS.
It is not very good practice I guess however it makes sense and does the job.
I've struggled with this issue for hours in a .net Core application. What finally fixed it for me was, in the Google developers console, to create and use a credential for "Desktop app" instead of a "Web application".
Yeah!! Using credentials of desktop app instead of web app worked for me fine. It took me more than 2 days to figure out this problem. The main problem is that google auth library dose not adding or supporting http://localhost:8000 as redirect uri for web app creds but credentials of desktop app fixed that issue. Cause its supporting http://___ connection instead of https: connection for redirect uri

Is there a way to generate an access token from within Identity Server without using the endpoints?

I'm building an identity server deployment (Identity Server 4, hosted in an ASP.NET Core MVC application). As a part of the new user registration process, I need the identity server application to make a request against another API. I'd like to use, basically, the client credential flow to make this request, but instead of having the identity server make an http request against its own endpoint, would it be possible to just programmatically generate the token in C#?
What I'd like to do would be something like this:
public class AccountController : Controller
{
[HttpPost("register")]
public async Task<IActionResult> Register(UserRegistrationModel model)
{
// do stuff like validate model, create user, update database, etc
// generate access token for other API
var client = identityServer4DbContext.Clients.FirstOrDefault(c => c.Id = "myself");
var token = tokenService.CreateAccessToken(client, StandardScopes.All.Concat(scopeForMyOtherApi));
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://myotherapi/");
var result = await httpClient.GetAsync("resource/info-i-need");
// do something with result.
}
}
I saw that there is an ITokenService in IdentityServer4, but it requires a TokenCreationRequest populated with stuff you only get when you have an http request (for a token) to handle, so it seems that it is only useful to IdentityServer4 itself.
I also recognize that I could use the IdentityModel client to make a request against my own endpoint, but that would involve a bit more configuration that I'd like to avoid - not to mention that it seems like I shouldn't have to do that from within the identity server application itself.
In IdentityServer 3 it was possible to call IssueClientToken() OWIN extension method.
In IdSrv 4, use IdentityServerTools.IssueJwtAsync() and IssueClientJwtAsync().

Categories