使用 graphql 和 apollo 客户端刷新 angular 令牌 [英] refresh token for angular using graphql and apollo client

查看:34
本文介绍了使用 graphql 和 apollo 客户端刷新 angular 令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试设置刷新令牌策略,以便在我的第一个请求返回 401 时使用 GraphQL 和 apollo 客户端在 angular 9 中刷新 JWT.

我为我创建 apolloclient 的 graphql 设置了一个新的 angular 模块.即使使用经过身份验证的请求,一切都很好,但我也需要让我的正常刷新令牌策略正常工作(在刷新令牌周期完成后重新制作并返回原始请求).我只找到了一些资源来帮助解决这个问题,而且我已经非常接近了 - 我唯一缺少的是从我的刷新令牌 observable 返回 observable.

这是认为应该工作的代码:

 import { NgModule } from '@angular/core';从'apollo-angular-link-http'导入{ HttpLinkModule, HttpLink };从'./authentication/services/authentication.service'导入{AuthenticationService};从阿波罗链接"导入 { ApolloLink };import { InMemoryCache } from 'apollo-cache-inmemory';从阿波罗角"导入 { ApolloModule, APOLLO_OPTIONS };导入 { onError } from 'apollo-link-error';导出函数 createApollo(httpLink: HttpLink, authenticationService: AuthenticationService) {const authLink = new ApolloLink((操作,转发)=> {operation.setContext({标题:{授权:'Bearer' + localStorage.getItem('auth_token')}});向前返回(操作);});const errorLink = onError(({ forward, graphQLErrors, networkError, operation }) => {如果(graphQLErrors){graphQLErrors.map(({ message, location, path }) =>{if (message.toLowerCase() === '未经授权') {authenticationService.refreshToken().subscribe(() => {向前返回(操作);});}});}});返回 {链接:errorLink.concat(authLink.concat(httpLink.create({ uri: 'http://localhost:3000/graphql' }))),缓存:新的 InMemoryCache(),};}@NgModule({出口:[ApolloModule,HttpLinkModule],供应商: [{提供:APOLLO_OPTIONS,使用工厂:createApollo,deps: [HttpLink, AuthenticationService]}]})导出类 GraphqlModule { }

我知道我的请求正在第二次工作,因为如果我从 authenticationService 订阅中的 forward(operation) observable 中注销结果,我可以在初始 401 失败后看到结果.

 if (message.toLowerCase() === 'unauthorized') {authenticationService.refreshToken().subscribe(() => {返回转发(操作).订阅(结果 => {控制台日志(结果);});});}

上面向我展示了来自原始请求的数据,但它并没有传递给我最初调用 graphql 的组件.

我远不是 observables 的专家,但我想我需要做某种映射(flatmap、mergemap 等)才能使这个返回正常工作,但我不知道.

任何帮助将不胜感激

TIA

编辑 #1:这让我更接近了,因为它现在实际上订阅了我在 AuthenticationService 中的方法(我在 tap() 中看到了结果)

 const errorLink = onError(({ forward, graphQLErrors, networkError, operation }) => {如果(graphQLErrors){if (graphQLErrors[0].message.toLowerCase() === '未经授权') {返回 authenticationService.refreshToken().管道(switchMap(() => forward(operation)));}}});

我现在看到这个错误被抛出:

<块引用>

core.js:6210 错误类型错误:您提供了一个预期流的无效对象.您可以提供 Observable、Promise、Array 或 Iterable.

编辑 #2:包括 onError() 函数签名的截图:

EDIT #3 这是最终的工作解决方案,以防其他人遇到此问题并需要它来进行角度处理.我不喜欢必须更新我的服务方法来返回一个承诺,然后将该承诺转换为一个 Observable - 但正如@Andrei Gătej 为我发现的那样,这个 Observable 来自不同的命名空间.

import { NgModule } from '@angular/core';从'apollo-angular-link-http'导入{ HttpLinkModule, HttpLink };从'./authentication/services/authentication.service'导入{AuthenticationService};从阿波罗链接"导入 { ApolloLink };import { InMemoryCache } from 'apollo-cache-inmemory';从阿波罗角"导入 { ApolloModule, APOLLO_OPTIONS };导入 { onError } from 'apollo-link-error';从阿波罗链接"导入 { Observable };导出函数 createApollo(httpLink: HttpLink, authenticationService: AuthenticationService) {const authLink = new ApolloLink((操作,转发)=> {operation.setContext({标题:{授权:'Bearer' + localStorage.getItem('auth_token')}});向前返回(操作);});const errorLink = onError(({ forward, graphQLErrors, networkError, operation }) => {如果(graphQLErrors){if (graphQLErrors.some(x => x.message.toLowerCase() === '未授权')) {return promiseToObservable(authenticationService.refreshToken().toPromise()).flatMap(() => forward(operation));}}});返回 {链接:errorLink.concat(authLink.concat(httpLink.create({ uri: '/graphql' }))),缓存:新的 InMemoryCache(),};}const promiseToObservable = (promise: Promise) =>new Observable((subscriber: any) => {承诺.然后(值 =>{如果(订阅者.关闭){返回;}订阅者.下一个(值);订阅者完成();},错误 =>订阅者.错误(错误));});@NgModule({出口:[ApolloModule,HttpLinkModule],供应商: [{提供:APOLLO_OPTIONS,使用工厂:createApollo,deps: [HttpLink, AuthenticationService]}]})导出类 GraphqlModule { }

解决方案

我对 GraphQL 不是很熟悉,但我认为这应该可以正常工作:

if (message.toLowerCase() === 'unauthorized') {返回 authenticationService.refreshToken().管道(switchMap(() => forward(operation)));}

此外,如果您想了解 mergeMap(和 concatMap)的工作原理,您可以查看 这个答案.>

switchMap 只保留一个活动的内部可观察对象,一旦外部值进来,当前内部可观察对象将被取消订阅,并根据新到达的外部值创建一个新的可观察对象和提供的函数.

I'm trying to set up a refresh token strategy to refresh JWT in angular 9 with GraphQL and apollo client when my first request returns a 401.

I have set up a new angular module for graphql where I'm creating my apolloclient. Everything works great even with authenticated requests but I need to have my normal refresh token strategy work as well (re-make and return the original request after refresh token cycle completes). I have found only a few resources to help with this and I've gotten really close - the only thing I'm missing is returning the observable from my refresh token observable.

Here is the code that would think should work:

    import { NgModule } from '@angular/core';
import { HttpLinkModule, HttpLink } from 'apollo-angular-link-http';
import { AuthenticationService } from './authentication/services/authentication.service';
import { ApolloLink } from 'apollo-link';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloModule, APOLLO_OPTIONS } from 'apollo-angular';
import { onError } from 'apollo-link-error';

export function createApollo(httpLink: HttpLink, authenticationService: AuthenticationService) {

  const authLink = new ApolloLink((operation, forward) => {
    operation.setContext({
      headers: {
        Authorization: 'Bearer ' + localStorage.getItem('auth_token')
      }
    });
    return forward(operation);
  });

  const errorLink = onError(({ forward, graphQLErrors, networkError, operation }) => {
    if (graphQLErrors) {
      graphQLErrors.map(({ message, locations, path }) =>
        {
         if (message.toLowerCase() === 'unauthorized') {
          authenticationService.refreshToken().subscribe(() => {
            return forward(operation);
          });
         }
        }
      );
    }
  });

  return {
    link: errorLink.concat(authLink.concat(httpLink.create({ uri: 'http://localhost:3000/graphql' }))),
    cache: new InMemoryCache(),
  };
}


@NgModule({
  exports: [ApolloModule, HttpLinkModule],
  providers: [
    {
      provide: APOLLO_OPTIONS,
      useFactory: createApollo,
      deps: [HttpLink, AuthenticationService]
    }
  ]
})
export class GraphqlModule { }

I know that my request is working the second time because if I log out the result from the forward(operation) observable inside my authenticationService subscription, I can see the results after the initial 401 failure.

 if (message.toLowerCase() === 'unauthorized') {
  authenticationService.refreshToken().subscribe(() => {
    return forward(operation).subscribe(result => {
      console.log(result);
    });
  });
 }

the above shows me the data from the original request, but it's not being passed up to my component that originally called the graphql.

I'm far from an expert with observables but I'm thinking I need to do some kind of map (flatmap, mergemap etc) to make this return work correctly, but I just don't know.

Any help would be greatly appreciated

TIA

EDIT #1: this is getting me closer as it's now actually subscribing to my method in AuthenticationService (I see results in the tap())

    const errorLink = onError(({ forward, graphQLErrors, networkError, operation }) => {
    if (graphQLErrors) {
      if (graphQLErrors[0].message.toLowerCase() === 'unauthorized') {
        return authenticationService.refreshToken()
        .pipe(
          switchMap(() => forward(operation))
        );
      }
    }
  });

I'm now seeing this error being thrown:

core.js:6210 ERROR TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

EDIT #2: Including screenshot of onError() function signature:

EDIT #3 Here is the final working solution in case someone else comes across this and needs it for angular. I don't love having to update my service method to return a promise, and then convert that promise into an Observable - but as @Andrei Gătej discovered for me, this Observable is from a different namespace.

import { NgModule } from '@angular/core';
import { HttpLinkModule, HttpLink } from 'apollo-angular-link-http';
import { AuthenticationService } from './authentication/services/authentication.service';
import { ApolloLink } from 'apollo-link';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloModule, APOLLO_OPTIONS } from 'apollo-angular';
import { onError } from 'apollo-link-error';
import { Observable } from 'apollo-link';


export function createApollo(httpLink: HttpLink, authenticationService: AuthenticationService) {

  const authLink = new ApolloLink((operation, forward) => {
    operation.setContext({
      headers: {
        Authorization: 'Bearer ' + localStorage.getItem('auth_token')
      }
    });
    return forward(operation);
  });

  const errorLink = onError(({ forward, graphQLErrors, networkError, operation }) => {
    if (graphQLErrors) {
      if (graphQLErrors.some(x => x.message.toLowerCase() === 'unauthorized')) {
        return promiseToObservable(authenticationService.refreshToken().toPromise()).flatMap(() => forward(operation));
      }
    }
  });

  return {
    link: errorLink.concat(authLink.concat(httpLink.create({ uri: '/graphql' }))),
    cache: new InMemoryCache(),
  };
}

const promiseToObservable = (promise: Promise<any>) =>
    new Observable((subscriber: any) => {
      promise.then(
        value => {
          if (subscriber.closed) {
            return;
          }
          subscriber.next(value);
          subscriber.complete();
        },
        err => subscriber.error(err)
      );
    });


@NgModule({
  exports: [ApolloModule, HttpLinkModule],
  providers: [
    {
      provide: APOLLO_OPTIONS,
      useFactory: createApollo,
      deps: [HttpLink, AuthenticationService]
    }
  ]
})
export class GraphqlModule { }

解决方案

I'm not quite familiar with GraphQL, but I think this should work fine:

if (message.toLowerCase() === 'unauthorized') {
return authenticationService.refreshToken()
  .pipe(
    switchMap(() => forward(operation))
  );
}

Also, if you'd like to know about how mergeMap(and concatMap) work, you can have a look at this answer.

switchMap keeps only one active inner observable and as soon as an outer value comes in, the current inner observable will be unsubscribed and a new one will be created, based on the newly arrived outer value and the provided function.

这篇关于使用 graphql 和 apollo 客户端刷新 angular 令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆