hasura / learn-graphql

Real world GraphQL tutorials for frontend developers with deadlines!
https://hasura.io/learn/
MIT License
1.19k stars 647 forks source link

[GraphQL error]: Authentication hook unauthorized this request #1048

Open kiitosu opened 1 month ago

kiitosu commented 1 month ago

I followed frontend tutorial graphql with react https://hasura.io/learn/graphql/react/introduction/. And I get authentication error with token I get with useAccessToken.

Base code I used is https://github.com/hasura/learn-graphql/commit/698f187e0999ffeae85d81e57af141bcffd729db. Error message is "[GraphQL error]: Authentication hook unauthorized this request " at TodoPrivateList.js:66.

When I hard code jwt like below that is gotten at https://hasura.io/learn/graphql/graphiql, I can query in localhost web site with npm start.

Why I get authentication error with token I get with useAccessToken? How can I handle token?

const createApolloClient = (authToken) => {
    console.log(`authToken is ${authToken}`)
    return new ApolloClient({
        link: new HttpLink({
            uri: 'https://hasura.io/learn/graphql',
            headers: {
                Authorization: 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik9FWTJSVGM1UlVOR05qSXhSRUV5TURJNFFUWXdNekZETWtReU1EQXdSVUV4UVVRM05EazFNQSJ9.eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbInVzZXIiXSwieC1oYXN1cmEtdXNlci1pZCI6ImF1dGgwfDYyZGYyN2E3ZGMwMTBhYjg3YTFlMmRiYSJ9LCJuaWNrbmFtZSI6ImtoMDQxMiIsIm5hbWUiOiJraDA0MTJAZ21haWwuY29tIiwicGljdHVyZSI6Imh0dHBzOi8vcy5ncmF2YXRhci5jb20vYXZhdGFyLzI2ZmMzZThmNTVlMjIwM2UzOTgzMzQyZmYwMzUyZmM1P3M9NDgwJnI9cGcmZD1odHRwcyUzQSUyRiUyRmNkbi5hdXRoMC5jb20lMkZhdmF0YXJzJTJGa2gucG5nIiwidXBkYXRlZF9hdCI6IjIwMjQtMDctMjFUMDg6Mjk6MTIuODYyWiIsImlzcyI6Imh0dHBzOi8vZ3JhcGhxbC10dXRvcmlhbHMuYXV0aDAuY29tLyIsImF1ZCI6IlAzOHFuRm8xbEZBUUpyemt1bi0td0V6cWxqVk5HY1dXIiwiaWF0IjoxNzIxNTUxNDc4LCJleHAiOjE3MjE1ODc0NzgsInN1YiI6ImF1dGgwfDYyZGYyN2E3ZGMwMTBhYjg3YTFlMmRiYSIsImF0X2hhc2giOiJLcUNDay04T192ZjVaNWRDdjdXMGhnIiwic2lkIjoiMVNVUl9iUDFpcy1aNktKd1NGMmY5WGc2Rk9kYlFtQTEiLCJub25jZSI6IlQ3QWU0YktzTTlYX2VBeS1aT0xMLlowTGZ0MzRNU3FpIn0.hoSkaLMAjFeycQB7yHGEOQUWvpZd2e4CR72B-Hv3iMBgQtWc5t0uXqZZmDYYPQxpD30ZrLVtytv0XI_qc8zQLdc_B759VCUfah2hQ_sxifwYntRUV41ZdyGFNPw_zqQa9hSF6npFHXRQrnff7EHmvDEKL8YSGrOq1zMDhkpBN72ZCZQjmwUT3swABA51KB4J6ZlKYhPodq9GoOSPzxmYwsvWiO2nvoj7wJFDlfX6aiNRGai3a9lypCEPHo-vSLcD9gUXMrdsi-aeuCmPoHpyiWThNeCklprFaPaO4yJSCFECGLCOqX3JFhC5NILNK7ZUQVyHkHRHxkbjgK7VYx4AhA'
            }
        }),
        cache: new InMemoryCache(),
    });
};

Diff patch is below.

diff --git a/tutorials/frontend/react-apollo-hooks/app-boilerplate/src/components/App.js b/tutorials/frontend/react-apollo-hooks/app-boilerplate/src/components/App.js
index 2199664..c88f7d4 100644
--- a/tutorials/frontend/react-apollo-hooks/app-boilerplate/src/components/App.js
+++ b/tutorials/frontend/react-apollo-hooks/app-boilerplate/src/components/App.js
@@ -1,4 +1,4 @@
-import React from "react";
+import React, {useState} from "react";
 import { useAuth0 } from "@auth0/auth0-react";

 import Header from "./Header";
@@ -9,9 +9,26 @@ import OnlineUsersWrapper from "./OnlineUsers/OnlineUsersWrapper";

 import useAccessToken from "../hooks/useAccessToken";

+import { ApolloClient, ApolloProvider, InMemoryCache, HttpLink } from '@apollo/client';
+
+const createApolloClient = (authToken) => {
+    console.log(`authToken is ${authToken}`)
+    return new ApolloClient({
+        link: new HttpLink({
+            uri: 'https://hasura.io/learn/graphql',
+            headers: {
+                Authorization: `Bearer ${authToken}`
+            }
+        }),
+        cache: new InMemoryCache(),
+    });
+};
+
+
 const App = () => {
   const idToken = useAccessToken();
   const { loading, logout } = useAuth0();
+  const [client] = useState(createApolloClient(idToken));

   if (loading) {
     return <div>Loading...</div>;
@@ -20,25 +37,28 @@ const App = () => {
   if (!idToken) {
     return <Login />;
   }
+
   return (
-    <div>
-      <Header logoutHandler={logout} />
-      <div className="row container-fluid p-left-right-0 m-left-right-0">
-        <div className="row col-md-9 p-left-right-0 m-left-right-0">
-          <div className="col-md-6 sliderMenu p-30">
-            <TodoPrivateWrapper />
-          </div>
-          <div className="col-md-6 sliderMenu p-30 bg-gray border-right">
-            <TodoPublicWrapper />
-          </div>
-        </div>
-        <div className="col-md-3 p-left-right-0">
-          <div className="col-md-12 sliderMenu p-30 bg-gray">
-            <OnlineUsersWrapper />
-          </div>
+    <ApolloProvider client={client}>
+        <div>
+            <Header logoutHandler={logout} />
+            <div className="row container-fluid p-left-right-0 m-left-right-0">
+                <div className="row col-md-9 p-left-right-0 m-left-right-0">
+                <div className="col-md-6 sliderMenu p-30">
+                    <TodoPrivateWrapper />
+                </div>
+                <div className="col-md-6 sliderMenu p-30 bg-gray border-right">
+                    <TodoPublicWrapper />
+                </div>
+                </div>
+                <div className="col-md-3 p-left-right-0">
+                <div className="col-md-12 sliderMenu p-30 bg-gray">
+                    <OnlineUsersWrapper />
+                </div>
+                </div>
+            </div>
         </div>
-      </div>
-    </div>
+    </ApolloProvider>
   );
 };

diff --git a/tutorials/frontend/react-apollo-hooks/app-boilerplate/src/components/Todo/TodoPrivateList.js b/tutorials/frontend/react-apollo-hooks/app-boilerplate/src/components/Todo/TodoPrivateList.js
index f1d43bc..cf84d9f 100644
--- a/tutorials/frontend/react-apollo-hooks/app-boilerplate/src/components/Todo/TodoPrivateList.js
+++ b/tutorials/frontend/react-apollo-hooks/app-boilerplate/src/components/Todo/TodoPrivateList.js
@@ -1,26 +1,21 @@
 import React, { useState, Fragment } from "react";
-
 import TodoItem from "./TodoItem";
 import TodoFilters from "./TodoFilters";
+import {gql, useQuery} from '@apollo/client'
+const GET_MY_TODOS = gql`
+query getMyTodos {
+    todos(where: { is_public: { _eq: false} }, order_by: { created_at: desc }) {
+        id
+        title
+        created_at
+        is_completed
+    }
+}`;

 const TodoPrivateList = props => {
   const [state, setState] = useState({
     filter: "all",
     clearInProgress: false,
-    todos: [
-      {
-        id: "1",
-        title: "This is private todo 1",
-        is_completed: true,
-        is_public: false
-      },
-      {
-        id: "2",
-        title: "This is private todo 2",
-        is_completed: false,
-        is_public: false
-      }
-    ]
   });

   const filterResults = filter => {
@@ -32,11 +27,12 @@ const TodoPrivateList = props => {

   const clearCompleted = () => {};

-  let filteredTodos = state.todos;
+  const {todos} = props
+  let filteredTodos = todos
   if (state.filter === "active") {
-    filteredTodos = state.todos.filter(todo => todo.is_completed !== true);
+    filteredTodos = todos.filter(todo => todo.is_completed !== true);
   } else if (state.filter === "completed") {
-    filteredTodos = state.todos.filter(todo => todo.is_completed === true);
+    filteredTodos = todos.filter(todo => todo.is_completed === true);
   }

   const todoList = [];
@@ -61,4 +57,30 @@ const TodoPrivateList = props => {
   );
 };

-export default TodoPrivateList;
+const TodoPrivateListQuery = ()=> {
+    const { loading, error, data } = useQuery(GET_MY_TODOS)
+    if(loading) {
+        return <div>Loading...</div>
+    }
+    if (error) {
+        console.error(`[GraphQL error]: ${error.message}`);
+        if (error.graphQLErrors) {
+            error.graphQLErrors.forEach(({ message, locations, path }) =>
+                console.error(
+                    `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
+                )
+            );
+        }
+        if (error.networkError) {
+            console.error(`[Network error]: ${error.networkError}`);
+        }
+        if (error.message.includes("Authentication hook unauthorized this request")) {
+            return <div>Authentication Error! Please log in again.</div>;
+        }
+        return <div>Error!</div>;
+    }
+    return <TodoPrivateList todos={data.todos}/>;
+}
+
+export default TodoPrivateListQuery;
+export {GET_MY_TODOS}

TodoPrivateList.js

import React, { useState, Fragment } from "react";
import TodoItem from "./TodoItem";
import TodoFilters from "./TodoFilters";
import {gql, useQuery} from '@apollo/client'
const GET_MY_TODOS = gql`
query getMyTodos {
    todos(where: { is_public: { _eq: false} }, order_by: { created_at: desc }) {
        id
        title
        created_at
        is_completed
    }
}`;

const TodoPrivateList = props => {
  const [state, setState] = useState({
    filter: "all",
    clearInProgress: false,
  });

  const filterResults = filter => {
    setState({
      ...state,
      filter: filter
    });
  };

  const clearCompleted = () => {};

  const {todos} = props
  let filteredTodos = todos
  if (state.filter === "active") {
    filteredTodos = todos.filter(todo => todo.is_completed !== true);
  } else if (state.filter === "completed") {
    filteredTodos = todos.filter(todo => todo.is_completed === true);
  }

  const todoList = [];
  filteredTodos.forEach((todo, index) => {
    todoList.push(<TodoItem key={index} index={index} todo={todo} />);
  });

  return (
    <Fragment>
      <div className="todoListWrapper">
        <ul>{todoList}</ul>
      </div>

      <TodoFilters
        todos={filteredTodos}
        currentFilter={state.filter}
        filterResultsFn={filterResults}
        clearCompletedFn={clearCompleted}
        clearInProgress={state.clearInProgress}
      />
    </Fragment>
  );
};

const TodoPrivateListQuery = ()=> {
    const { loading, error, data } = useQuery(GET_MY_TODOS)
    if(loading) {
        return <div>Loading...</div>
    }
    if (error) {
        console.error(`[GraphQL error]: ${error.message}`);
        if (error.graphQLErrors) {
            error.graphQLErrors.forEach(({ message, locations, path }) =>
                console.error(
                    `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
                )
            );
        }
        if (error.networkError) {
            console.error(`[Network error]: ${error.networkError}`);
        }
        if (error.message.includes("Authentication hook unauthorized this request")) {
            return <div>Authentication Error! Please log in again.</div>;
        }
        return <div>Error!</div>;
    }
    return <TodoPrivateList todos={data.todos}/>;
}

export default TodoPrivateListQuery;
export {GET_MY_TODOS}

App.js

import React, {useState} from "react";
import { useAuth0 } from "@auth0/auth0-react";

import Header from "./Header";
import Login from "./Auth/Login";
import TodoPrivateWrapper from "./Todo/TodoPrivateWrapper";
import TodoPublicWrapper from "./Todo/TodoPublicWrapper";
import OnlineUsersWrapper from "./OnlineUsers/OnlineUsersWrapper";

import useAccessToken from "../hooks/useAccessToken";

import { ApolloClient, ApolloProvider, InMemoryCache, HttpLink } from '@apollo/client';

const createApolloClient = (authToken) => {
    console.log(`authToken is ${authToken}`)
    return new ApolloClient({
        link: new HttpLink({
            uri: 'https://hasura.io/learn/graphql',
            headers: {
                Authorization: `Bearer ${authToken}`
            }
        }),
        cache: new InMemoryCache(),
    });
};

const App = () => {
  const idToken = useAccessToken();
  const { loading, logout } = useAuth0();
  const [client] = useState(createApolloClient(idToken));

  if (loading) {
    return <div>Loading...</div>;
  }

  if (!idToken) {
    return <Login />;
  }

  return (
    <ApolloProvider client={client}>
        <div>
            <Header logoutHandler={logout} />
            <div className="row container-fluid p-left-right-0 m-left-right-0">
                <div className="row col-md-9 p-left-right-0 m-left-right-0">
                <div className="col-md-6 sliderMenu p-30">
                    <TodoPrivateWrapper />
                </div>
                <div className="col-md-6 sliderMenu p-30 bg-gray border-right">
                    <TodoPublicWrapper />
                </div>
                </div>
                <div className="col-md-3 p-left-right-0">
                <div className="col-md-12 sliderMenu p-30 bg-gray">
                    <OnlineUsersWrapper />
                </div>
                </div>
            </div>
        </div>
    </ApolloProvider>
  );
};

export default App;
praveenweb commented 1 month ago

Hi @kiitosu - The access token is being set using the following snippet here - https://github.com/hasura/learn-graphql/blob/5e19739d128a62ce0989810a2f74334e52d80e8e/tutorials/frontend/react-apollo-hooks/app-final/src/hooks/useAccessToken.js#L13

const getAccessToken = async () => {
      const { audience, scope } = AUTH_CONFIG;

      try {
        const accessToken = await getAccessTokenSilently({
          audience,
          scope,
        });
        setIdToken(accessToken);
      } catch (e) {
        console.log(e.message);
      }
    };

I would start debugging there. Quick caveat: The tutorial is using older versions of Auth0 SDK, so I would double-check if it is using the right one since newer ones may have breaking changes. Meanwhile, I will try running it from my side to see what could be wrong.