benwinding / react-admin-firebase

A firebase data provider for the react-admin framework
https://benwinding.github.io/react-admin-firebase/
MIT License
460 stars 178 forks source link

Using the JWT gives me HandleAuthLogin: invalid credentials #93

Closed dvasdekis closed 4 years ago

dvasdekis commented 4 years ago

Hi guys (probably Ben! thanks for the follow!),

I'm trying to use the Firebase JWT, unsuccessfully, when connecting to a RA dataProvider.

My App.js code is:

import React from "react";
import { fetchUtils, Admin, Resource } from "react-admin";
import { TodoCreate, TodoEdit, TodoList } from "./todos";
import { UserList, UserShow } from "./users";
import hasuraDataProvider from "ra-data-hasura";
import { FirebaseAuthProvider } from "react-admin-firebase";

const firebaseConfig = {...config here...};
const firebaseOptions = { logging: true,  persistence: "local"};
const authProvider = FirebaseAuthProvider(firebaseConfig, firebaseOptions);

// Define Hasura data provider
const hasuraUri = "http://localhost:8081";
const JWT = authProvider.getJWTToken().then(function (JWT) {return JWT});
const httpClient = (url, options = {}) => {
      options.headers = new Headers({ Accept: 'application/json' });
      // This sets the JWT from Firebase as the Authorisation header for Hasura
      options.headers.set('Authorization', `Bearer ${JWT}`);
  return fetchUtils.fetchJson(url, options)
};
const dataProvider = hasuraDataProvider(hasuraUri, httpClient);

const App = () => {
  return (
    <Admin dataProvider={dataProvider}  authProvider={authProvider} >
       <Resource name="todos" list={TodoList}  edit={TodoEdit} create={TodoCreate} />
       <Resource name="users" list={UserList} show={UserShow} />
    </Admin>
  );
};
export default App;

So far, pretty standard. The JWT object I create also posts a valid JWT to console when logged, and my server accepts this JWT as valid when I send it manually.

However, I get the HandleAuthLogin: invalid credentials error when running this code in my stack. The cause is, I believe, due to:

  1. The above code assigning the unresolved promise of a JWT to the JWT variable, rather than the JWT itself.
  2. The dataProvider rejecting the query, as the promise of a JWT isn't yet a valid JWT.
  3. Somehow the React-Admin auth component then takes over, and throws the HandleAuthLogin: invalid credentials error. See the output from Chrome here.
  4. The JWT resolves to the valid JWT value, but by then it's too late and the browser has already sent the promise in the request header.

Some hope lies in the return of HandleAuthLogin: user sucessfully logged in, which contains the valid JWT in the return, under user.user.xa, which would then ensure that the token is available at the instant that the auth module returns a success. However for the life of me I can't figure out how to extract the token from this return and use it as the header instead.

You can try the full stack by cloning this branch, running docker-compose up and logging in with test@example.com and bigpassword on localhost:8080.

benwinding commented 4 years ago

Hi @dvasdekis,

Sounds like an interesting project. I'm sorry I don't have any experience in hasura and am not so good with tokens and Auth. So you'll have to bear with me.

It may be related to this issue #91 where logouts are caused by weird behaviour in the AuthProvider.

If you could clone the latest 'master' branch in this repo, and do some more testing in the src-demo directory. Then you'll get more information and better stack traces with sourcemaps.

The code-base is relatively simple and you might find the problem for your situation.

Let me know how you go!

Cheers, Ben

dvasdekis commented 4 years ago

Hi @benwinding

I managed to get it all working. You can see the example here. Give it a try locally and see what you think!

Thanks for the help! :)

5achinJani commented 4 years ago

+1 having the same issue. after login even doing a page refresh makes a logout due to getIdToken throwing an error. httpLink gets called first and to add auth headers in it I call:

firebase.auth()?.currentUser?.getIdToken();

which results to undefined and then later onAuthStateChanged resolves with the token image

5achinJani commented 4 years ago

My current workaround for this is to call authProvider.checkAuth(); before building the data provider and managing the isAuthReady state on the app level.

 const [state, setState] = useState(initialState);
  useMount(async () => {
    try {
      const user = await authProvider.checkAuth();
      console.log({ user }, "mount");
    } catch (error) {
      console.log(error);
    }

    console.log("buildOpenCrudProvider");
    buildOpenCrudProvider({
      client: apollo_client,
      //@ts-ignore
      introspection: { schema },
      buildQuery: enhanceBuildQuery(buildQuery)
    }).then(dataProvider => {
      setState({
        ...state,
        isAuthReady: true,
        //@ts-ignore
        dataProvider
      });
    });
  });

  const { dataProvider, isAuthReady } = state;

  if (!dataProvider || !isAuthReady) {
    return (
      <div className="loader-container">
        <div className="loader">Loading...</div>
      </div>
    );
  }
abumalick commented 4 years ago

thank you very much for your solution @dvasdekis It is a lot more clean than mine 👍

I think you can close this issue, right?

dvasdekis commented 4 years ago

@abumalick I would close it except that @5achinJani is having the same issue and is using this thread for it?

5achinJani commented 4 years ago

Hey @dvasdekis yeah the fix is to await the firebase auth state to be ready before your App gets init. You can close it.