3
votes

I have a single page Javascript application (SPA). I have a middle-tier Azure Function service that uses TypeScript/NodeJS. The SPA allows users to login and, on demand, contact the middle tier service to retrieve calendar data from the MS Graph. The middle tier then needs to be able to periodically retrieve that calendar data again, on behalf of the user, without another explicit request from the client.

I need to support both Azure AD accounts and personal MSA accounts. I have one AAD app registration for the SPA and one for the middle-tier service. For Azure AD accounts, this all "just works" because the the middle tier receives an access token from the client, exchanges it for an access token + refresh token from AAD, then retrieves data from the MS Graph API using the access token which it periodically refreshes by using the refresh token it got as part of the OBO flow.

I know that making this work with personal MSA accounts is more challenging because of the lack of combined consent. The only suggestion I could find about how to make this work with MSA was on this page under the "Use of a single application" section. However, I cannot seem to make this work.

If I change my SPA to request all the same scopes that my middle tier needs (https://graph.microsoft.com/Calendars.Read openid profile email offline_access) when calling acquireTokenSilent (on the MSAL library), the correct consent dialog shows but the access token I get back is not a JWT. I understand that this can be expected (because of the "Important" note at the top of this page), however that causes problems when I try to do the OBO flow with my middle tier. If I take that non-JWT access token, send it to my middle tier service (which now identifies itself using the same AAD app registration as the SPA), then try to call the AAD endpoint for the OBO flow, I get this error:

AADSTS50027: JWT token is invalid or malformed.
Trace ID: a3647c0e-e7f5-4919-a457-1bd1e951f501
Correlation ID: a44f9292-1ba0-43b7-aafe-0a77e5adbc35
Timestamp: 2019-12-04 18:52:58Z

...so it's clearly expecting a JWT.

Is there a way to make this OBO flow work with personal MSAs? Or is this OBO flow not the correct thing to do when using personal MSA's with a middle tier that needs a refresh token (so it can periodically re-call the MS Graph API on behalf of the user)? If it's not the right thing to do, how can my middle tier get a working refresh token so it can periodically retrieve a new access token?

Note: I have read this SO post but it does not appear to address the issue I'm having -- I understand that a common OBO pattern cannot be used for both AAD and personal MSA accounts (frustratingly) but I am having trouble getting any personal MSA OBO flow working, ignoring for a moment how much code, if any, I might be able to share between the Azure AD and personal MSA OBO flows.

Thanks!

1

1 Answers

0
votes

For the problem you've stated, if the front end can get a successful consent from both AAD and MSA users for Graph, you might not need an OBO call to get what you seek.

I suggest you utilize the MSAL's token caching feature to get what you seek here.

MSAL provides the ability to cache tokens externally, specifically in your case the refresh tokens. A detailed walk-through is provided here.

AAD V2 allows an app id to be used for multiple instances of the same app, and its recommended more so especially if they share the same topology deployment unit.

Once your app's front end and backend have the same app id.

  1. Implement an external token cache for the app.
  2. Initialize both the front end and the backend against the same token cache.
  3. Make sure the front end gets the necessary consent for all the required scopes.
  4. The backend would be able to get the consented user's AT from cache whenever it wants. MSAL abstracts the internal process of using the RT to get a fresh AT. This is possible becuase the app id is the same.
  5. If MSAL in your backend fails (and it periodically will, like when user changes their pwd), save this event somewhere and advise the user to go through the sign-in process on the front end again.