In my .net core 1.1 code I'm doing the authentication as follows(which is sending the bearer token to external URL and look for claims in the return token). This code is in Configure method
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = signinAuthority,
RequireHttpsMetadata = signinHTTPS,
ClientId = "skybus",
ClientSecret = "secret",
ResponseType = "code id_token",
Scope = { "api1", "offline_access" },
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true
});
Now I upgraded my code to .net Core 2.0 the both UseCookieAuthentication & UseOpenIdConnectAuthentication are changed. I'm finding it difficult to find what needs to be done in this case
What I changed it to is as follows in ConfigureServices method
services.AddAuthentication(options => {
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(o =>
{
o.Authority = signinAuthority;
o.SignInScheme = "Cookies";
o.RequireHttpsMetadata = signinHTTPS;
o.ClientId = "skybus";
o.ClientSecret = "secret";
o.ResponseType = "code id_token";
o.GetClaimsFromUserInfoEndpoint = true;
o.SaveTokens = true;
o.Scope.Add("api1");
o.Scope.Add("offline_access");
});
In Browser I see this URL after the above changes. It should either show me the external login page if user is not logged in or return to home page of my website
http://localhost:5000/Account/Login?ReturnUrl=%2F
I followed this link from microsoft to start my migration. Most of the migration is covered by the this link, but I faced issue where most of my claims are missing.
With the ASP.NET Core 1.x, client would have received the claims: nbf, exp, iss, aud, nonce, iat, c_hash, sid, sub, auth_time, idp, amr.
In Core 2.0 we only get sid, sub and idp. What happened?
Microsoft added a new concept to their OpenID Connect handler called ClaimActions. Claim actions allow modifying how claims from an external provider are mapped (or not) to a claim in your ClaimsPrincipal. Looking at the ctor of the OpenIdConnectOptions, you can see that the handler will now skip the following claims by default:
ClaimActions.DeleteClaim("nonce");
ClaimActions.DeleteClaim("aud");
ClaimActions.DeleteClaim("azp");
ClaimActions.DeleteClaim("acr");
ClaimActions.DeleteClaim("amr");
ClaimActions.DeleteClaim("iss");
ClaimActions.DeleteClaim("iat");
ClaimActions.DeleteClaim("nbf");
ClaimActions.DeleteClaim("exp");
ClaimActions.DeleteClaim("at_hash");
ClaimActions.DeleteClaim("c_hash");
ClaimActions.DeleteClaim("auth_time");
ClaimActions.DeleteClaim("ipaddr");
ClaimActions.DeleteClaim("platf");
ClaimActions.DeleteClaim("ver");
If you want to “un-skip” a claim, you need to delete a specific claim action when setting up the handler. The following is the very intuitive syntax to get the amr claim back:
options.ClaimActions.Remove("amr");
Requesting more claims from the OIDC provider
When you are requesting more scopes, e.g. profile or custom scopes that result in more claims, there is another confusing detail to be aware of.
Depending on the response_type in the OIDC protocol, some claims are transferred via the id_token and some via the userinfo endpoint.
So first of all, you need to enable support for the userinfo endpoint in the handler:
options.GetClaimsFromUserInfoEndpoint = true;
In the end you need to add the following class to import all other custom claims
public class MapAllClaimsAction : ClaimAction
{
public MapAllClaimsAction() : base(string.Empty, string.Empty)
{
}
public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
{
foreach (var claim in identity.Claims)
{
// If this claimType is mapped by the JwtSeurityTokenHandler, then this property will be set
var shortClaimTypeName = claim.Properties.ContainsKey(JwtSecurityTokenHandler.ShortClaimTypeProperty) ?
claim.Properties[JwtSecurityTokenHandler.ShortClaimTypeProperty] : string.Empty;
// checking if claim in the identity (generated from id_token) has the same type as a claim retrieved from userinfo endpoint
JToken value;
var isClaimIncluded = userData.TryGetValue(claim.Type, out value) || userData.TryGetValue(shortClaimTypeName, out value);
// if a same claim exists (matching both type and value) both in id_token identity and userinfo response, remove the json entry from the userinfo response
if (isClaimIncluded && claim.Value.Equals(value.ToString(), StringComparison.Ordinal))
{
if (!userData.Remove(claim.Type))
{
userData.Remove(shortClaimTypeName);
}
}
}
// adding remaining unique claims from userinfo endpoint to the identity
foreach (var pair in userData)
{
JToken value;
var claimValue = userData.TryGetValue(pair.Key, out value) ? value.ToString() : null;
identity.AddClaim(new Claim(pair.Key, claimValue, ClaimValueTypes.String, issuer));
}
}
}
and then use the above class code to add it to ClaimActions
options.ClaimActions.Add(new MapAllClaimsAction());
I don't know if you are using IdentityServer4 but they have provided some samples on github for ASP.NET Core 2.0.
Hope this helps you.
Sample Project
Did you take the authentication middleware into use?
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
...
Related
I have an existing MVC project that uses FormsAuthentication for its authentication.
I need to incorporate the option of logging in with an OpenID IDP in addition to the regular login page already available.
The problem I'm having is challenging the IDP on demand and setting the authentication cookie once the claims are received, I can't find the reason why the cookie is not sticking. The flow seems to be working fine, and I can see the claims in the AuthorizationCodeReceived callback.
Here's the Startup.Auth.cs code:
var notificationHandlers = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = (context) =>
{
string username = context.AuthenticationTicket.Identity.FindFirst("preferred_username").Value;
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(60), true, "");
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
context.Response.Cookies.Append(FormsAuthentication.FormsCookieName, encryptedTicket);
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
if (context.OwinContext.Request.Path.Value != "/Account/SignInWithOpenId")
{
context.OwinContext.Response.Redirect("/Account/Login");
context.HandleResponse();
}
return Task.FromResult(0);
}
};
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "xxxxxxxxx",
ClientId = "MyClient",
ClientSecret = "xxxxxxxx",
RedirectUri = "http://localhost:52389/",
PostLogoutRedirectUri = "http://localhost:52389/",
ResponseType = "code id_token",
Scope = "openid profile email roles",
UseTokenLifetime = false,
TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = "preferred_username",
RoleClaimType = "role"
},
Notifications = notificationHandlers
});
app.SetDefaultSignInAsAuthenticationType("Cookies");
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = "Cookies",
AuthenticationMode = AuthenticationMode.Passive,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider()
});
app.UseStageMarker(PipelineStage.Authenticate);
And here's the AccountController SignInWithOpenId method:
public ActionResult SignInWithOpenId()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType);
// If I don't have this line, reponse redirects to the forms authentication login... so maybe something is wrong here?
return new HttpUnauthorizedResult("IDP");
}
else
{
return RedirectToAction("Index", "Default");
}
}
Any pointers would be greatly appreciated. Thank you.
This is the exact thing I'm trying to do at the moment. I will let you know if I find anything useful.
Update:
I ended up disabling Forms Authentication in the MVC web app. I was doing a proof of concept so it wasn't a hard requirement. I know this was not really what you were getting at. I successfully used my IdP to login and redirect back to the web app. Where the proof of concept ended was the HttpContext.User object was needed to be populated.
I was able to get this, or at least an equivalent, working in .NET 4.7. My use case is that most subscribers are being upgraded to log in via Azure AD B2C, but we have public PCs that we want to authenticate with a manual claim via an obscured URL.
I'm using Microsoft.Owin.Security.OpenIDConnect and related packages, and the Owin startup is standard, although I will point out this line:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
I had to disable Forms authentication entirely; I could not get this working when anything other than Anonymous Authentication was enabled in IIS.
The core of the solution was actually an example I found here: How to use OWIN forms authentication without aspnet identity
/* URL validated, add authenticated claim */
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "PublicPC"),
new Claim(ClaimTypes.Email, "PublicPC#example.org")
};
var id = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);
var ctx = HttpContext.Current.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(id);
But critically, I needed to specify CookieAuthenticationDefaults.AuthenticationType, which is what I'm using in the Owin startup.
solved by adding these code to Global.asax:
protected void Application_BeginRequest()
{
Context.Response.SuppressFormsAuthenticationRedirect = true;
}
according to Prevent ASP.NET from redirecting to login.aspx
I have been making a web application using ASP.NET Core 3.1 using clean architecture.
I have some class libraries like Infrastructure, Persistence, Domain, Application, and a MVC application project named "Web" as the startup point of my application.
In the Web layer I have "Areas" in which I have an Admin area containing some controllers and action methods which return JSON as my API endpoints to be used in a React-based app.
I also have some controllers in the Web MVC project in Controllers folder which their action methods return html views.
I also use Identity and JWT for my API endpoints but:
- What if I want to use claims-based Identity in my MVC controllers which their action results return html views?
- What is the best practice for using claims-based Identity in ASP.NET Core 3.1 in such an application?
Any help would be appreciated.
After doing some research, I found the solution in ASP.NET core Authorization documentation in an article with the title "Authorize with a specific scheme in ASP.NET Core".
Based on the mentioned article in Microsoft ASP .NET core documentation, In some scenarios, such as Single Page Applications (SPAs), it's common to use multiple authentication methods. For example, the app may use cookie-based authentication to log in and JWT bearer authentication for JavaScript requests.
An authentication scheme is named when the authentication service is configured during authentication. For example:
public void ConfigureServices(IServiceCollection services)
{
// Code omitted for brevity
services.AddAuthentication()
.AddCookie(options => {
options.LoginPath = "/Account/Unauthorized/";
options.AccessDeniedPath = "/Account/Forbidden/";
})
.AddJwtBearer(options => {
options.Audience = "http://localhost:5001/";
options.Authority = "http://localhost:5000/";
});
In the preceding code, two authentication handlers have been added: one for cookies and one for bearer.
Selecting the scheme with the Authorize attribute
[Authorize(AuthenticationSchemes =
JwtBearerDefaults.AuthenticationScheme)]
public class MixedController : Controller
In the preceding code, only the handler with the "Bearer" scheme runs. Any cookie-based identities are ignored.
This is the solution which solved my problem and I thought it would be good to share it with you guys for those who need this.
Multiple Authentication Schemes in .Net Core 3.1 or .Net 5.0
Startup.cs
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(x =>
{
x.LoginPath = "/";
x.ExpireTimeSpan = TimeSpan.FromMinutes(Configuration.GetValue<int>("CookieExpiry"));
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetValue<string>("JWTSecret"))),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder = defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
/api/auth/login
public async Task<AuthenticationResult> Login([FromForm] string userName, [FromForm] string password, [FromHeader] string authmode = "")
{
if (userName != "demo" || password != "demo")
return new AuthenticationResult { HasError = true, Message = "Either the user name or password is incorrect." };
var claims = new Claim[]
{
new Claim(ClaimTypes.Name, userName)
};
if(authmode?.ToLower() == "token")
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_config.GetValue<string>("JWTSecret"));
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims, "JWT"),
Expires = DateTime.UtcNow.AddMinutes(_config.GetValue<int>("JWTExpiry")),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var jwt = tokenHandler.WriteToken(token);
return new AuthenticationResult { Token = jwt };
}
else
{
ClaimsPrincipal princ = new ClaimsPrincipal(new ClaimsIdentity(claims, "COOKIE"));
await HttpContext.SignInAsync(princ);
return new AuthenticationResult();
}
}
Output:
I understand it's a long question but I would really appreciate it if anyone could share their thoughts or experience with me as I've been around this for a few days now trying lots of things. I'm having an asp net core 3.1 web API application and an ASP.NET Core 3.1 MVC application.
Both have been registered in Azure AD. The API project is supposed to create calendar events based on the request payload it receives from the MVC project. I am following the Microsoft instructions from this link here
But once the API project makes a call against the Microsoft Graph, it fails with the following error:
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure. Invalid audience.",
I'm putting in the minimum here to provide some more info but the whole sample can be downloaded from the link above.
ASP.NET Core MVC Startup.cs:
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options =>
{
Configuration.Bind("AzureAd", options);
AzureAdOptions.Settings = options;
})
.AddCookie();
ASP.NET Core MVC project AddAzureAd function:
public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
{
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, ConfigureAzureOptions>();
builder.AddOpenIdConnect();
return builder;
}
ConfigureAzureOptions:
public void Configure(string name, OpenIdConnectOptions options)
{
options.ClientId = _azureOptions.ClientId;
options.Authority = _azureOptions.Authority;
options.UseTokenLifetime = true;
options.CallbackPath = _azureOptions.CallbackPath;
options.RequireHttpsMetadata = false;
options.ClientSecret = _azureOptions.ClientSecret;
options.Resource = "https://graph.microsoft.com"; // AAD graph
// Without overriding the response type (which by default is id_token), the OnAuthorizationCodeReceived event is not called.
// but instead OnTokenValidated event is called. Here we request both so that OnTokenValidated is called first which
// ensures that context.Principal has a non-null value when OnAuthorizeationCodeReceived is called
options.ResponseType = "id_token code";
// Subscribing to the OIDC events
options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived;
options.Events.OnAuthenticationFailed = OnAuthenticationFailed;
}
And here's the code from the API project to configure Azure Options:
private class ConfigureAzureOptions : IConfigureNamedOptions<JwtBearerOptions>
{
private readonly AzureAdOptions _azureOptions;
public ConfigureAzureOptions(IOptions<AzureAdOptions> azureOptions)
{
_azureOptions = azureOptions.Value;
}
public void Configure(string name, JwtBearerOptions options)
{
// options.Audience = _azureOptions.ClientId;
options.Authority = $"{_azureOptions.Instance}{_azureOptions.TenantId}";
// The valid audiences are both the Client ID(options.Audience) and api://{ClientID}
// --->>> I've changed this to also have "https://graph.micrososft.com" but no luck
options.TokenValidationParameters.ValidAudiences = new string[] { _azureOptions.ClientId, $"api://{_azureOptions.ClientId}" }; // <<--- I've changed this to "https://graph.micrososft.com" but no luck
// If you want to debug, or just understand the JwtBearer events, uncomment the following line of code
// options.Events = JwtBearerMiddlewareDiagnostics.Subscribe(options.Events);
}
public void Configure(JwtBearerOptions options)
{
Configure(Options.DefaultName, options);
}
}
This is how I gain a token from the MVC project - the authority is the api://client_id:
string userObjectID = User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
//AuthenticationContext authContext = new AuthenticationContext(AzureAdOptions.Settings.Authority, new NaiveSessionCache(userObjectID, HttpContext.Session));
AuthenticationContext authContext = new AuthenticationContext(AzureAdOptions.Settings.Authority);
ClientCredential credential = new ClientCredential(AzureAdOptions.Settings.ClientId, AzureAdOptions.Settings.ClientSecret);
I appreciate your thoughts and experience on this - thanks again for your time.
Looks like your client app is acquiring a Microsoft Graph API token:
options.Resource = "https://graph.microsoft.com";
An access token has an audience (aud claim) that specifies what API it is meant for.
Your client app needs to use your API's client id or application ID URI as the resource.
This way you get an access token that is meant for your API.
The Resource option there is limited to one API.
If you need tokens for multiple APIs,
you'll need to setup an event listener for AuthorizationCodeReceived and use MSAL.NET to exchange the authorization code for tokens.
I have a sample app that does this: https://github.com/juunas11/aspnetcore2aadauth/blob/97ef0d62297995c350f40515938f7976ab7a9de2/Core2AadAuth/Startup.cs#L58.
This app uses .NET Core 2.2 and ADAL though, but the general approach with MSAL would be similar.
I have a problem enabling Facebook auth in my ASP.NET Core web app. I'm using ASP.NET Core Authentication but not Identity. The auth is configured in Startup like this:
services
.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddFacebook(options =>
{
options.ClientId = "clientid";
options.ClientSecret = "secret";
options.CallbackPath = "/signinfacebookcallback";
});
As shown in the code, I want to use cookie auth, but also allow people to sign in with Facebook. Once they have been successfully signed in, I want to set the auth cookie.
To show the challenge, I have the following action:
[HttpGet]
[Route("signinfacebook")]
public ActionResult SignInFacebook()
{
return Challenge(FacebookDefaults.AuthenticationScheme);
}
This redirects the user to the Facebook login screen. Once they sign in, the user is redirected to the URL specified in config:
[HttpGet]
[Route("signinfacebookcallback")]
public async Task<ActionResult> SignInFacebookCallback()
{
var result = await HttpContext.AuthenticateAsync();
if (!result.Succeeded) return Redirect("/login/");
...
}
When I debug the code, result.Succeeded returns false and the AuthenticationResult object doesn't contain more information on why Succeeded is false.
I verified that the app id and secret are correct.
What could be the issue here?
The CallbackPath in the OpenID Connect middleware is internal path that are used for the authentication flow of the OpenID Connect protocol. After Identity provider redirect user to that url in your application , middeware will handle token valiation ,token decode,exchange token and finally fill the user principle , and that process is fired before your controller gets involved .
Since CallbackPath is internal and will handled by OpenID Connect middleware automatically , you don't need to care about , make sure the callback is registered in facebook's allowed redirect url and let middleware handle the callback .If you want to redirect user to specific route/page after authentication , put the url to AuthenticationProperties :
if (!User.Identity.IsAuthenticated)
{
return Challenge(new AuthenticationProperties() { RedirectUri = "/home/Privacy" } ,FacebookDefaults.AuthenticationScheme);
}
And you should remove the callback path route (signinfacebookcallback) in your application .
UPDATE
If you want to access database and manage local user , you can use built-in events in middleware, for AddFacebook middleware , you can use OnTicketReceived to add access database , manage users and add claims to user's princple :
.AddFacebook(options =>
{
options.ClientId = "xxxxxxxxxxxxx";
options.ClientSecret = "xxxxxxxxxxxxxxxxxxxx";
options.CallbackPath = "/signinfacebookcallback";
options.Events = new OAuthEvents
{
OnTicketReceived = ctx =>
{
//query the database to get the role
var db = ctx.HttpContext.RequestServices.GetRequiredService<YourDbContext>();
// add claims
var claims = new List<Claim>
{
new Claim(ClaimTypes.Role, "Admin")
};
var appIdentity = new ClaimsIdentity(claims);
ctx.Principal.AddIdentity(appIdentity);
return Task.CompletedTask;
},
};
});
I am using ASP.NET Core 2.0, with Azure AD v2.0 endpoint.
I am getting claims like this:
var currentUser = User;
var displayName = currentUser.FindFirst("name").Value;
var claims = currentUser.Claims;
I am not used to using this User to get claims, but could not get the old way with System.Security.Claims to work. So my first question is, is this how I should be getting my claims? And my second question is, how do I add claims to this User?
is this how I should be getting my claims?
AFAIK, you could leverage ControllerBase.HttpContext.User or ControllerBase.User for retrieving the System.Security.Claims.ClaimsPrincipal for current user. Details you could follow the similar issue1 and issue2.
And my second question is, how do I add claims to this User?
As you said you are using ASP.NET Core 2.0, with Azure AD v2.0. I assumed that when using UseOpenIdConnectAuthentication, you could add the additional claims under OnTokenValidated as follows:
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = Configuration["AzureAD:ClientId"],
Authority = string.Format(CultureInfo.InvariantCulture, Configuration["AzureAd:AadInstance"], "common", "/v2.0"),
ResponseType = OpenIdConnectResponseType.IdToken,
PostLogoutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"],
Events = new OpenIdConnectEvents
{
OnRemoteFailure = RemoteFailure,
OnTokenValidated = TokenValidated
},
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,
NameClaimType = "name"
}
});
private Task TokenValidated(TokenValidatedContext context)
{
/* ---------------------
// Replace this with your logic to validate the issuer/tenant
---------------------
// Retriever caller data from the incoming principal
string issuer = context.SecurityToken.Issuer;
string subject = context.SecurityToken.Subject;
string tenantID = context.Ticket.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
// Build a dictionary of approved tenants
IEnumerable<string> approvedTenantIds = new List<string>
{
"<Your tenantID>",
"9188040d-6c67-4c5b-b112-36a304b66dad" // MSA Tenant
};
o
if (!approvedTenantIds.Contains(tenantID))
throw new SecurityTokenValidationException();
--------------------- */
var claimsIdentity=(ClaimsIdentity)context.Ticket.Principal.Identity;
//add your custom claims here
claimsIdentity.AddClaim(new Claim("test", "helloworld!!!"));
return Task.FromResult(0);
}
Then, I used the following code to retrieve the user claims:
public IActionResult UserInfo()
{
return Json(User.Claims.Select(c=>new {type=c.Type,value=c.Value}).ToList());
}
Test:
Moreover, you could refer to this sample Integrating Azure AD (v2.0 endpoint) into an ASP.NET Core web app.