Django, Djoser social auth : State could not be found in server-side session data. status_code 400

Viewed 1332

I'm implementing an auth system with django and react. The two app run respectively on port 8000, 3000. I have implemented the authentication system using the Djoser package. This package uses some dependencies social_core and social_django. Everything seems to be configured ok. I click on login google button...I'm redirected to the google login page and then back to my front-end react app at port 3000 with the state and code parameters on the url.

At this point I'm posting those parameters to the backend. The backend trying to validate the state checking if the state key is present in the session storage using the code below from (social_core/backends/oauth.py)

def validate_state(self):
        """Validate state value. Raises exception on error, returns state
        value if valid."""
        if not self.STATE_PARAMETER and not self.REDIRECT_STATE:
            return None
        state = self.get_session_state()
        request_state = self.get_request_state()
        if not request_state:
            raise AuthMissingParameter(self, 'state')
        elif not state:
            raise AuthStateMissing(self, 'state')
        elif not constant_time_compare(request_state, state):
            raise AuthStateForbidden(self)
        else:
            return state

At this point for some reasons the state session key is not there..and I receive an error saying that state cannot be found in session data ( error below )

{"error":["State could not be found in server-side session data."],"status_code":400}

I recap all the action I do:

  1. Front-end request to backend to generate given the provider google-oauth2 a redirect url. With this action the url is generated also the state key is stored on session with a specific value ( google-oauth2_state ).
  2. Front-end receive the url and redirect to google auth page.
  3. Authentication with google and redirection back to the front-end with a state and code parameters on the url.
  4. Front-end get the data form url and post data to back-end to verify that the state received is equal to the generated on the point (1).

For some reasons the state code is not persisted... Any ideas and help will be really appreciated.

Thanks to all.

4 Answers

ok so this is a common problem while you are working with social auth. I had the same problem for so many times.

The flow:

  1. make a request to http://127.0.0.1:8000/auth/o/google-oauth2/?redirect_uri=http://localhost:3000/ (example)

  2. you will get a authorization_url. if you notice in this authorization_url there is a state presented . this is the 'state of server side'.

  3. now you need to click the authorization_url link.Then you will get the google auth page.After that you will be redirect to your redirect url with a state and a code. Remember this state should be the same state as the server side state .(2)

  4. make post req to http://127.0.0.1:8000/auth/o/google-oauth2/?state=''&code=''. if your states are not the same then you will get some issue.

everytime you wanna login , you need to make a request to http://127.0.0.1:8000/auth/o/google-oauth2/?redirect_uri=http://localhost:3000/ and then to http://127.0.0.1:8000/auth/o/google-oauth2/?state=''&code='' thus you will get the same state.

Without necessary detailed information, I can only tell 2 possible reasons:

  1. You overrode backend with improper session operations(or the user was logged out before auth was finished).
  2. Front-end used incorrect state parameter

You could test social login without front-end, let's say if you're trying to sign in with Google:

  1. Enter the social login URL in browser, like domain.com:8000/login/google-oauth2/
  2. Authorize
  3. See if the page redirected to your default login page correctly

If yes, then probably you need to check your front-end code, and if no, then check your backend code.

At the end, if you're not so sensitive to the potential risk, you could also override GoogleOAuth2 class as following to disable state check:

from social_core.backends import google

class GoogleOAuth2(google.GoogleOAuth2):
    STATE_PARAMETER = False

I think you may need some changes in you authorizing flow in step NO.3 and 4.

3.Authentication with google and redirection back to the front-end with a state and code parameters on the url.
4.Front-end get the data form url and post data to back-end to verify that the state received is equal to the generated on the point (1).

maybe you should redirect back to server side after google's authorization.

then at the server side, do the check! validate the state and code (maybe do more things).

then let server redirect to the front-end site you wanted to before.

for some reason, redirect to front-end directly will miss the param.. :-)

Finally, I reach a point where everything is working 200 percent fine, on local as well as production. The issue was totally related to the cookies and sessions: So rite answer typo is make it look to your backend server as if the request is coming from localhost:8000, not localhost:3000, means the backend domain should be the same always. For making it possible you have two ways: 1: server should serve the build of the frontend then your frontend will always be on the same domain as the backend. 2: make a simple view in django and attach an empty template to it with only a script tag including logic to handle google auth. always when you click on signing with google move back you you're that view and handle the process and at the end when you get back your access token pass it to the frontend through params. I used 2nd approach as this was appropriate for me. what you need to do is just make a simple View and attach a template to it so on clicking on signIN with google that view get hit. and other process will be handled by the view and on your given URL access token will be moved. View Code:

class GoogleCodeVerificationView(TemplateView):
    permission_classes = []
    template_name = 'social/google.html'
    def get_context_data(self, **kwargs):
        context =  super().get_context_data(**kwargs)
        context["redirect_uri"] = "{}://{}".format(
            settings.SOCIAL_AUTH_PROTOCOL, settings.SOCIAL_AUTH_DOMAIN)
        context['success_redirect_uri'] = "{}://{}".format(
            settings.PASSWORD_RESET_PROTOCOL, settings.PASSWORD_RESET_DOMAIN)
        return context

backend script code:

<body>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
    <script>
      function redirectToClientSide(success_redirect_uri) {
        window.location.replace(`${success_redirect_uri}/signin/`);
      }
      function getFormBoday(details) {
        return Object.keys(details)
          .map(
            (key) =>
              encodeURIComponent(key) + "=" + encodeURIComponent(details[key])
          )
          .join("&");
      }
      try {
        const urlSearchParams = new URLSearchParams(window.location.search);
        const params = Object.fromEntries(urlSearchParams.entries());
        const redirect_uri = "{{redirect_uri|safe}}";
        const success_redirect_uri = "{{success_redirect_uri|safe}}";
        if (params.flag === "google") {
          axios
            .get(
              `/api/accounts/auth/o/google-oauth2/?redirect_uri=${redirect_uri}/api/accounts/google`
            )
            .then((res) => {
              window.location.replace(res.data.authorization_url);
            })
            .catch((errors) => {
              redirectToClientSide(success_redirect_uri);
            });
        } else if (params.state && params.code && !params.flag) {
          const details = {
            state: params.state,
            code: params.code,
          };
          const formBody = getFormBoday(details);
          // axios.defaults.withCredentials = true;
          axios
            .post(`/api/accounts/auth/o/google-oauth2/?${formBody}`)
            .then((res) => {
              const formBody = getFormBoday(res.data);
              window.location.replace(
                `${success_redirect_uri}/google/?${formBody}`
              );
            })
            .catch((errors) => {
              redirectToClientSide(success_redirect_uri);
            });
        } else {
          redirectToClientSide(success_redirect_uri);
        }
      } catch {
        redirectToClientSide(success_redirect_uri);
      }
    </script>
  </body>
Related