web-dev-qa-db-ja.com

反応コンポーネント外のauth0providerからのトークンへのアクセス

ログイン時にユーザーから提供されたauth0トークンを使用して、useAuth0.getTokenSilentlyを介してAPIを呼び出しています。

この例では、fetchTodoListaddTodoItem、およびupdateTodoItemはすべて、認証にトークンが必要です。これらの関数を別のファイル(utils/api-client.jsなど)に抽出して、明示的にトークンを渡さなくてもインポートできるようにしたいと思います。

import React, { useContext } from 'react'
import { Link, useParams } from 'react-router-dom'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCircle, faList } from '@fortawesome/free-solid-svg-icons'
import axios from 'axios'
import { queryCache, useMutation, useQuery } from 'react-query'
import { TodoItem } from '../models/TodoItem'
import { TodoInput } from './TodoInput'
import { TodoList as TodoListComponent } from './TodoList'
import { TodoListsContext } from '../store/todolists'
import { TodoListName } from './TodoListName'
import { TodoList } from '../models/TodoList'
import { useAuth0 } from '../utils/react-auth0-wrapper'

export const EditTodoList = () => {

  const { getTokenSilently } = useAuth0()

  const fetchTodoList = async (todoListId: number): Promise<TodoList> => {
    try {
      const token = await getTokenSilently!()

      const { data } = await axios.get(
        `/api/TodoLists/${todoListId}`,
        {
          headers: {
            Authorization: `Bearer ${token}`
          }
        }
      )
      return data
    } catch (error) {
      return error
    }
  }

  const addTodoItem = async (todoItem: TodoItem): Promise<TodoItem> => {
    try {
      const token = await getTokenSilently!()

      const { data } = await axios.post(
        '/api/TodoItems',
        todoItem,
        {
          headers: {
            Authorization: `Bearer ${token}`,
          }
        }
      )
      return data
    } catch (addTodoListError) {
      return addTodoListError
    }
  }

  const updateTodoItem = async (todoItem: TodoItem) => {
    try {
      const token = await getTokenSilently!()

      const { data } = await axios.put(
        '/api/TodoItems',
        todoItem,
        {
          headers: {
            Authorization: `Bearer ${token}`,
          }
        }
      )
      return data
    } catch (addTodoListError) {
      return addTodoListError
    }
  }

  const [updateTodoItemMutation] = useMutation(updateTodoItem, {
    onSuccess: () => {
      queryCache.refetchQueries(['todoList', todoListId])
    }
  })

  const [addTodoItemMutation] = useMutation(addTodoItem, {
    onSuccess: () => {
      console.log('success')
      queryCache.refetchQueries(['todoList', todoListId])
    }
  })

  const onAddTodoItem = async (todoItem: TodoItem) => {
    try {
      await addTodoItemMutation({ 
        ...todoItem, 
        todoListId: parseInt(todoListId, 10) 
      })
    } catch (error) {
      // Uh oh, something went wrong
    }
  }

  const { todoListId } = useParams()
  const { status, data: todoList, error } = useQuery(['todoList', todoListId], () => fetchTodoList(todoListId))
  const { todoLists, setTodoList } = useContext(TodoListsContext)
  const todoListIndex = todoLists.findIndex(
    list => todoListId === list.id.toString()
  )

  const setTodoItems = (todoItems: TodoItem[]) => {
    // if(todoList) {
    //   const list = { ...todoList, todoItems }
    //   setTodoList(todoListIndex, list)
    // }
  }

  const setTodoListName = (name: string) => {
    // setTodoList(todoListIndex, { ...todoList, name })
  }

  return (
    <>
      <Link className="block flex align-items-center mt-8" to="/">
        <span className="fa-layers fa-fw fa-3x block m-auto group">
          <FontAwesomeIcon 
            icon={faCircle} 
            className="text-teal-500 transition-all duration-200 ease-in-out group-hover:text-teal-600" 
          />
          <FontAwesomeIcon icon={faList} inverse transform="shrink-8" />
        </span>
      </Link>

      {status === 'success' && !!todoList && (
        <>
          <TodoListName
            todoListName={todoList.name}
            setTodoListName={setTodoListName}
          />
          <TodoInput 
            onAddTodoItem={onAddTodoItem} 
          />

          <TodoListComponent
            todoItems={todoList.todoItems}
            setTodoItems={setTodoItems}
            updateTodo={updateTodoItemMutation}
          />
        </>
      )}
    </>
  )
}

これがリポジトリへのリンクです: https://github.com/gpspake/todo-client

3
ItsGeorge

はい、わかった!

私はよく理解したので、私の本当の質問は、コンポーネントで宣言する必要がないようにauth0トークンをaxiosリクエストに提供する方法でした。

短い答え:auth0が初期化されるときにトークンを取得し、 axios interceptor を登録して、そのトークンをすべてのaxiosリクエストのヘッダー値として設定します。

長い答え(TypeScriptの例):

トークンを取得して axios interceptor を登録する関数を宣言する

_const setAxiosTokenInterceptor = async (accessToken: string): Promise<void> => {
  axios.interceptors.request.use(async config => {
    const requestConfig = config
    if (accessToken) {
      requestConfig.headers.common.Authorization = `Bearer ${accessToken}`
    } 
    return requestConfig
  })
}

_

Auth0providerラッパーで、auth0クライアントが初期化および認証されると、setAxiosTokenInterceptorを使用してトークンを取得し、インターセプターを登録する関数に渡します(- Auth0 React SDKクイックスタート ):

_useEffect(() => {
    const initAuth0 = async () => {
        const auth0FromHook = await createAuth0Client(initOptions)
        setAuth0(auth0FromHook)

        if (window.location.search.includes('code=')) {
            const { appState } = await auth0FromHook.handleRedirectCallback()
            onRedirectCallback(appState)
        }

        auth0FromHook.isAuthenticated().then(
            async authenticated => {
                setIsAuthenticated(authenticated)
                if (authenticated) {
                    auth0FromHook.getUser().then(
                        auth0User => {
                            setUser(auth0User)
                        }
                    )
                    // get token and register interceptor
                    const token = await auth0FromHook.getTokenSilently()
                    setAxiosTokenInterceptor(token).then(
                        () => {setLoading(false)}
                    )
                }
            }
        )


    }
    initAuth0().catch()
}, [])
_

Promiseが解決されたときにsetLoading(false)を呼び出すと、auth0のロードが完了した場合に、インターセプターが登録されていることが保証されます。リクエストを行うコンポーネントは、auth0の読み込みが完了するまでレンダリングされないため、トークンなしで呼び出しを行うことはできません。

これにより、すべてのaxios関数を別のファイルに移動して、必要なコンポーネントにインポートすることができました。これらの関数のいずれかが呼び出されると、インターセプターはヘッダーにトークンを追加します_utils/todo-client.ts_

_
import axios from 'axios'
import { TodoList } from '../models/TodoList'
import { TodoItem } from '../models/TodoItem'

export const fetchTodoLists = async (): Promise<TodoList[]> => {
  try {
    const { data } = await axios.get(
      '/api/TodoLists'
    )
    return data
  } catch (error) {
    return error
  }
}

export const fetchTodoList = async (todoListId: number): Promise<TodoList> => {
  try {
    const { data } = await axios.get(
      `/api/TodoLists/${todoListId}`
    )
    return data
  } catch (error) {
    return error
  }
}

export const addTodoItem = async (todoItem: TodoItem): Promise<TodoItem> => {
  try {
    const { data } = await axios.post(
      '/api/TodoItems',
      todoItem
    )
    return data
  } catch (addTodoListError) {
    return addTodoListError
  }
}
...

_

githubの完全なソース

0
ItsGeorge

これを解決する方法はいくつかあります。

コードベースをあまり変更しないようにするため。私はstoreproviderフック。そこには多くのストアライブラリがあります。

これは、Reactレンダリング以外でも使用できる小さなバージョンです。
https://github.com/storeon/storeon

これは、法案に合うかもしれないと私が見つけた非常に小さな店のほんの一例でした。

Reactの外でストアライブラリを使用すると、次のようになります。

import store from './path/to/my/store.js;'

// Read data
const state = store.get();

// Save data in the store
store.dispatch('foo/bar', myToken);
1
Kunukn