AzureAD / microsoft-authentication-library-for-js

Microsoft Authentication Library (MSAL) for JS
http://aka.ms/aadv2
MIT License
3.64k stars 2.65k forks source link

Improve documentation and code samples on auto redirect after login #3092

Closed svdHero closed 3 years ago

svdHero commented 3 years ago

Library

Documentation location

Description

I am writing a React-SPA, I am new to MSAL and for the last two days I have been trying to figure out how the whole login and redirect process works. In my case, I would like my app to redirect users automatically to the sub-page from where they started the login process.

When it comes to auto redirect there are two relevant properties in BrowserAuthOptions that I can change:

The documentation about the relationship between those two are a bit confusing. the msal-react doc says:

redirectUri : URI where the authorization code response is sent back to. Whatever location is specified here must have the MSAL library available to handle the response.

navigateToLoginRequestUrl: If true, will navigate back to the original request location before processing the authorization code response. If the redirectUri is the same as the original request location, this flag should be set to false.

The FAQ says:

If navigateToRequestUrl property in MSAL configuration parameters is set to true, you will be redirected again to the page you were on when you called loginRedirect, unless that page was also set as your redirectUri. On the final page your application must call handleRedirectPromise() in order to process the hash and cache tokens in local/session storage. [...] Please review one of our samples (for instance) to see the redirect flow in action.

I have several questions here:

  1. It says "If the redirectUri is the same as the original request location, this flag should be set to false". Why? What is the danger here? An infinite loop of redirects? The documentation should give more explanation on this.
  2. Some user start the login process from the homepage (meaning the root of my app), others that have bookmarked a deep link into my app start the login process from some other sub-page. So some bookmark could actually end up being the redirectUri itself. So should I set navigateToLoginRequestUrl to trueor false then?
  3. It does not help that all of the MSAL code samples set redirectUri to http://localhost:3000/ which is the root url of the whole app. This is misleading. With respect to question 2 and navigateToLoginRequestUrl, am I right that redirectUri should be set to something like http://localhost:3000/login-redirect rather than the root url / index of my web app? In production where my app is accessible via https://mycompany.com/my-app, the redirectUri should be something like https://mycompany.com/my-app/login-redirect in order to avoid conflicts, right? Maybe the code samples could be adapted in order to make this more clear.
  4. What does "final page" in the quote above exactly mean? And why should handleRedirectPromise be called on said "final page". I thought it should be called by the page under redirectUri in order to reach another (final?) page. The wording is highly confusing.
  5. Where, when, how should I call handleRedirectPromise in a React app? The same way as in the provided vanilla JS sample above?

This article says:

The state parameter can also be used to encode information of the app's state before redirect. You can pass the user's state in the app, such as the page or view they were on, as input to this parameter.

This seems to suggest that I have to write the login request url to the AuthenticationParameters.state manually? But that is not true, right. MSAL does that behind the scenes, doesn't it? That is exactly what PuclicClientApplication.handleRedirectPromise is doing automagically and out-of-the-box. Is that correct?

I would be extremely grateful for answers to my questions above as well as improvement of the related documentation and code samples. I am sure this will help other MSAL beginners, too.

tnorling commented 3 years ago

@svdHero Sorry for the confusion, we'll see what we can do to make the docs more clear. In the meantime, I'll try to clear some of it up for you

It says "If the redirectUri is the same as the original request location, this flag should be set to false". Why? What is the danger here? An infinite loop of redirects? The documentation should give more explanation on this.

This should be corrected in the docs. It should say that it can be set to false. But it's perfectly fine to set it to true.

Some user start the login process from the homepage (meaning the root of my app), others that have bookmarked a deep link into my app start the login process from some other sub-page. So some bookmark could actually end up being the redirectUri itself. So should I set navigateToLoginRequestUrl to trueor false then?

This depends. If you register all your pages as redirectUris in the Azure Portal you can have each request set the redirectUri to it's own url and set navigateToLoginRequestUrl to false. However, if you'd like to use a single central redirectUri, such as your homepage, for all redirects you can set navigateToLoginRequestUrl to true in order to ensure the user ultimately ends up back on the original page they started authentication from.

It does not help that all of the MSAL code samples set redirectUri to http://localhost:3000/ which is the root url of the whole app. This is misleading. With respect to question 2 and navigateToLoginRequestUrl, am I right that redirectUri should be set to something like http://localhost:3000/login-redirect rather than the root url / index of my web app? In production where my app is accessible via https://mycompany.com/my-app, the redirectUri should be something like https://mycompany.com/my-app/login-redirect in order to avoid conflicts, right? Maybe the code samples could be adapted in order to make this more clear.

The code samples demonstrate the single redirectUri pattern explained above, which is the most common. If you'd like to use the multi redirectUri pattern you should not need to make any changes other than changing the redirectUri

What does "final page" in the quote above exactly mean? And why should handleRedirectPromise be called on said "final page". I thought it should be called by the page under redirectUri in order to reach another (final?) page. The wording is highly confusing.

This means the page that the user started on. Let's say your user clicks a login button on http://localhost:3000/login-redirect but you set your redirectUri to http://localhost:3000/ and set navigateToLoginRequestUrl to true. The order of redirections will look like this:

http://localhost:3000/login-redirect -> login.microsoftonline.com -> http://localhost:3000/ -> http://localhost:3000/login-redirect

In this scenario you need to call handleRedirectPromise on http://localhost:3000/ where it will notice it is not on the /login-redirect route and redirect you to it. Then you need to call handleRedirectPromise again on http://localhost:3000/login-redirect in order to process the response from the authentication server and save the tokens.

Where, when, how should I call handleRedirectPromise in a React app? The same way as in the provided vanilla JS sample above?

msal-react does this for you under the hood. You do not need to, and should not, call it yourself. See FAQ and React Samples for more guidance on working with redirects in msal-react.

svdHero commented 3 years ago

@tnorling Thank you for the very detailed answer. That helped a lot. In particular, I did not expected handleRedirectPromise to be called twice. Thanks for pointing that out.

For better context, I definitely want to use the single redirectUri pattern where I only register one single redirectUri in the Azure Portal. Said single redirectUri in Azure Portal is what I meant by http://localhost:3000/login-redirect. Maybe I did not explain that well enough. I believe you interpreted that as http://localhost:3000/deep-link/where-login-process-starts (aka loginRequestUrl). So in my scenario the order of redirection would be:

http://localhost:3000/deep-link/where-login-process-starts -> login.microsoftonline.com -> http://localhost:3000/login-redirect -> http://localhost:3000/deep-link/where-login-process-starts

or in case the login process was triggered on the homepage (app root): http://localhost:3000/ -> login.microsoftonline.com -> http://localhost:3000/login-redirect -> http://localhost:3000/

Your explanation was great. Did I make the correct logical transfer?

I have two quick follow-up questions:

  1. Could you pin-point the exact source code line where handleRedirectPromise actually makes the browser change its url (first call) and where it saves the tokens (second call). I believe this is triggered inside this method, but I don't really understand the whole event emitting code.

  2. In msal-react all the above magic happens through the React component <MsalProvider> by this call. Is that correct?

Thanks for your help.

tnorling commented 3 years ago

@svdHero

Your explanation was great. Did I make the correct logical transfer?

Yes what you've shared is how it's expected to work.

Could you pin-point the exact source code line where handleRedirectPromise actually makes the browser change its url (first call) and where it saves the tokens (second call). I believe this is triggered inside this method, but I don't really understand the whole event emitting code.

Navigation here and handling the response here

In msal-react all the above magic happens through the React component by this call. Is that correct?

Correct.

svdHero commented 3 years ago

I managed to get my React app up and running with auto redirect working great. Thank you so much, @tnorling .

One final question: How about the opposite process, i.e., the logout process?

My use case is one where locale (language and region) is encoded in the url. So let's assume the user is on http://localhost:3000/de-DE/my-sub-page. Via auto redirect he/she ends up on this very page again after the login redirect. However, what's the best practice using postLogoutRedirectUri for making sure that the user ends up on http://localhost:3000/de-DE/logout and not on, e.g., http://localhost:3000/en-US/logout after the logout redirect?

Do I have to handle that myself in sessionStorage? That would be a shame, because since the locale is already in the url, I would not want to duplicate language state management both in storage and in the url. Does MSAL provide any tools for that? Is there any state than can be attached to the logout request similar to the login request?

tnorling commented 3 years ago

@svdHero Unfortunately after logout the service redirects back to the postLogoutRedirectUri without any additional information. When MSAL loads on a page it is not aware it is returning from a logout, it's like loading the page for the first time.

You currently have 2 options:

  1. Explicitly pass the postLogoutRedirectUri you would like to be returned to, including locale
  2. Provide a single page as postLogoutRedirectUri, such as a blank, region agnostic page, and have that page auto redirect to the correct locale (you would have to handle the logic to do this)
svdHero commented 3 years ago

Ok. Thanks for clarifying.

I think I've got a good overview of the login process now. Let me know if and how I can help to improve the documentation of this, in case you see the need to do that.

github-actions[bot] commented 3 years ago

This issue has not seen activity in 14 days. If your issue has not been resolved please leave a comment to keep this open. It will be closed in 7 days if it remains stale.

github-actions[bot] commented 3 years ago

This issue has been closed due to inactivity. If this has not been resolved please open a new issue. Thanks!