Should I try an MSAL getTokenSilent() before any request for a protected resource? - ember.js

I'm integrated the MSAL JS library with an EmberJS SPA. In the current implementation I have a getTokenSilent() call before a user requests a "protected" resource. This means that after a user Authenticates, they collect a new access token before the resource is requested.
Is this intended? Aside from an extra network request I don't see many drawbacks. It should allow for very short-lived access tokens and allow MSAL to check that a user is still in a "valid" state (ie they haven't had their account disabled) since they logged in.
I'm very new to OAUTH and MSAL so some guidance would be great.

The pattern is indeed that one. Before making a request to your API, you should call the acquireTokenSilent() function to see if you can retrieve a suitable token silently. That function will do the following:
Checks the cache for a valid token and returns it
If there is no valid token, the function will try to acquire a new one with a refresh token
If both options fails, there will be an InteractionRequiredAuthError thrown, which you need to catch and trigger an acquireTokenRedirect() (or acquireTokenPopup()).
You can see this vanilla JS MSAL sample:
https://github.com/Azure-Samples/ms-identity-javascript-tutorial/tree/main/3-Authorization-II/1-call-api
Check the getTokenRedirect() function:
function getTokenRedirect(request) {
/**
* See here for more info on account retrieval:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md
*/
request.account = myMSALObj.getAccountByUsername(username);
return myMSALObj.acquireTokenSilent(request)
.catch(error => {
console.error(error);
console.warn("silent token acquisition fails. acquiring token using popup");
if (error instanceof msal.InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return myMSALObj.acquireTokenRedirect(request);
} else {
console.error(error);
}
});
}

Related

AWS Amplify Auth.updateUserAttributes() clear localStorage/sessionStorage

I am writing a web-based app that uses AWS Cognito as the authentication service. I use 'aws-amplify' to implement the client-app.
I am using Auth.updateUserAttributes() to update a custom attribute of users on Cognito. However, I found that the call of this function would clear all the Cognito-related items, including idToken, refreshToken, and accessToken stored in localStorage. As a result, the web app behaves like sign-out.
Here is the code about Auth's configuration
Amplify.configure({
Auth: {
userPoolId: process.env.REACT_APP_AWS_COGNITO_USER_POOL_ID,
region: process.env.REACT_APP_AWS_COGNITO_REGION,
userPoolWebClientId: process.env.REACT_APP_AWS_COGNITO_APP_CLIENT_ID,
storage: window.localStorage,
authenticationFlowType: 'CUSTOM_AUTH',
},
});
and the code I wrote to update the user's attribute. (I followed the example code from the amplify docs https://docs.amplify.aws/lib/auth/manageusers/q/platform/js/#managing-user-attributes)
let user = await Auth.currentAuthenticatedUser();
console.log(JSON.stringify(localStorage)); // it outputs the localstorage with idToken,
// refreshToken, accessToken and other items
// start with 'CognitoIdentityServiceProvider'
const result = await Auth.updateUserAttributes(user, {
'custom:attributes_1': '123456789',
});
console.log(result); // OUTPUT: SUCCESS
console.log(JSON.stringify(localStorage)); // Only 'amplify-signin-with-hostedUI'.
// idToken, refreshToken, accessToken and
// other items were gone. No key, no value.
After the last line, I could not interact with the web page anymore. If I refreshed the web page, I found I had signed out and had to sign in again.
It was still the same if I changed the storage for Auth from localStorage to sessionStorage.
Here are my questions:
Is this kind of behavior normal? Does Auth.updateUserAttributes() leads to a force sign-out?
If it's true, is there any way to avoid a mandatory sign-out?
If it's not, what's wrong with my code or configuration? Or should I do some specific configuration for the Cognito service?
Thanks a lot!
Well, I figured it out after reading the source code of aws-amplify.
Behind the call of Auth.userUpdateAttributes, amplify will finally call CognitoUser.refreshSession(refreshToken, callback, clientMetadata) (https://github.com/aws-amplify/amplify-js/blob/f28918b1ca1111f98c231c8ed6bccace9ad9e607/packages/amazon-cognito-identity-js/src/CognitoUser.js#L1446). Inside this function, amplify sends an 'InitiateAuth' request to Coginito. If an error of 'NotAuthorizedException' happens, amplify calls clearCachedUser() that delete everything I mentioned in my question from the localStorage.
There was an error of 'NotAuthorizedException' happening and reported by the network work monitor of Chrome Browser'. I thought it was generated after the sign-out-like behavior. However, it turned out to be triggered because no deviceKey was passed to the request's parameters.
So the whole story was:
I set remember device options in Cognito;
I used 'CUSTOM_AUTH' as the authentication flow type;
When a user successfully signed in to my application, Cognito didn't give the client the deviceKey because of the 'CUSTOM_AUTH' authentication flow type.
When Auth.userUpdateAttributes() was called, CognitoUser.refreshSession() was called behind it. It attached no deviceKey to Cognito when it sent ana request to asked Cognito to refresh the token. The Cognito rejected the request with an error of 'NotAuthorizedException'. The CognitoUser.refreshSession() handled the error and called clearCachedUser() to delete the stored tokens and other info from the localStorage.
My final solution is to turn off the remember device option since I have to use 'CUSTOM_AUTH' as the authentication flow type according to my application's functional requirements.
According to https://aws.amazon.com/premiumsupport/knowledge-center/cognito-user-pool-remembered-devices/, the remember device function only works when the authentication flow type is set as 'USER_SRP_AUTH'.

Postman + NTLM Authentication + Authorization with claims + ASP.NET Core API = 403 Forbidden

We have an ASP.NET Core API that uses Windows Authentication and Claim based identity.
The API has one Controller with multiple Actions. The Actions have different authorization policies.
[Authorize(Policy = "Read")]
[HttpGet]
public async Task<ActionResult<Item>> Read()
{ ... }
[Authorize(Policy = "Write")]
[HttpPost]
public async Task<ActionResult<Item>> Write(Item item)
{ ... }
In Startup.cs we have this:
services.AddAuthorization(options => {
options.AddPolicy("Read", policy => policy.RequireClaim("OurReadType","OurReadValue"));
options.AddPolicy("Write", policy => policy.RequireClaim("OurWriteType","OurWriteValue"));
});
We also have a front end that consumes this API. Everything works fine when the front end application accesses our API. Users have only access to read actions if they have the read claim and the same goes for write actions. When a user that has only the read claim tries to call a write action they'll get a 401 Unauthorized. This is all expected behavior. No problems so far.
The problem starts when we try to access our API from Postman. ONLY from Postman do we get 403 Forbidden errors.
Postman is configured to use NTLM Authentication using my personal username and password. And my account has both read and write claims.
If we remove the [Authorize(Policy = "Read")] annotation from an action, we no longer get the 403 error when calling that action using Postman. This makes me think that the problem is somewhere with postman and claims based authorization.
Does anybody have an idea of what the problem is? I'm fairly new to claims based identity and to using Windows authentication to this extent. So any help is appreciated.

How can I get my Auth0 permissions into the scope claim of the access token for an AWS HTTP API Jwt Authorizer?

I am trying to get an AWS HTTP API JWT Authorizer with scopes on an endpoint to work happily with my Auth0 access tokens.
The JWT Authorizer looks for the necessary scopes in the access token's "scope". I am thinking that this is used for fine-grained authorization. But, Auth0 returns permissions in a "permissions" array rather than in the token's "scope".
Is there a way to get my permissions to show up in the "scope" of the access token so that I can use the JWT Authorizer to handle fine-grained permissions? Or will I need to have my lambda function dissect the authenticated JWT after its gone past the JWT Authorizer?
There is a way to add your user permissions into the scope claim of your token. This thread details two ways to achieve this, thread:
Adding a rule that reads these permissions and copies them into your access_token scope claims.
function (user, context, callback) {
var ManagementClient = require('auth0#2.17.0').ManagementClient;
var management = new ManagementClient({
token: auth0.accessToken,
domain: auth0.domain
});
var params = { id: user.user_id};
management.getUserPermissions(params, function (err, permissions) {
var permissionNames = [];
permissions.forEach(function(obj) { permissionNames.push(obj.permission_name); });
if (err) {
// Handle error.
}
context.accessToken.scope = permissionNames;
callback(null, user, context);
});
}
Using the RBAC feature with TOKEN_DIALECT. Note that information around this is extremely scarce, this post linked below it the only piece of information I have found about it. Also I could not get this to work consistently so personally I use the method #1 listed here. TOKEN_DIALECT
In an ideal world option #2 is really the best and has the least configuration, but I have had issues with this.
Is there a way to get my permissions to show up in the "scope" of the access token so that I can use the JWT Authorizer to handle fine-grained permissions?
This is a bad idea. A JWT token is small (<8 kb according to this answer). What will happen when you have a million resources? Will your "array of permissions" have a million items, too?
https://auth0.com/blog/on-the-nature-of-oauth2-scopes/
scopes are used to express what an application can do on behalf of a given user. (...) scopes are used for handling delegation scenarios (...) overloading scopes to represent actual privileges assigned to the app (as opposed to the delegated permissions mentioned above) is problematic
I work for Auth0 and we're building a solution to handle fine-grained authorization. See https://zanzibar.academy/

How to pass access token in context for particular mutation in apollo graphql client

I am integrating the Microsoft graph API in my application, so currently I am getting access token at the client-side through MSAL npm module, so I need access token at the apollo server to hit the Microsoft graph API's(This access token is only required for only one resolver, for the application authorization I have different access token that I check for every resolver), so is there any way I can add context while mutation like I tried this -
<Mutation mutation={CREATE_MICROSOFT_TEAMS} context={{ microsoftgraphaccesstoken: msalAccessToken }}> I have tried to pass context in the mutation but it doesn't work, is there something like this, or should I pass the token in the argument and then add that in to the headers when my apollo server is going to hit the microsoft graph API's.
There are multiple ways to access the token server-side, the easiest way is to consider the token as part of the mutation variables, since it is specific to one resolver anyway.
Using mutation variables:
// client.js
<Mutation
mutation={CREATE_MICROSOFT_TEAMS}
variables={{ microsoftgraphaccesstoken: msalAccessToken }}
>
// server.js
function createMsTeamsResolver(source, args, context, info) {
console.log(args.microsoftgraphaccesstoken);
}
That way you can also access any arguments as you would normally.

AWS Cognito + aws-amplify: session state always keep user logged in?

I'm using AWS Cognito and aws-amplify to manage user authentication. When I load up my app, I call Auth.currentSession() which seems to always return the user I was logged in as if I do not explicitly log out by calling Auth.signOut().
I'm fine with this should the user choose a "keep user logged in", but if they don't, how would I go about making sure the user gets logged out once they leave the app?
I tried adding an event listener in my login() method but that didn't work i.e. the user was still logged in when I returned to the app:
.
.
.
if (!keepSignedIn) {
window.addEventListener('unload', function(event) {
Auth.signOut();
});
}
I'm pretty sure the logout() method creates a promise - it operates asynchronously. So the page is probably being destroyed before the promise's logout code is executed.
You can confirm this by executing console.log(Auth.signOut());. If it's a promise it'll log Promise { <pending> }
There's no way to halt unloading of the page, as that would be bad if we could.
What you need is a synchronous signout function. Fortunately, you can just clear the browser local storage, which is a synchronous operation. (Local storage is where Amplify stores the auth tokens.)
if (!keepSignedIn) {
window.addEventListener('unload', function(event) {
localStorage.clear();
});
}
Depending on your situation you may need to instead find and remove individual local storage items, instead of clearing them all.
You can clear Amplify cache before Auth.signOut()
import AmplifyCache from '#aws-amplify/cache';
AmplifyCache.clear();