Apollo 客户端未在 Nextjs Lamda 中发送 JWT 不记名令牌

Posted

技术标签:

【中文标题】Apollo 客户端未在 Nextjs Lamda 中发送 JWT 不记名令牌【英文标题】:Apollo Client not sending JWT bearer token in Nextjs Lamda 【发布时间】:2020-04-19 23:22:58 【问题描述】:

TLDR

标头 authorization 没有随 apollo 一起发送。这导致You do not have the appropriate capabilities to perform this action

const apolloClient = ( cookies:  token = null , headers ) => 
  const authLink = setContext(_ => 
    console.info(token) //correct tokens
    return 
      headers: 
        ...headers,
        authorization: `Bearer $token`,
      ,
    
  )

  return new ApolloClient(
    link: ApolloLink.from([
      onError(( graphQLErrors, networkError ) => 
          console.error(graphQLErrors, networkError)
      ),
      authLink.concat(
        createHttpLink(
          uri: graphQL,
          credentials: 'same-origin',
          fetch: fetch,
        )
      ),
    ]),
    cache: new InMemoryCache(),
  )

加长版

我需要在我的 nextjs lamba 文件之一中对自己进行身份验证,该文件现在位于 /pages/api/*

我目前有一个apollo.serverless.js 文件,它被导入到pages/api/users/update/role/[userId].js 我进行数据库交互的地方。

这对我来说有点混乱。我不确定这是唯一的方法,但这是我的大脑带我去的地方。我正在传递 req intoapollo.serverless.js`,以便我可以访问 cookie 中的不记名令牌。

这可能就像我的 apollo.client 设置不正确一样简单。

我正在直接测试链接 http://localhost:3000/api/users/update/role/dXNlcjoxMTg=,因为条带 webhook 将使用查询参数直接访问此 url。

pages/api/users/update/role/[userId].js

import  useQuery, useMutation  from '../../../../../lib/apollo/apollo.serverless'
import  updateMemberRole, allUsers  from '../../../queries/users'

export default async (req, res) => 
  let data
  try 
    const mutationInfo = await useMutation(
      
        mutation: updateMemberRole,
        variables: 
          userId: req.query.userId,
        ,
      ,
      req
    )
    data = mutationInfo.data
   catch (err) 
    data = err
  
  res.status(200).json( data )



使用 [userId] 发送的标头

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,en-GB;q=0.8,fr;q=0.7
Cache-Control: no-cache
Connection: keep-alive
Cookie: analyticsCookieAccepted=1; analyticsCookieNoticeClosed=1; _ga=GA1.1.2044509544.1574855120; wordpress_test_cookie=WP+Cookie+check; OPTOUTMULTI=0:0%7Cc4:0%7Cc3:0%7Cc2:0; wp-settings-45=editor%3Dhtml%26libraryContent%3Dbrowse%26imgsize%3Dfull; wp-settings-time-45=1575018303; theme=light; wp-settings-time-1=1575496501; utag_main=v_id:016eacdb7ad1001fa3af49cf1fec01069001606100fb8$_sn:18$_se:1$_ss:1$_st:1575891251585$ses_id:1575889451585%3Bexp-session$_pn:1%3Bexp-session; wordpress_logged_in_86a9106ae65537651a8e456835b316ab=glasshousegames%7C1578351721%7CtL4KMHIW7tTAUuCzUOJd8r6Mu5buST9mheH2tn9WFQs%7C593e133b11f6c0745f577e32d66db0cf1ccfa012504f9015fc64c515d2df77d2; token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3QiLCJpYXQiOjE1NzgwNTgyOTIsIm5iZiI6MTU3ODA1ODI5MiwiZXhwIjoxNTc4NjYzMDkyLCJkYXRhIjp7InVzZXIiOnsiaWQiOiIxMTgifX19.iMSh4KjuON3otpOqO3TXpYAh2bQYu48sqm9pzsgeBis
Host: localhost:3002
Pragma: no-cache
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36

apollo.serverless.js

import dotenv from 'dotenv'
import  fetch  from 'cross-fetch/polyfill'
import  createHttpLink  from 'apollo-link-http'
import  ApolloClient  from 'apollo-client'
import  InMemoryCache  from 'apollo-cache-inmemory'
import  setContext  from 'apollo-link-context'
import  onError  from 'apollo-link-error'
import  ApolloLink  from 'apollo-link'
import  graphQL  from 'app.config'
import ApolloClass,  sortParams  from '~/lib/apollo/apollo.class'

dotenv.config()

const apolloClient = ( cookies:  token = null , headers ) => 
  const authLink = setContext(_ => 
    return 
      headers: 
        ...headers,
        authorization: `Bearer $token`,
      ,
    
  )

  return new ApolloClient(
    link: ApolloLink.from([
      onError(( graphQLErrors, networkError ) => 
        if (graphQLErrors)
          graphQLErrors.forEach(( message, locations, path ) => console.log(`[GraphQL error]: Message: $message, Location: $locations, Path: $path`))
        if (networkError) console.log(`[Network error]: $networkError`)
      ),
      authLink.concat(
        createHttpLink(
          uri: graphQL,
          credentials: 'same-origin',
          fetch: fetch,
        )
      ),
    ]),
    cache: new InMemoryCache(),
  )


export const useQuery = async function(query) 
  const  options =   = sortParams([...arguments])
  const  loading, data: queryData, error, ...props  = await apolloClient.query(
    query,
    ...options,
  )
  let transformData = 

  if (queryData) transformData = new ApolloClass(queryData).start()
  return 
    queryData,
    error,
    loading,
    data: transformData,
  


export const useMutation = async function(mutation, req) 
  const  data  = await apolloClient(req).mutate(mutation)
  return  data 


错误

// 20200106132714
// http://localhost:3002/api/users/update/role/dXNlcjoxMTg=


  "data": 
    "graphQLErrors": [
      
        "message": "You do not have the appropriate capabilities to perform this action",
        "category": "user",
        "locations": [
          
            "line": 2,
            "column": 3
          
        ],
        "path": [
          "updateUser"
        ]
      
    ],
    "networkError": null,
    "message": "GraphQL error: You do not have the appropriate capabilities to perform this action"
  

所以我认为问题在于我的标头没有被发送,因为我在此处详述的标头中看不到它们,在查看 http://localhost:3000/api/users/update/role/cm9sZTpzdGFuZGFyZA== 时我在标头中看不到它们

值得一提的是,我正在使用 GraphQL 和 wp-GraphQL 以及 JWT 作为我的身份验证令牌。并使用JWT Authentication for WP REST API 处理 JWT 令牌。我没有引起更多关注的原因是我在标题中看不到任何Bearer SomeToken,所以我确信一旦发送令牌,一切正常。 JWT 在我通过 cookie 进行的所有交互中都能正常工作。

.htaccess

RewriteCond %HTTP:Authorization ^(.)
RewriteRule ^(.) - [E=HTTP_AUTHORIZATION:%1]
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

<ifmodule mod_headers.c="">
    # SetEnvIf Origin "^(.*\.domain\.com)$" ORIGIN_SUB_DOMAIN=$1
    SetEnvIf Origin "http(s)?://(www\.)?(localhost|now.sh|dev.domain1.games)$" ORIGIN_SUB_DOMAIN=$1
    Header set Access-Control-Allow-Origin "%ORIGIN_SUB_DOMAINe" env=ORIGIN_SUB_DOMAIN
    Header set Access-Control-Allow-Methods: "*"
    Header set Access-Control-Allow-Headers: "Origin, X-Requested-With, Content-Type, Accept, Authorization, Content-Disposition"
</ifmodule>

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/jpg "access 1 year"
    ExpiresByType image/jpeg "access 1 year"
    ExpiresByType image/gif "access 1 year"
    ExpiresByType image/png "access 1 year"
    ExpiresByType text/css "access 1 month"
    ExpiresByType text/html "access 1 month"
    ExpiresByType application/pdf "access 1 month"
    ExpiresByType text/x-javascript "access 1 month"
    ExpiresByType application/x-shockwave-flash "access 1 month"
    ExpiresByType image/x-icon "access 1 year"
</IfModule>

【问题讨论】:

我认为您应该确保已发送身份验证标头 是的,这就是我遇到的问题。但无论我尝试什么,我都无法将其发送出去。所以帮助会有很大的 cookie 是“httpOnly”吗? 有趣的想法。尽管我在本地进行测试并且它适用于正常请求,但我肯定会检查这一点。只是不在兰巴里面。但我会确认 cookie 在哪里可用。但我已经确认我可以从 cookie 中访问令牌。但问题似乎是我无法设置自定义标题。 【参考方案1】:

所以我在查看 this 并注意到他们的中间件使用了不同的措辞,也许它更明确,但是......我会探索这个......

const authLink = new ApolloLink((operation, forward) => 
  operation.setContext(
     headers: 
       ...headers,
       authorization: `Bearer $token`,
     );
  return forward(operation);
);

【讨论】:

【参考方案2】:

我认为您的请求已经将 cookie 展开在标头中,而不是在 cookie 对象中。所以从那里提取它应该是可行的。您的 req 可能是以前的请求重建 - 其中它只是复制标头而不是创建 cookie 对象?这段代码应该会给你一些想法。

const apolloClient = (req) => 
  const authLink = setContext(_ => 
    const cookieToken = req.getHeader('Cookie')
        .map(ck => /^([^=]+)=(.+)/.exec(ck))
        .filter(([_, key]) => key == "token")
        .map(([_, _, cooked]) => cooked)

    return 
      headers: 
        ...req.headers,
        authorization: `Bearer $req.cookies.token || cookieToken.shift()`,
      ,
    
  )

【讨论】:

感谢您的回答。我实际上确实可以访问我的token,但它只是奇怪地没有显示在请求标头中 你有 ApolloLink.from([ onError(...), authLink.concat(createhttplink(...))] 我想知道 ApolloLink.from([onError(.. .)、authLink、createHttpLink(...)]) 可能无法将它们等效地放在一起?【参考方案3】:

将此添加到您的 .htaccess 文件中

SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

【讨论】:

谢谢你。我的文件里已经有了这个。 ``` RewriteCond %HTTP:Authorization ^(.) RewriteRule ^(.) - [E=HTTP_AUTHORIZATION:%1] SetEnvIf 授权 "(.*)" HTTP_AUTHORIZATION=$1``` 我认为最好使用 javascript 从您的浏览器运行 fetch 调用到您的服务器提供所需的标头。在您的服务器端检查标头是否正确接收。这只是为了确定丢失的标头是来自您的客户端还是服务器端 我实际上在 apollo-client 中使用fetchauthLink.concat( createHttpLink( uri: graphQL, credentials: 'same-origin', fetch: fetch, )

以上是关于Apollo 客户端未在 Nextjs Lamda 中发送 JWT 不记名令牌的主要内容,如果未能解决你的问题,请参考以下文章

如何在客户端 NextJS 中使用 Apollo GraphQL 订阅?

如何从 NextJS 服务器为 Apollo 客户端补充水分

在 nextJS 上配置 apollo 客户端以进行订阅的问题

使用 Apollo 客户端在 nextJs 中传递授权标头的最佳方法? ReferenceError: localStorage 未定义

Express cookie 解析器未在生产中创建 cookie

将 Strapi 连接到 NextJS/Apollo 客户端 GraphQL - 未提供所需类型“XXX”的变量“$input”