我正在使用用户登录时提供的auth0令牌通过useAuth0.getTokenSilently进行api调用。
在此示例中,fetchTodoList
,addTodoItem
和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}
/>
</>
)}
</>
)
}
答案 0 :(得分:2)
我不确定您为什么不能在各个函数内部访问令牌?是不是因为它们不是React函数组件而是常规函数?
我要做的一件事情是创建一个useFetch挂钩,该挂钩可以获取用户令牌并将其附加到请求本身。然后,无需专门导出这些功能,我可以调用此新的fetch挂钩。这是我的意思的例子。
import React from "react"
import { useAuth0 } from "../utils/auth"
const useFetch = () => {
const [response, setResponse] = React.useState(null)
const [error, setError] = React.useState(null)
const [isLoading, setIsLoading] = React.useState(false)
const { getTokenSilently } = useAuth0()
const fetchData = async (url, method, body, authenticated, options = {}) => {
setIsLoading(true)
try {
if (authenticated) {
const token = await getTokenSilently()
if (!options.headers) {
options.headers = {}
}
options.headers["Authorization"] = `Bearer ${token}`
}
options.method = method
if (method !== "GET") {
options.body = JSON.stringify(body)
}
const res = await fetch(url, options)
const json = await res.json()
setResponse(json)
setIsLoading(false)
if (res.status === 200) {
return json
}
throw { msg: json.msg }
} catch (error) {
console.error(error)
setError(error)
throw error
}
}
return { response, error, isLoading, fetchData }
}
export default useFetch
答案 1 :(得分:2)
这是@james-quick 答案的变体,我使用“RequestFactory”生成 axios 格式的请求,然后只添加来自 Auth0 的 auth 标头
我遇到了同样的问题,我通过将所有 API 调用逻辑移到我创建的自定义钩子中来解决此限制:
import { useAuth0 } from '@auth0/auth0-react';
import { useCallback } from 'react';
import makeRequest from './axios';
export const useRequest = () => {
const { getAccessTokenSilently } = useAuth0();
// memoized the function, as otherwise if the hook is used inside a useEffect, it will lead to an infinite loop
const memoizedFn = useCallback(
async (request) => {
const accessToken = await getAccessTokenSilently({ audience: AUDIANCE })
return makeRequest({
...request,
headers: {
...request.headers,
// Add the Authorization header to the existing headers
Authorization: `Bearer ${accessToken}`,
},
});
},
[isAuthenticated, getAccessTokenSilently]
);
return {
requestMaker: memoizedFn,
};
};
export default useRequest;
用法示例:
import { RequestFactory } from 'api/requestFactory';
const MyAwesomeComponent = () => {
const { requestMaker } = useRequest(); // Custom Hook
...
requestMaker(QueueRequestFactory.create(queueName))
.then((response) => {
// Handle response here
...
});
}
RequestFactory 为我的不同 API 调用定义并生成请求负载,例如:
export const create = (queueName) => ({ method: 'post', url: '/queue', data: { queueName } });
Here 是完整的 Auth0 集成 PR,供参考。
答案 2 :(得分:1)
有多种解决方法。
不要过多更改代码库。我会去一个带有提供者和挂钩的商店。那里有很多商店图书馆。
这是一个很小的版本,也可以在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);
答案 3 :(得分:1)
我在如何在 React 组件之外使用 getAccessTokenSilently
时遇到了类似的问题,我最终得到的是:
我的 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();
}
}
在我的应用根目录中,我从 auth0 传递了 getAccessTokenSilently
useEffect(() => {
httpClient.setTokenGenerator(getAccessTokenSilently);
}, [getAccessTokenSilently]);
就是这样!
您现在有一个 axios
实例准备好使用
答案 4 :(得分:0)
好,知道了!
现在我更了解了,我真正的问题是如何为axios请求提供auth0令牌,这样就无需在组件中声明它们。
简短答案: 初始化auth0时获取令牌,然后注册axios interceptor将该令牌设置为所有axios请求的标头值。
长答案(打字稿中的示例):
声明一个带有令牌并注册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 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()
}, [])
在解决承诺后调用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
}
}
...