来自 react 组件之外的 auth0provider 的访问令牌

Its*_*rge 13 typescript reactjs auth0 axios

我正在使用用户在登录时提供的 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}
          />
        </>
      )}
    </>
  )
}
Run Code Online (Sandbox Code Playgroud)

这是 repo 的链接:https : //github.com/gpspake/todo-client

小智 11

我在如何getAccessTokenSilently在 React 组件之外使用时遇到了类似的问题,我最终得到的是:

我的 HTTP 客户端包装器

export class HttpClient {
  constructor() {
    HttpClient.instance = axios.create({ baseURL: process.env.API_BASE_URL });

    HttpClient.instance.interceptors.request.use(
      async config => {
        const token = await this.getToken();

        return {
          ...config,
          headers: { ...config.headers, Authorization: `Bearer ${token}` },
        };
      },
      error => {
        Promise.reject(error);
      },
    );

    return this;
  }

  setTokenGenerator(tokenGenerator) {
    this.tokenGenerator = tokenGenerator;
    return this;
  }

  getToken() {
    return this.tokenGenerator();
  }
}


Run Code Online (Sandbox Code Playgroud)

在我的应用程序根目录中,我通过getAccessTokenSilentlyauth0

 useEffect(() => {
    httpClient.setTokenGenerator(getAccessTokenSilently);
  }, [getAccessTokenSilently]);

Run Code Online (Sandbox Code Playgroud)

就是这样!

您现在有一个axios实例准备好执行经过身份验证的请求


Its*_*rge 3

好的,我知道了!

现在我更好地理解了,我真正的问题是如何向 axios 请求提供 auth0 令牌,以便不需要在组件中声明它们。

简短的答案:在初始化 auth0 时获取令牌并注册axios 拦截器以将该令牌设置为所有 axios 请求的标头值。

长答案(打字稿中的示例):

声明一个接受令牌并注册axios 拦截器的函数

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
  })
}

Run Code Online (Sandbox Code Playgroud)

在 auth0provider 包装器中,当 auth0 客户端初始化并经过身份验证时,获取令牌并将setAxiosTokenInterceptor其传递给注册拦截器的函数(来自Auth0 React SDK Quickstart的修改示例):

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()
}, [])
Run Code Online (Sandbox Code Playgroud)

在解决 Promise 时调用setLoading(false)可确保如果 auth0 加载完成,则拦截器已注册。由于在 auth0 完成加载之前不会呈现发出请求的任何组件,因此这可以防止在没有令牌的情况下进行任何调用。

这使我能够将所有 axios 函数移至一个单独的文件中,并将它们导入到需要它们的组件中。当调用这些函数中的任何一个时,拦截器会将令牌添加到标头中 utils/todo-client.ts

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()
}, [])
Run Code Online (Sandbox Code Playgroud)

完整源码在 github 上

  • 我知道令牌只会在 auth0 初始化时设置一次?这意味着当令牌过期时(有效期短或应用程序长时间运行)API 请求将失败。 (4认同)