Graph API with ASP.NET Core Blazor WebAssembly - azure-active-directory

I would like to get information from Microsoft graph web API. I followed these instructions:
https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/graph-api?view=aspnetcore-5.0
The problem is that the variable "token" in the AuthenticateRequestAsync method is always null. It means that the Blazor app does not get the token.
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
var result = await TokenProvider.RequestAccessToken(
new AccessTokenRequestOptions()
{
Scopes = new[] { "https://graph.microsoft.com/User.Read" }
});
if (result.TryGetToken(out var token))
{
request.Headers.Authorization ??= new AuthenticationHeaderValue(
"Bearer", token.Value);
}
}
The Program.cs has the following code:
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, RemoteUserAccount>(options =>
{
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://graph.microsoft.com/User.Read");
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});
builder.Services.AddGraphClient("https://graph.microsoft.com/User.Read");
In Index.razor I just add two lines of code I OnInitializedAsync method
var request = GraphClient.Me.Request();
user = await request.GetAsync();
I spent a lot of time to figure out what is the main issue but without success. I will appreciate any help.

Please imagine the single-page website. Usually, this kind of page has a "contact us" tab where is the contact form. If the user fills up the contact form then data have to be somehow sent to us. For this purpose, I tried to use MS graph API. When the user clicks the submit button, in the background the registration to my account will be created and an email will be sent to me. It means that the user is not aware of any registration procedure. – Samo Simoncic
For your app to be able to create users in a tenant, it needs to use an app only flow which requires a secret. We do not advise exposing app only flows of this nature, which can easily be exploited to create bogus users or overwhelm your tenant, open to the general public.
The best approach would be to take this registrations in a local DB, and then have a daemon app process them behind the scenes. Here is the sample where daemon console application is calling Microsoft Graph.

Not sure about the cause of the issue.
But I can make it work with the following code and configuration:
Program.cs
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
// Adds the Microsoft graph client (Graph SDK) support for this app.
builder.Services.AddMicrosoftGraphClient("https://graph.microsoft.com/User.Read");
// Integrates authentication with the MSAL library
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://graph.microsoft.com/User.Read");
});
await builder.Build().RunAsync();
}
appsettings.json
{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/exxxxx4e-bd27-40d5-8459-230ba2xxxxxb",
"ClientId": "7xxxxxx8-88b3-4c02-a2f8-0a890xxxxxx5",
"CallbackPath": "/signin-oidc",
"ValidateAuthority": "true",
"DefaultScopes": [
"openid",
"profile"
]
}
}
You can refer to the configuration and sample code here.

I have cloned your repo from the GitHub URL you posted in the comments.
There is no issue with the code to fetch the data from the Microsoft Graph API, the problem is that you have written the code of calling the API when the apps shows the index component before even the user logs in, you have to check if the user is logged in first and add a login button to the UI or you can add [Authorize] to the index page so it will redirect the user to Login before it shows the component and make the API and to implement that make sure to add the CascadingAuthenticationState and AuthorizeView to your App.razor as following
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)">
<NotAuthorized>
#if (!context.User.Identity.IsAuthenticated)
{
<a class="btn btn-success" href="/authentication/login">Login with Microsoft</a>
}
else
{
<p>You are not authorized to access this resource.</p>
}
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
And then in your Index.razor add at the top the following line
#attribute [Authorize]
Then you launch the app if the user is not logged in, he/she will be asked to do so and then go to the Index component and make the API call which will succed then

Related

Azure B2C Logout in Blazor

I've build a Blazor server app and I'm using the Azure b2c which I build using the wizard.
I don't have a login page and I only use the Google as oauth provider. I just have the default blanket redirect which is fine for me.
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy
options.FallbackPolicy = options.DefaultPolicy;
});
One issue is that I'm facing is that when I'm opening the app it doesn't prompt me asking which account I want to use. I know I'm already signed into my google account as whole but when opening my app I would like the app to prompt for "choosing the account". When I run the same userflow on the azure portal it does prompt me. The same just doesn't happen for my app. How can I make sure that the app always asks to select the account? I read some articles which said to add "prompt" keyword but I don't know where to add that as I'm not calling any custom url.
Another issue I'm facing is that the log-out doesn't work as expected. In my app logout sequence is same as the default which redirects user to MicrosoftIdentity/Account/SignIn link.
<AuthorizeView>
<Authorized>
Hello, #context.User.Identity?.Name!
Log out
</Authorized>
<NotAuthorized>
Log in
</NotAuthorized>
</AuthorizeView>
Once I click the logout button; I does something and then redirects me to this page.
However once I click the back button, the app opens as normal with the user still signed in. I expected the app to prompt for login at-least this time.
Can you please help me with the right approach for the implementing this. I prefer to avoid advance things like custom user flows. Perhaps some settings in appsettings.json can do the trick?
Thanks a lot.
For the logout to work properly ,In the Redirect URIs section in portal, setredirect URIs.
Example:
redirectUri : https://localhost:44365/signin-oidc
In the Logout URL section, https://localhost:44365/signout-oidc or : https://localhost:44365/signin-oidc
Please check Configure session behavior - Azure Active Directory B2C | Microsoft Learn
In appsetting.json set "CallbackPath": "/signin-oidc" and set a userflow SignedOutCallbackPath
appsettings.json:
"AzureAd": {
"Authority": "https://xx.b2clogin.com/XXXXXX.onmicrosoft.com/B2C_1_SignUpSignIn",
"Instance": "https://XXXXXX.b2clogin.com",
"TenantId": "XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX",
"ClientId": "XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX",
"ClientSecret": "XXXXXXXXXXXXXXXXXX"
"CallbackPath": "/signin-oidc",
"Domain": "XXXXXX.onmicrosoft.com",
"SignUpSignInPolicyId": "B2C_1_SignUpSignIn",
"SignedOutCallbackPath": "/signout/B2C_1_susi",
"ResetPasswordPolicyId": "B2C_1_PasswordReset",
"EditProfilePolicyId": "B2C_1_EditProfile",
},
"API": {
"BaseUrl": "",
"Scopes": "https://XXXXXX.onmicrosoft.com/ xxx/<scope>"
},
In startup.cs:
builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Events.OnSignedOutCallbackRedirect = context =>
{
context.HttpContext.Response.Redirect(context.Options.SignedOutRedirectUri);
context.HandleResponse();
return Task.CompletedTask;
};
});
Or
set the prompt value to login or select_account using OnRedirectToIdentityProvider when OIDC authentication handler is registered
public void ConfigureServices(IServiceCollection services)
{
.....
services.Configure<OpenIdConnectOptions>(AzureADB2CDefaults.OpenIdScheme, options =>
{
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
if (context.Properties.Items.TryGetValue("prompt", out string prompt))
{
context.ProtocolMessage.Prompt = prompt;
}
return Task.CompletedTask;
}
};
...
}
When I logged in
Selected logout which redirects to signout path which redirects to post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44365%2Fsignout-callback-oidc&state=
Then if I clicked back , then I cant access other areas without login
Reference : https://github.com/Azure-Samples/ms-identity-blazor-server/blob/main/WebApp-OIDC/B2C/blazorserver-B2C/appsettings.json

Django DRF + Allauth: OAuth2Error: Error retrieving access token on production build

We are integrating DRF (dj_rest_auth) and allauth with the frontend application based on React. Recently, the social login was added to handle login through LinkedIn, Facebook, Google and GitHub. Everything was working good on localhost with each of the providers. After the staging deployment, I updated the secrets and social applications for a new domain. Generating the URL for social login works fine, the user gets redirected to the provider login page and allowed access to login to our application, but after being redirected back to the frontend page responsible for logging in - it results in an error: (example for LinkedIn, happens for all of the providers)
allauth.socialaccount.providers.oauth2.client.OAuth2Error:
Error retrieving access token:
b'{"error":"invalid_redirect_uri","error_description":"Unable to retrieve access token: appid/redirect uri/code verifier does not match authorization code. Or authorization code expired. Or external member binding exists"}'
Our flow is:
go to frontend page -> click on provider's icon ->
redirect to {BACKEND_URL}/rest-auth/linkedin/url/ to make it a POST request (user submits the form) ->
login on provider's page ->
go back to our frontend page {frontend}/social-auth?source=linkedin&code={the code we are sending to rest-auth/$provider$ endpoint}&state={state}->
confirm the code & show the profile completion page
The adapter definition (same for every provider):
class LinkedInLogin(SocialLoginView):
adapter_class = LinkedInOAuth2Adapter
client_class = OAuth2Client
#property
def callback_url(self):
return self.request.build_absolute_uri(reverse('linkedin_oauth2_callback'))
Callback definition:
def linkedin_callback(request):
params = urllib.parse.urlencode(request.GET)
return redirect(f'{settings.HTTP_PROTOCOL}://{settings.FRONTEND_HOST}/social-auth?source=linkedin&{params}')
URLs:
path('rest-auth/linkedin/', LinkedInLogin.as_view(), name='linkedin_oauth2_callback'),
path('rest-auth/linkedin/callback/', linkedin_callback, name='linkedin_oauth2_callback'),
path('rest-auth/linkedin/url/', linkedin_views.oauth2_login),
Frontend call to send the access_token/code:
const handleSocialLogin = () => {
postSocialAuth({
code: decodeURIComponent(codeOrAccessToken),
provider: provider
}).then(response => {
if (!response.error) return history.push(`/complete-profile?source=${provider}`);
NotificationManager.error(
`There was an error while trying to log you in via ${provider}`,
"Error",
3000
);
return history.push("/login");
}).catch(_error => {
NotificationManager.error(
`There was an error while trying to log you in via ${provider}`,
"Error",
3000
);
return history.push("/login");
});
}
Mutation:
const postSocialUserAuth = builder => builder.mutation({
query: (data) => {
const payload = {
code: data?.code,
};
return {
url: `${API_BASE_URL}/rest-auth/${data?.provider}/`,
method: 'POST',
body: payload,
}
}
Callback URLs and client credentials are set for the staging environment both in our admin panel (Django) and provider's panel (i.e. developers.linkedin.com)
Again - everything from this setup is working ok in the local environment.
IMPORTANT
We are using two different domains for the backend and frontend - frontend has a different domain than a backend
The solution was to completely change the callback URL generation
For anyone looking for a solution in the future:
class LinkedInLogin(SocialLoginView):
adapter_class = CustomAdapterLinkedin
client_class = OAuth2Client
#property
def callback_url(self):
callback_url = reverse('linkedin_oauth2_callback')
site = Site.objects.get_current()
return f"{settings.HTTP_PROTOCOL}://{site}{callback_url}"
Custom adapter:
class CustomAdapterLinkedin(LinkedInOAuth2Adapter):
def get_callback_url(self, request, app):
callback_url = reverse(provider_id + "_callback")
site = Site.objects.get_current()
return f"{settings.HTTP_PROTOCOL}://{site}{callback_url}"
It is important to change your routes therefore for URL generation:
path('rest-auth/linkedin/url/', OAuth2LoginView.adapter_view(CustomAdapterLinkedin))
I am leaving this open since I think this is not expected behaviour.

IdentityServer4 - How to Logout Blazor Webassembly Client from IdentityServer

i am playing around with Blazor WASM and IdentityServer4. Login/Logut flows invoked from the client are all working well. Used Microsofts documentation found here Microsofts Docs
IdentityServer4 is hosted as a seperate Microservice as well as the Blazor WASM App - two indepented projects.
Now i am facing the problem of signing out from the IdentiyServer4. Invoking the logout from the IdentityServer4 UI doesnt logout the user from the Blazor WASM App. I already read this explenation signout IdentityServer4
"oidc": {
"Authority": "http://localhost:8010/",
"ClientId": "demoportal.blazor",
"DefaultScopes": [
"openid",
"profile"
],
"PostLogoutRedirectUri": "http://localhost:8070/authentication/logout-callback",
"RedirectUri": "http://localhost:8070/authentication/login-callback",
"ResponseType": "code"
}
I haven´t found anything so far to achieve the goal. From my unterstanding it has to be used as oidc connect session managements not front or backend channel policy. But i cant find any useful docs on microsofts site.
After lots of reading ive found the answer.
Microsoft descripes the SPA difficulties right here: Microsoft Handle-Token-Request-Errors
These pointed me to implement on my base component something like this:
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
var user = (await authenticationStateTask).User;
if (user.Identity.IsAuthenticated)
{
var tokenResult = await AccessTokenProvider.RequestAccessToken();
if(tokenResult.Status == AccessTokenResultStatus.RequiresRedirect)
{
NavigationManager.NavigateTo(tokenResult.RedirectUrl);
}
}
}
It works like a charm.
Btw dont forget to include the token when configuring HttpClient.
services.AddHttpClient<YOURSERVICEHERE>()
.AddHttpMessageHandler(sp =>
{
var handler = sp.GetService<AuthorizationMessageHandler>()
.ConfigureHandler(
authorizedUrls: new[] { "URI here" },
scopes: new[] { "your scope here" });
return handler;
})
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>()

Authentication with oidc-client.js and Identityserver4 in a React frontend

Lately I'm trying to set-up authentication using IdentityServer4 with a React client. I followed the Adding a JavaScript client tutorial (partly) of the IdentityServer documentation: https://media.readthedocs.org/pdf/identityserver4/release/identityserver4.pdf also using the Quickstart7_JavaScriptClient file.
The downside is that I'm using React as my front-end and my knowledge of React is not good enough to implement the same functionality used in the tutorial using React.
Nevertheless, I start reading up and tried to get started with it anyway. My IdentityServer project and API are set-up and seem to be working correctly (also tested with other clients).
I started by adding the oidc-client.js to my Visual Code project. Next I created a page which get's rendered at the start (named it Authentication.js) and this is the place where the Login, Call API and Logout buttons are included. This page (Authentication.js) looks as follows:
import React, { Component } from 'react';
import {login, logout, api, log} from '../../testoidc'
import {Route, Link} from 'react-router';
export default class Authentication extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div>
<button id="login" onClick={() => {login()}}>Login</button>
<button id="api" onClick={() => {api()}}>Call API</button>
<button id="logout" onClick={() => {logout()}}>Logout</button>
<pre id="results"></pre>
</div>
<div>
<Route exact path="/callback" render={() => {window.location.href="callback.html"}} />
{/* {<Route path='/callback' component={callback}>callback</Route>} */}
</div>
</div>
);
}
}
In the testoidc.js file (which get's imported above) I added all the oidc functions which are used (app.js in the example projects). The route part should make the callback.html available, I have left that file as is (which is probably wrong).
The testoidc.js file contains the functions as follow:
import Oidc from 'oidc-client'
export function log() {
document.getElementById('results').innerText = '';
Array.prototype.forEach.call(arguments, function (msg) {
if (msg instanceof Error) {
msg = "Error: " + msg.message;
}
else if (typeof msg !== 'string') {
msg = JSON.stringify(msg, null, 2);
}
document.getElementById('results').innerHTML += msg + '\r\n';
});
}
var config = {
authority: "http://localhost:5000",
client_id: "js",
redirect_uri: "http://localhost:3000/callback.html",
response_type: "id_token token",
scope:"openid profile api1",
post_logout_redirect_uri : "http://localhost:3000/index.html",
};
var mgr = new Oidc.UserManager(config);
mgr.getUser().then(function (user) {
if (user) {
log("User logged in", user.profile);
}
else {
log("User not logged in");
}
});
export function login() {
mgr.signinRedirect();
}
export function api() {
mgr.getUser().then(function (user) {
var url = "http://localhost:5001/identity";
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = function () {
log(xhr.status, JSON.parse(xhr.responseText));
}
xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
xhr.send();
});
}
export function logout() {
mgr.signoutRedirect();
}
There are multiple things going wrong. When I click the login button, I get redirected to the login page of the identityServer (which is good). When I log in with valid credentials I'm getting redirected to my React app: http://localhost:3000/callback.html#id_token=Token
This client in the Identity project is defined as follows:
new Client
{
ClientId = "js",
ClientName = "JavaScript Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
// where to redirect to after login
RedirectUris = { "http://localhost:3000/callback.html" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:3000/index.html" },
AllowedCorsOrigins = { "http://localhost:3000" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
Though, it seems the callback function is never called, it just stays on the callback url with a very long token behind it..
Also the getUser function keeps displaying 'User not logged in' after logging in and the Call API button keeps saying that there is no token. So obviously things are not working correctly. I just don't know on which points it goes wrong.
When inspecting I can see there is a token generated in the local storage:
Also when I click the logout button, I get redirected to the logout page of the Identity Host, but when I click logout there I don't get redirected to my client.
My questions are:
Am I on the right track implementing the oidc-client in combination with IdentityServer4?
Am I using the correct libraries or does react require different libraries than the oidc-client.js one.
Is there any tutorial where a react front-end is used in combination with IdentityServer4 and the oidc-client (without redux), I couldn't find any.
How / where to add the callback.html, should it be rewritten?
Could someone point me in the right direction, there are most likely more things going wrong here but at the moment I am just stuck in where to even begin.
IdentityServer4 is just a backend implementation of OIDC; so, all you need to do is implement the flow in the client using the given APIs. I don't know what oidc-client.js file is but it is most likely doing the same thing that you could have implemented yourself. The flow itself is very simple:
React app prepares the request and redirects the user to the Auth server with client_id and redirect_uri (and state, nonce)
IdentityServer checks if the client_id and redirect_uri match.
If the user is not logged in, show a login box
If a consent form is necessary (similar to when you login via Facebook/Google in some apps), show the necessary interactions
If user is authenticated and authorized, redirect the page to the redirect_uri with new parameters. In your case, you the URL will look like this: https://example.com/cb#access_token=...&id_token=...&stuff-like-nonce-and-state
Now, the React app needs to parse the URL, access the values, and store the token somewhere to be used in future requests:
Easiest way to achieve the logic is to first set a route in the router that resolves into a component that will do the logic. This component can be "invisible." It doesn't even need to render anything. You can set the route like this:
<Route path="/cb" component={AuthorizeCallback} />
Then, implement OIDC client logic in AuthorizeCallback component. In the component, you just need to parse the URL. You can use location.hash to access #access_token=...&id_token=...&stuff-like-nonce-and-state part of the URL. You can use URLSearchParams or a 3rd party library like qs. Then, just store the value in somewhere (sessionStorage, localStorage, and if possible, cookies). Anything else you do is just implementation details. For example, in one of my apps, in order to remember the active page that user was on in the app, I store the value in sessionStorage and then use the value from that storage in AuthorizeCallback to redirect the user to the proper page. So, Auth server redirects to "/cb" that resolves to AuthorizeCallback and this component redirects to the desired location (or "/" if no location was set) based on where the user is.
Also, remember that if the Authorization server's session cookie is not expired, you will not need to relogin if the token is expired or deleted. This is useful if the token is expired but it can be problematic when you log out. That's why when you log out, you need to send a request to Authorization server to delete / expire the token immediately before deleting the token from your storage.

Sending Azure token to API back-end keeps returning 401 Unauthorzied error when OnTokenValidated is added to the API

Recently we have created a React front-end which communicates with our API back-end following this tutorial: https://itnext.io/a-memo-on-how-to-implement-azure-ad-authentication-using-react-and-net-core-2-0-3fe9bfdf9f36
Just as in the tutorial we have set-up the authentication in the front-end with the adal-react library. We added/registered the front-end in azure.
Next we created our API (.Net Core 2) and also registered this in the azure environment, the config is setup in the appsettings:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantDomain": "our_azure_environment.onmicrosoft.com",
"TenantId": "our_azure_environment.onmicrosoft.com",
"ClientId": "our_front-end_azure_id_1234"
}
In the API we also added the JWT middleware in the ConfigureServices as follow:
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Audience = Configuration["AzureAd:ClientId"];
options.Authority = $"{Configuration["AzureAd:Instance"]}{Configuration["AzureAd:TenantId"]}";
});
When testing (calling an endpoint from the front-end) after logging in the front-end works, the data is being returned and the user is authenticated (api endpoint has the Authorize attribute), when not logged in the api endpoint returns 401 (as it should).
The problem is as follows:
When I add the following piece of code to the API ConfigureServices (which I want to use to do some additional stuff after authenticating) :
options.Events = new JwtBearerEvents()
{
OnTokenValidated = context =>
{
//Check if user has a oid claim
if (!context.Principal.HasClaim(c => c.Type == "oid"))
{
context.Fail($"The claim 'oid' is not present in the token.");
}
return Task.CompletedTask;
}
};
suddenly, the calls to the API endpoint return a 401 (Unauthorized) error when logged in.. Though, if I remove the OnTokenValidated part it works fine.
When reaching the OnTokenValidated, the token should already be validated / authenticated or am I wrong?
IntelliSense also says; Invoked after the security token has passed validation and a ClaimsIdentity has been generated.
Did I forgot to add some setting? My feeling tells me that it is propably a wrong setup in azure itself but I have actually no clue.
The same token which is send from the front-end to the API is also being send to the graph API, when doing this, graph asks to give consent and after agreeing it works. With this in mind I believe I should add some permission to the API or something but I am not sure.
UPDATE
juunas pointed out in his comment below that I was using the wrong ClaimsPrincipal value this fixed the initial problem but now the following gave me the 401 error:
In my ConfigureServices (before the AddAuthentication part) I have added the following to manage / add users to my AspNetUsers table (in my azure database):
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<TRSContext>()
.AddDefaultTokenProviders();
When adding this code to the pipeline, I once more get the 401 error in the front-end. Any clue why this is?
UPDATE2
I found the solution for above (update). This was caused due to AddIdentity taken over the Authentication from JWT. This can be avoided by adding:
Options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
Options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
to .AddAuthentication options:
services.AddAuthentication(Options =>
{
Options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
Options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
Options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
More information about the above can be found here:
https://github.com/aspnet/Identity/issues/1376
The error appears in the first case due to the fact that .NET ClaimsPrincipal objects translate the oid claim type to: http://schemas.microsoft.com/identity/claims/objectidentifier.
So it needs to be like:
options.Events = new JwtBearerEvents()
{
OnTokenValidated = context =>
{
//Check if user has a oid claim
if (!context.Principal.HasClaim(c => c.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier"))
{
context.Fail($"The claim 'oid' is not present in the token.");
}
return Task.CompletedTask;
}
};

Resources