来自反应组件之外的 auth0provider 的访问令牌
access token from auth0provider outside of react components
我正在使用用户在登录时提供的 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}
/>
</>
)}
</>
)
}
这是回购的 link:https://github.com/gpspake/todo-client
有多种方法可以解决这个问题。
不要过多更改您的代码库。我会选择 store 和 provider 和 hook。那里有许多商店图书馆。
这是一个小版本,也可以在 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);
我不太清楚为什么您无法在您的各个函数中访问令牌?是因为它们不是 React 函数组件而是普通函数吗?
我所做的其中一件事是创建一个 useFetch 挂钩,它可以获取用户令牌并将其附加到请求本身。然后,我可以调用这个新的获取挂钩,而不是专门导出那些函数。这是我的意思的一个例子。
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
好的,知道了!
现在我理解得更好了,我真正的问题是如何为 axios 请求提供 auth0 令牌,这样它们就不需要在组件中声明。
简短回答:
在 auth0 初始化时获取令牌并注册一个 axios interceptor 以将该令牌设置为所有 axios 请求的 header 值。
长答案(打字稿中的示例):
声明一个接受令牌并注册一个 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 wrapper中,当auth0客户端被初始化和认证时,使用setAxiosTokenInterceptor
获取token并将其传递给注册拦截器的函数(修改自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 已完成加载,则已注册拦截器。由于 none 发出请求的组件会在 auth0 完成加载之前呈现,这可以防止在没有令牌的情况下进行任何调用。
这让我可以将我所有的 axios 函数移动到一个单独的文件中,并将它们导入到需要它们的组件中。当调用这些函数中的任何一个时,拦截器会将令牌添加到 header
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
}
}
...
这是@james-quick 回答的一个变体,我在其中使用“RequestFactory”生成 axios 格式的请求,然后只添加来自 Auth0[ 的 auth header =14=]
我遇到了同样的问题,我通过将所有 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 供参考。
关于如何在 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();
}
}
在我的 App root 上,我从 auth0
传递 getAccessTokenSilently
useEffect(() => {
httpClient.setTokenGenerator(getAccessTokenSilently);
}, [getAccessTokenSilently]);
就是这样!
您现在有一个 axios
实例准备好通过
执行经过身份验证的请求
我喜欢将 API 调用放在它们自己的目录中(例如在 /api
下),并让调用 API 的代码尽可能小。我在这里采用了与其他人类似的方法,使用 Auth0、TypeScript、Axios(包括拦截器)和 React hooks。
TLDR 答案
将您的 Axios 拦截器放在一个钩子中,然后在分段的 API 个钩子中使用该钩子(即 useUserApi
、useArticleApi
、useCommentApi
等等) .然后,您可以使用 Auth0.
干净地调用您的 API
长答案
定义你的 Axios 钩子,我只介绍了我当前使用的 HTTP 方法:
# src/api/useAxios.ts
import { useAuth0 } from '@auth0/auth0-react';
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
// We wrap Axios methods in a hook, so we can centrally handle adding auth tokens.
const useAxios = () => {
const { getAccessTokenSilently } = useAuth0();
axios.interceptors.request.use(async (config: any) => {
if (config.url.indexOf('http') === -1) {
config.url = `${process.env.REACT_APP_API_ENDPOINT}/${config.url}`;
}
if (typeof config.headers.Authorization === 'undefined') {
config.headers.Authorization = `Bearer ${await getAccessTokenSilently()}`;
}
return config;
});
return {
get: async (url: string, config?: AxiosRequestConfig<any> | undefined): Promise<AxiosResponse> => axios.get(url, config),
delete: async (url: string, config?: AxiosRequestConfig<any> | undefined): Promise<AxiosResponse> => axios.delete(url, config),
post: async (url: string, data?: any, config?: AxiosRequestConfig<any> | undefined): Promise<AxiosResponse> => axios.post(url, data, config),
put: async (url: string, data?: any, config?: AxiosRequestConfig<any> | undefined): Promise<AxiosResponse> => axios.put(url, data, config),
patch: async (url: string, data?: any, config?: AxiosRequestConfig<any> | undefined): Promise<AxiosResponse> => axios.patch(url, data, config),
}
};
export default useAxios;
我在这里所做的是通过调用 getAccessTokensSilently()
添加不记名令牌(如果尚未定义的话)。此外,如果我的 URL 中不存在 HTTP,那么我会从我的环境变量中附加默认的 API URL - 这意味着我可以保持我的请求简短而不必使用完整的URL 每次。
现在我根据我的用户 API 定义一个钩子如下:
# src/api/useUserApi.ts
import { UserInterface } from '[REDACTED]/types';
import { AxiosResponse } from 'axios';
import useAxios from './useAxios';
const useUserApi = () => {
const { get, post, put } = useAxios();
return {
getUser: (id: string): Promise<AxiosResponse<UserInterface>> => get(`user/${id}`),
putUser: (user: UserInterface) => put('user', user),
postUser: (user: UserInterface) => post('user', user),
}
};
export default useUserApi;
你可以看到我从axios中暴露了底层的HTTP方法,然后在API具体场景getUser
、putUser
和postUser
中使用它们。
现在我可以继续在某些应用程序逻辑中调用我的 API,将 API 代码保持在绝对最低限度,但仍然允许完全传递和键入 Axios 对象。
import { useAuth0 } from '@auth0/auth0-react';
import { useNavigate } from 'react-router';
import useUserApi from '../../../api/useUserApi';
const LoginCallback = (): JSX.Element => {
const navigate = useNavigate()
const { user, isAuthenticated, isLoading } = useAuth0();
const { getUser, putUser, postUser} = useUserApi();
const saveUserToApi = async () => {
if (!user?.sub) {
throw new Error ('User does not have a sub');
}
// Try and find the user, if 404 then create a new one for this Auth0 sub
try {
const userResult = await getUser(user.sub);
putUser(Object.assign(user, userResult.data));
navigate('/');
} catch (e: any) {
if (e.response.status === 404) {
postUser({
id: user.sub,
email: user.email,
name: user.name,
givenName: user.givenName,
familyName: user.familyName,
locale: user.locale,
});
navigate('/');
}
}
}
if (isLoading) {
return <div>Logging you in...</div>;
}
if (isAuthenticated && user?.sub) {
saveUserToApi();
return <p>Saved</p>
} else {
return <p>An error occured whilst logging in.</p>;
}
};
export default LoginCallback;
可以注意到上面的postUser
、putUser
和getUser
API请求都是一个行,只有声明(const { getUser, putUser, postUser} = useUserApi();
)和否则需要导入。
这个答案绝对是站在巨人的肩膀上,但我认为它对喜欢保持 API 通话尽可能干净的人来说同样有用。
我正在使用用户在登录时提供的 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}
/>
</>
)}
</>
)
}
这是回购的 link:https://github.com/gpspake/todo-client
有多种方法可以解决这个问题。
不要过多更改您的代码库。我会选择 store 和 provider 和 hook。那里有许多商店图书馆。
这是一个小版本,也可以在 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);
我不太清楚为什么您无法在您的各个函数中访问令牌?是因为它们不是 React 函数组件而是普通函数吗?
我所做的其中一件事是创建一个 useFetch 挂钩,它可以获取用户令牌并将其附加到请求本身。然后,我可以调用这个新的获取挂钩,而不是专门导出那些函数。这是我的意思的一个例子。
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
好的,知道了!
现在我理解得更好了,我真正的问题是如何为 axios 请求提供 auth0 令牌,这样它们就不需要在组件中声明。
简短回答: 在 auth0 初始化时获取令牌并注册一个 axios interceptor 以将该令牌设置为所有 axios 请求的 header 值。
长答案(打字稿中的示例):
声明一个接受令牌并注册一个 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 wrapper中,当auth0客户端被初始化和认证时,使用setAxiosTokenInterceptor
获取token并将其传递给注册拦截器的函数(修改自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 已完成加载,则已注册拦截器。由于 none 发出请求的组件会在 auth0 完成加载之前呈现,这可以防止在没有令牌的情况下进行任何调用。
这让我可以将我所有的 axios 函数移动到一个单独的文件中,并将它们导入到需要它们的组件中。当调用这些函数中的任何一个时,拦截器会将令牌添加到 header
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
}
}
...
这是@james-quick 回答的一个变体,我在其中使用“RequestFactory”生成 axios 格式的请求,然后只添加来自 Auth0[ 的 auth header =14=]
我遇到了同样的问题,我通过将所有 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 供参考。
关于如何在 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();
}
}
在我的 App root 上,我从 auth0
传递getAccessTokenSilently
useEffect(() => {
httpClient.setTokenGenerator(getAccessTokenSilently);
}, [getAccessTokenSilently]);
就是这样!
您现在有一个 axios
实例准备好通过
我喜欢将 API 调用放在它们自己的目录中(例如在 /api
下),并让调用 API 的代码尽可能小。我在这里采用了与其他人类似的方法,使用 Auth0、TypeScript、Axios(包括拦截器)和 React hooks。
TLDR 答案
将您的 Axios 拦截器放在一个钩子中,然后在分段的 API 个钩子中使用该钩子(即 useUserApi
、useArticleApi
、useCommentApi
等等) .然后,您可以使用 Auth0.
长答案
定义你的 Axios 钩子,我只介绍了我当前使用的 HTTP 方法:
# src/api/useAxios.ts
import { useAuth0 } from '@auth0/auth0-react';
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
// We wrap Axios methods in a hook, so we can centrally handle adding auth tokens.
const useAxios = () => {
const { getAccessTokenSilently } = useAuth0();
axios.interceptors.request.use(async (config: any) => {
if (config.url.indexOf('http') === -1) {
config.url = `${process.env.REACT_APP_API_ENDPOINT}/${config.url}`;
}
if (typeof config.headers.Authorization === 'undefined') {
config.headers.Authorization = `Bearer ${await getAccessTokenSilently()}`;
}
return config;
});
return {
get: async (url: string, config?: AxiosRequestConfig<any> | undefined): Promise<AxiosResponse> => axios.get(url, config),
delete: async (url: string, config?: AxiosRequestConfig<any> | undefined): Promise<AxiosResponse> => axios.delete(url, config),
post: async (url: string, data?: any, config?: AxiosRequestConfig<any> | undefined): Promise<AxiosResponse> => axios.post(url, data, config),
put: async (url: string, data?: any, config?: AxiosRequestConfig<any> | undefined): Promise<AxiosResponse> => axios.put(url, data, config),
patch: async (url: string, data?: any, config?: AxiosRequestConfig<any> | undefined): Promise<AxiosResponse> => axios.patch(url, data, config),
}
};
export default useAxios;
我在这里所做的是通过调用 getAccessTokensSilently()
添加不记名令牌(如果尚未定义的话)。此外,如果我的 URL 中不存在 HTTP,那么我会从我的环境变量中附加默认的 API URL - 这意味着我可以保持我的请求简短而不必使用完整的URL 每次。
现在我根据我的用户 API 定义一个钩子如下:
# src/api/useUserApi.ts
import { UserInterface } from '[REDACTED]/types';
import { AxiosResponse } from 'axios';
import useAxios from './useAxios';
const useUserApi = () => {
const { get, post, put } = useAxios();
return {
getUser: (id: string): Promise<AxiosResponse<UserInterface>> => get(`user/${id}`),
putUser: (user: UserInterface) => put('user', user),
postUser: (user: UserInterface) => post('user', user),
}
};
export default useUserApi;
你可以看到我从axios中暴露了底层的HTTP方法,然后在API具体场景getUser
、putUser
和postUser
中使用它们。
现在我可以继续在某些应用程序逻辑中调用我的 API,将 API 代码保持在绝对最低限度,但仍然允许完全传递和键入 Axios 对象。
import { useAuth0 } from '@auth0/auth0-react';
import { useNavigate } from 'react-router';
import useUserApi from '../../../api/useUserApi';
const LoginCallback = (): JSX.Element => {
const navigate = useNavigate()
const { user, isAuthenticated, isLoading } = useAuth0();
const { getUser, putUser, postUser} = useUserApi();
const saveUserToApi = async () => {
if (!user?.sub) {
throw new Error ('User does not have a sub');
}
// Try and find the user, if 404 then create a new one for this Auth0 sub
try {
const userResult = await getUser(user.sub);
putUser(Object.assign(user, userResult.data));
navigate('/');
} catch (e: any) {
if (e.response.status === 404) {
postUser({
id: user.sub,
email: user.email,
name: user.name,
givenName: user.givenName,
familyName: user.familyName,
locale: user.locale,
});
navigate('/');
}
}
}
if (isLoading) {
return <div>Logging you in...</div>;
}
if (isAuthenticated && user?.sub) {
saveUserToApi();
return <p>Saved</p>
} else {
return <p>An error occured whilst logging in.</p>;
}
};
export default LoginCallback;
可以注意到上面的postUser
、putUser
和getUser
API请求都是一个行,只有声明(const { getUser, putUser, postUser} = useUserApi();
)和否则需要导入。
这个答案绝对是站在巨人的肩膀上,但我认为它对喜欢保持 API 通话尽可能干净的人来说同样有用。