Getting Roles from Token - oauth-2.0

I'm trying to verify a role type from an azure web token. This is my authorization policy.
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o => {
o.Authority = Configuration["Identity:Authority"];
o.RequireHttpsMetadata = false;
});
services.AddAuthorization(options => {
options.AddPolicy("ApiAccess", policy => {
policy.RequireClaim(System.Security.Claims.ClaimTypes.Role, "apiAccess");
});
});
I can see within the token provided there is roles with my role type in it. But if I try to access any page in my web app it shows that I am not of the correct role.
"roles": [
"apiAccess"
],

Are you sure that System.Security.Claims.ClaimTypes.Role resolves to roles? What if you used roles string directly:
policy.RequireClaim("roles", "apiAccess");
or use the .RequireRole method instead?
policy.RequireRole("apiAccess")

Related

Facing with error when redirecting to OpenID connect authorization server endpoint

I am getting error whenever my client application make request to my authorization server which uses OpenID connect (please refer below screenshot).
I have setup the authorization server at https://localhost:5001/. The Authorization Server is working as intended when I tested with Angular client app which I found on the internet. But when I tried it with my .net core mvc app, I'm getting the afore mentioned error.
I have configured my mvc app for OpenId connect as follows:
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.ClientId = "spa_client";
options.ClaimsIssuer = "https://localhost:5001/";
options.Authority = "https://localhost:5001/";
options.ResponseType = OpenIdConnectResponseType.Code;
options.SignedOutRedirectUri = "https://localhost:7077/";
//options.GetClaimsFromUserInfoEndpoint = true;
options.RequireHttpsMetadata = false;
});
builder.Services.AddAuthorization();
It gets redirected to the authorization server endpoint https://localhost:5001. However, I'm always getting the error screen. The working Angular configuration for OpenID connect:
const redirUri = isDevMode()
? 'http://localhost:4200'
: window.location.origin;
const devModeIssuer = 'https://localhost:5001/';
this.oauthService.configure({
clientId: 'spa_client',
issuer: isDevMode()
? devModeIssuer
: window.location.origin + '/',
redirectUri: redirUri,
responseType: 'code',
scope: 'openid roles email server_scope api_scope',
requireHttps: false
});
this.oauthService.events.subscribe(async (e: OAuthEvent) => {
if (e.type === 'token_received' || e.type === 'token_refreshed') {
this.user.loadProfile();
}
if (e.type === 'discovery_document_loaded' && this.oauthService.hasValidAccessToken()) {
this.user.loadProfile();
}
});
this.oauthService.loadDiscoveryDocumentAndLogin({
onTokenReceived: () => {
this.user.loadProfile();
}
});
Where do I get it wrong?

MSAL access token refreshing not working through AcquireTokenSilent

I am implementing MSAL authentication for our SPA. I've followed various official and unofficial guides and so far this is my auth implementation:
loginMicrosoft() {
myMSALObj
.loginPopup(msalConfig.loginRequest)
.then((response) => {
console.log(response);
this.username = response.account.userName;
this.account = response.account;
})
.catch((error) => {
console.error(error);
});
},
readEvents() {
this.getTokenPopup(msalConfig.tokenRequest)
.then((response) => {
console.log("silent token!: ", response);
this.callMSGraph(
graphConfig.graphConfig.graphGetCalendarEventsEndpoint,
response.accessToken
);
})
.catch((error) => {
console.error(error);
});
},
createEvent() {
this.getTokenPopup(msalConfig.tokenRequest)
.then((response) => {
this.callMSGraphCreateEvent(
graphConfig.graphConfig.graphCreateCalendarEventEndpoint,
response.accessToken
);
})
.catch((error) => {
console.error(error);
});
},
getTokenPopup(request) {
request.account = this.account;
console.log(request);
return myMSALObj.acquireTokenSilent(request).catch((error) => {
console.warn(
"silent token acquisition fails. acquiring token using popup : ",
error
);
if (error instanceof Msal.InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return myMSALObj
.acquireTokenPopup(request)
.then((tokenResponse) => {
console.log(tokenResponse);
return tokenResponse;
})
.catch((error) => {
console.error(error);
});
} else {
console.warn(error);
}
});
},
async callMSGraph(endpoint, token) {
console.log("request made to Graph API at: " + new Date().toString());
const resp = await axios.post("/api/microsoft/get-events", { endpoint, token });
this.calendarEvents = resp.data.value;
console.log("vaste: ", resp);
},
async callMSGraphCreateEvent(endpoint, token) {
console.log("request made to Graph API at: " + new Date().toString());
const resp = await axios.post("/api/microsoft/create-event", {
endpoint,
token,
});
this.calendarEvents = resp.data.value;
console.log("vaste: ", resp);
},
Everything works as intended, until the access token is reaching expiry.
If AcquireTokenSilent is called 5 minutes before the expiration of after the expiration of the access token, I would expect it to return a new access token, using the hidden refresh token in the MSAL cache. Instead, I get the following error:
silent token acquisition fails. acquiring token using popup :
InteractionRequiredAuthError: Silent authentication was denied. The
user must first sign in and if needed grant the client application
access to the scope 'User.Read Calendars.ReadWrite openid
profile'.
It doesn't seem to be normal to ask for user sign-in every hour, and I cant seem to find any resources on this issue..
Any help is greatly appreciated!
EDIT:
I tried adding offline_access to my token request scopes. My scopes setup is following:
export const loginRequest = {
scopes: ["User.Read", "Calendars.ReadWrite", "offline_access"],
};
export const tokenRequest = {
scopes: ["User.Read", "Calendars.ReadWrite", "offline_access"],
forceRefresh: false, // Set this to "true" to skip a cached token and go to the server to get a new token
};
Now im getting the login popup with the following error every time i try to call the api:
InteractionRequiredAuthError: Silent authentication was denied. The user must first sign in and if needed grant the client application access to the scope 'User.Read Calendars.ReadWrite offline_access openid profile'.
Probably your scopes (in your app registration or in your msal config, depending on where you define your config and if you are using .default scope) do not include the request for the offline_access scope. Please include it, it is required if you want to (auto-) refresh your tokens. If you don't get a new consent prompt after adding this scope (user must agree, i.e. give consent to this), just reset your app consents in azure portal, or consent manually.

Identity Server 4 and auto redirect on sign out

Playing around with a demo project from PluralSight, I am trying to have the IDP redirect back to the server app on sign out.
The PostLogOutRedirectUris is defined in the config for the Client at the IDP level, but it doesn't seem to have any effect.
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "bethanyspieshophr",
ClientName = "Bethany's Pie Shop HRM",
AllowOfflineAccess = true,
AccessTokenLifetime = 120,
RequireConsent = false,
RequirePkce = true,
AllowedGrantTypes = GrantTypes.Code,
ClientSecrets = {
new Secret("108B7B4F-BEFC-4DD2-82E1-7F025F0F75D0".Sha256()) },
RedirectUris = { "https://localhost:44301/signin-oidc" },
PostLogoutRedirectUris = { "https://localhost:44301/signout-oidc" },
AllowedScopes = { "openid", "profile", "email", "bethanyspieshophrapi" }
}
};
If I manually at runtime set the LoggedOutViewModel it works as expected.
How are you performing the logout request? Remember that the value in the client settings is just the registered value which is verified during logout request. The user is not redirected automatically to the postLogoutRedirectUri. You have to pass a post_logout_redirect_uri parameter to the end session endpoint and this parameter must match on of the values in the PostLogoutRedirectUris setting. To use this feature you should also post a valid ID token in the id_token_hint parameter, so that the server knows which client is requesting the logout.
You can have a look at the end session enpoint docs for details.
The issue was simply due to a typo, which sent me on a wild goose chase.
PostLogoutRedirectUris = { "https://localhost:44301/signout-oidc" },
Should be
PostLogoutRedirectUris = { "https://localhost:44301/signout-callback-oidc" },
And then, it worked.

Client is getting "Forbidden" on UserInfo endpoint

So we've set up an IDP (IdentityServer4, Core2) and have been using it for our own applications without problems (Implicit Flow). Now though, one of our partners will be using our IDP to make API requests from another application.
We've setup the ApiResources:
new ApiResource("api", "API",
new List<string>() {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Profile,
"role",
"team"
})
The client in question:
new Client {
ClientName= "ClientName",
Description = "ClientDescription",
LogoUri = "/img/ClientLogos/clientLogo.png",
ClientUri = "https://client.url",
RedirectUris = {
"https://...",
"https://...",
"http://...",
"http://..."
},
AllowedGrantTypes = GrantTypes.Code,
RequireConsent = true,
ClientId = "clientId",
AllowedScopes = { "api" },
ClientSecrets = { new Secret("clientSecret".Sha256()) },
AlwaysSendClientClaims = true,
AllowOfflineAccess = true,
Claims = {
...
}
}
I (wrongfully) assumed that since the client has the "api" scope, which in turn has the "OpenID" and "Profile" scope, the client would automatically gain authorization to use the UserInfo endpoint, but they are getting the "Forbidden" StatusCode.
Can someone explain to me what we're doing wrong here?
I think you need to include IdentityResources as well, because it dictates whats part of the ID-token and what is available from the UserInfo endpoint.

Laravel 5 and Sentinel 2

I'm a complete starter using laravel 5.1. I was a PHP developer by 3 to 4 years and between those I was allways working with Java EE and I just came back to PHP environement and found a complete new list of frameworks.
After a little research, and using some surveys results, I found that Laravel is the ultimate one. Now I used Laragon to install it successfully and have my first fresh application running. I learning a little about how a route works and that's ok.
Now I need to use Sentinel 2.0 in order to apply the right roles/auth to my application and then add the socialize part.
So to do that, I need to know few things :
Is there any way to "completely" get rid of the Auth component beside removing the controller Auth folder and the route in routes.php ?
Is there any tutorial (as I can't find) telling how to REALLY include the sentinel means how to create a simple view with all what it needs (controller, vars, routes ....)
Thank you
Yes, you can. For example, this is my code for API rest with JWT and Sentinel. You can seed your database with Sentinel:
Create roles
Example EXA Role
$role = \Sentinel::getRoleRepository()->createModel()->create([
'name' => 'Example',
'slug' => 'EXA',
]);
$role->permissions = [
'servicio_dash' => true,
'servicio_widget' => true,
];
$role->save();
User Role USR
$role = \Sentinel::getRoleRepository()->createModel()->create([
'name' => 'User',
'slug' => 'USR',
]);
$role->permissions = [
'servicio_dash' => true,
'servicio_widget' =>false,
];
$role->save();
Create 50users and asignate EXA role(Using faker)
$usr_role = \Sentinel::findRoleBySlug('EXA');
factory(App\User::class, 50)->make()->each(function ($u) use ($usr_role) {
\Sentinel::registerAndActivate($u['attributes']);
});
Bonus Track: Factory example
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'email' => $faker->safeEmail,
'password' => 'p4ssw0rd',
'first_name' => $faker->firstName,
'last_name' => $faker->lastName,
'recycle' => false,
'phone' => $faker->phoneNumber,
'alt_email' => $faker->email
];
});
Only one user
$yo = factory(App\User::class)->make(['email' => 'jpaniorte#openmailbox.org']);
\Sentinel::registerAndActivate($yo['attributes']);
$jperez = User::where('email', 'jpaniorte#openmailbox.org')->firstOrFail();
$epa_role->users()->attach($jperez);
Authenticate Controller for API REST
public function authenticateCredentials(Request $request)
{
$credentials = $request->only('email', 'password');
$user = \Sentinel::authenticate($credentials);
return response()->json($user);
}
Authenticate with token (use JWT) and sentinel
public function authenticate(Request $request)
{
// grab credentials from the request
$credentials = $request->only('email', 'password');
try {
// attempt to verify the credentials and create a token for the user
if (!$token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
// something went wrong whilst attempting to encode the token
return response()->json(['error' => 'could_not_create_token'], 500);
}
// all good so return the token
return response()->json(compact('token'));
}
Note: For this, you need configure JWT options with custom Auth provider, you can find this here
In any controller
public function hasPermission($type)
{
//$sentinel = \Sentinel::findById(\JWTAuth::parseToken()->authenticate()->id); //->this is for a token
$sentinel = \Sentinel::findById(1); //if you now the id
if($sentinel->hasAccess([$type]))
return response()->json(true, 200);
//yout custom handle for noAccess here
}

Resources