Problemetiketten
Frage
Das genaue Szenario, das ich erreichen möchte, wird hier erwähnt:
https://github.com/apollographql/apollo-link/tree/master/packages/apollo-link-error#retrying -failed-requests
operation.setContext({
headers: {
...oldHeaders,
authorization: getNewToken(),
},
});
Wenn jedoch ein Token abgelaufen ist, sollte ein asynchrones resfreshToken
aufgerufen und "wartet" werden, bevor getNewToken
ein gültiges Authentifizierungstoken zurückgeben kann. Ich denke.
Meine Frage ist, wie man einen asynchronen resfreshToken
Aufruf durchführt. Ich habe await refreshToken()
versucht (was ein Versprechen auflöst, wenn es abgeschlossen ist), aber aus meinen protokollierten Stack-Traces scheint es, dass dies ziemlich viel mit RxJS durcheinanderbringt. Ich bin ein RxJS n00b, jede Hilfe wird sehr geschätzt!
Wenn Sie mit Versprechen besser vertraut sind, können Sie den fromPromise
Helfer verwenden
import { fromPromise } from 'apollo-link';
return fromPromise(refreshToken().then(token => {
operation.setContext({
headers: {
...oldHeaders,
authorization: token,
},
});
return forward(operation);
}))
@thymikee Habe deine Lösung
Uncaught (in promise) Error: Network error: Error writing result to store for query:
query UserProfile($id: ID!) {
UserProfile(id: $id) {
id
email
first_name
last_name
activated
created_at
updated_at
last_active
roles {
id
name
__typename
}
permissions {
name
value
__typename
}
profile {
address
secondary_email
phone {
id
number
type {
id
name
__typename
}
__typename
}
__typename
}
__typename
}
}
Cannot read property 'UserProfile' of undefined
at new ApolloError (ApolloError.js:43)
at QueryManager.js:327
at QueryManager.js:759
at Array.forEach (<anonymous>)
at QueryManager.js:758
at Map.forEach (<anonymous>)
at QueryManager.webpackJsonp../node_modules/apollo-client/core/QueryManager.js.QueryManager.broadcastQueries (QueryManager.js:751)
at QueryManager.js:254
Eine weitere Untersuchung zeigt, dass onError
des Apollo-Links zweimal aufgerufen wird, wenn der obige Code verwendet wird. Selbst die Begrenzung des refresh token
Versprechens, einmal ausgeführt zu werden, behebt den Fehler nicht.
Was passiert ist:
1) Erstabfrage wird ausgeführt
2) Es schlägt fehl und führt den apollo-Link onError
3) ?? Es läuft wieder apollos Link onError
4) Versprechen, Token zu aktualisieren, in onError
beendet die Ausführung und wird aufgelöst.
5) (Die anfängliche Abfrage wird kein zweites Mal ausgeführt, nachdem die Zusage erfolgreich war)
6) Die anfängliche Abfrage gibt ein Ergebnis mit data
als undefiniert zurück
Wir hoffen, dass jemand eine Lösung dafür findet, sonst müssen wir wieder langlebige Zugriffstoken verwenden, anstatt sie nach Ablauf zu aktualisieren.
Wenn Ihre Token-Abruflogik korrekt ist, sollte onError
nur einmal aufgerufen werden. Offenbar haben Sie Probleme mit Ihrer Token-Abfrage
@thymikee Async- Anfrage mit einem Dummy-Versprechen
Code:
return fromPromise(
new Promise((resolve) => {
let headers = {
//readd old headers
...operation.getContext().headers,
//switch out old access token for new one
authorization: `Bearer mynewaccesstoken`,
};
operation.setContext({
headers
});
return resolve(forward(operation));
})
)
Bearbeiten: fromPromise
und es funktioniert korrekt. Irgendwie endet die Verarbeitung des Link-Stacks, bevor das Ergebnis zurückgegeben wird, sodass forward(operation)
nicht ausgeführt wird.
Nach der Analyse des fromPromise
Codes und des Commit #172 kann fromPromise
nur in Vermutungen mit einem Apollo Link Objekt verwendet werden.
Bei der Recherche nach einer Lösung bin ich schließlich auf dieses Projekt gestoßen: apollo-link-token-refresh
Mein apollo-Link-Stack sieht jetzt wie folgt aus:
[
refreshTokenLink,
requestLink,
batchHttpLink
]
refreshTokenLink
wird immer aufgerufen, um das Zugriffstoken zu überprüfen, bevor eine Antwort an den graphql-Endpunkt ausgeführt wird, und funktioniert wie ein Zauber.
Dies setzt leider voraus, dass der Aufruf des graphql-Endpunkts immer authentifiziert werden muss (was in meinem Fall der Fall ist).
Es sieht so aus, als ob onError
Callback keine aync
Funktion oder Promise
Rückgaben akzeptiert. Siehe Code https://github.com/apollographql/apollo-link/blob/59abe7064004b600c848ee7c7e4a97acf5d230c2/packages/apollo-link-error/src/index.ts#L60 -L74
Dieses Problem wurde bereits gemeldet: #190
Ich denke, es würde besser funktionieren, wenn apollo-link-error
mit Promise
umgehen kann, ähnlich wie es apollo-link-retry
hier tut: #436
Wenn ich das gleiche Problem habe, muss ich bei Verwendung von apollo mit React native ein Token aus AsyncStorage onError entfernen, also muss es eine asynchrone Funktion sein
Diese Lösung hat bei mir funktioniert: https://stackoverflow.com/a/51321068/60223
Ich habe dies gelöst, indem ich ein Dienstprogramm promiseToObservable.js
:
import { Observable } from 'apollo-link';
export default promise =>
new Observable((subscriber) => {
promise.then(
(value) => {
if (subscriber.closed) return;
subscriber.next(value);
subscriber.complete();
},
err => subscriber.error(err)
);
return subscriber; // this line can removed, as per next comment
});
und dann
import { onError } from 'apollo-link-error';
import promiseToObservable from './promiseToObservable';
export default (refreshToken: Function) =>
onError(({
forward,
graphQLErrors,
networkError = {},
operation,
// response,
}) => {
if (networkError.message === 'UNAUTHORIZED') { // or whatever you want to check
// note: await refreshToken, then call its link middleware again!
return promiseToObservable(refreshToken()).flatMap(() => forward(operation));
}
});
@crazy4groovy danke für dein Beispiel, es hilft wirklich. Es gibt jedoch ein kleines Problem damit: subscriber
ist ein ungültiger Rückgabewert gemäß der Eingabe von Observable
: es sollte eher ZenObservable.SubscriptionObserver
:
export declare type Subscriber<T> = ZenObservable.Subscriber<T>;
export declare const Observable: {
new <T>(subscriber: Subscriber<T>): Observable<T>;
};
export declare namespace ZenObservable {
interface SubscriptionObserver<T> {
closed: boolean;
next(value: T): void;
error(errorValue: any): void;
complete(): void;
}
type Subscriber<T> = (observer: SubscriptionObserver<T>) => void | (() => void) | Subscription;
}
dh es ist sicher, stattdessen undefined zurückzugeben. Ich denke, es sollte in der README-Datei des Projekts erwähnt werden.
UPD: Ich habe dazu eine PR hinzugefügt: https://github.com/apollographql/apollo-link/pull/825
Dieses Problem hat bei Google einen hohen Stellenwert, daher teile ich meine Lösung hier, um einigen Leuten zu helfen: https://gist.github.com/alfonmga/9602085094651c03cd2e270da9b2e3f7
Ich habe deine Lösung ausprobiert, stehe aber vor einem neuen Problem:
Argument of type '(this: Observable<{}>, observer: Subscriber<{}>) => Observable<{}> | Promise<{}>' is not assignable to parameter of type '(this: Observable<{}>, subscriber: Subscriber<{}>) => TeardownLogic'.
Type 'Observable<{}> | Promise<{}>' is not assignable to type 'TeardownLogic'.
Type 'Observable<{}>' is not assignable to type 'TeardownLogic'.
Property 'unsubscribe' is missing in type 'Observable<{}>' but required in type 'Unsubscribable'
Wie speichern Sie das neue Authentifizierungstoken, wenn es aktualisiert wurde?
Sicher, ich kann neue Header in der Wiederholungsanfrage setzen, aber das ursprüngliche Zugriffstoken (das ich in Cookies speichere) wird nicht aktualisiert, was bedeutet, dass jede einzelne Anfrage an den Server das alte Zugriffstoken verwendet (und anschließend .) muss noch einmal aufgefrischt werden).
Aus irgendeinem Grund , warum ich die folgende Fehlermeldung erhalten , wenn ich versuche die Cookies während des Refresh zu aktualisieren (habe ich ein neues Problem hier darüber):
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:470:11)
at setCookie (/root/SimplyTidyAdmin/node_modules/nookies/dist/index.js:98:17)
at /root/SimplyTidyAdmin/.next/server/static/CAhshxrRWHVF6Gzbce~pU/pages/_app.js:1273:63
at process._tickCallback (internal/process/next_tick.js:68:7)
@StupidSexyJake vielleicht würde dir das helfen https://stackoverflow.com/questions/55356736/change-apollo-client-options-for-jwt-token Ich habe ein ähnliches Problem beim Aktualisieren des Tokens
Hallo, danke @crazy4groovy. Ich habe Ihre Lösung ausprobiert, aber ich habe immer noch das Problem, dass die Middleware, bei der ich das Token an die graphql-Anfrage anhänge, aufgerufen wird, bevor das neue Token auf die Anfrage gesetzt wird. Daher enthält der Header immer noch das ungültige Token.
Ein paar Hintergrundinfos: Wir erhalten einen Netzwerkfehler, wenn das Token ungültig ist, und über ein Refresh-Token können wir ein neues erhalten und es erneut versuchen. Da die Middleware jedoch aufgerufen wird, bevor das Aktualisierungstoken gesammelt und auf lokalen Speicher gesetzt wird, enthält sie immer noch das ungültige Token. Die Token-Aktualisierungslogik funktioniert einwandfrei, da wir dann am Ende den neuen Token-Satz erhalten. Ich habe das Problem ein wenig debuggt und das Timing ist wie folgt:
onError
es über die promiseToObservable
Logik behandelt und erneut versucht.onRefreshToken
Versprechen zu erhalten, befindet sich die Middleware bereits im zweiten Durchlauf mit dem alten Token.Hier ist ein Ausschnitt dieser Teile (wobei onRefreshtoken übersprungen wird. Es ist eine asynchrone Funktion, die ein Versprechen zurückgibt):
const promiseToObservable = (promise: Promise<any>) =>
new Observable((subscriber: any) => {
promise.then(
value => {
console.log(subscriber);
if (subscriber.closed) return;
subscriber.next(value);
subscriber.complete();
},
err => subscriber.error(err)
);
});
const authMiddleware = setContext((operation: GraphQLRequest) => {
const token = localStorage.getItem('ca_token');
return {
headers: {
...(token && !isSkipHeader(operation)
? { authorization: `Bearer ${token}` }
: {})
}
};
});
const errorLink = onError(
({
networkError,
graphQLErrors,
operation,
forward
}: ErrorResponse): any => {
if (networkError) {
switch (networkError.statusCode) {
case 401:
console.warn('Refreshing token and trying again');
// await refreshToken, then call its link middleware again
return promiseToObservable(onRefreshToken(client.mutate)).flatMap(() => forward(operation));
default:
// Handle all other errors here. Irrelevant here.
}
}
if (graphQLErrors) {
// Handle gql errors, irrelevant here.
}
}
);
Könnten Sie mir bitte sagen, was ich hier vermisse? Vielen Dank im Voraus...
Okay, sorry für die Verwirrung, falls vorhanden...
Ich habe die Antwort gefunden und es ist eine dumme, nachdem ich stundenlang danach gesucht und - natürlich - gefunden habe, nachdem ich hier gepostet habe: während der Initialisierung des apollo-Clients habe ich Middleware und Fehlerlink getauscht. Jetzt funktioniert es. Fehlerlink sollte natürlich zuerst sein..
alt: link: from([authMiddleware, errorLink, /* others */])
neu: link: from([errorLink, authMiddleware, /* others */])
Entschuldigung nochmal..
Hallo Leute,
Ich habe das folgende Problem bei der Verwendung von onError für Aktualisierungstoken. Zum Zweck von SSR mit nextjs sammle ich Daten aus allen graphql-Abfragen, aber was passiert, wenn wir zum Beispiel 2 Abfragen haben und jede von ihnen mit einem Fehler endet, weil das jwt-Token abgelaufen ist. Dann feuert es zweimal den onError und wir fordern zweimal Aktualisierungstoken an, was teuer ist. Ich kann mir nicht vorstellen, woher das Problem kommen könnte. Hier ist der Code, den ich verwende. Können Sie bitte dabei helfen.
https://gist.github.com/shaxaaa/15817f1bcc7b479f3c541383d2e83650
Ich habe ein bisschen mit diesem Problem gerungen, aber endlich habe ich es zum Laufen gebracht. Ich warf ein Paket zusammen.
https://github.com/baleeds/apollo-link-refresh-token
Der Hauptunterschied zwischen diesem Paket und dem Paket namens apollo-link-token-refresh besteht darin, dass dieses Paket auf einen Netzwerkfehler wartet, bevor es eine Aktualisierung versucht.
Lassen Sie es mich wissen, wenn Sie Ideen für Änderungen haben.
Hier ist die grundlegende Verwendung:
const refreshTokenLink = getRefreshTokenLink({
authorizationHeaderKey: 'Authorization',
fetchNewAccessToken,
getAccessToken: () => localStorage.getItem('access_token'),
getRefreshToken: () => localStorage.getItem('refresh_token'),
isAccessTokenValid: accessToken => isTokenValid(accessToken),
isUnauthenticatedError: graphQLError => {
const { extensions } = graphQLError;
if (
extensions &&
extensions.code &&
extensions.code === 'UNAUTHENTICATED'
) {
return true;
}
return false;
},
});
Ich habe dies gelöst, indem ich ein Dienstprogramm
promiseToObservable.js
:import { Observable } from 'apollo-link'; export default promise => new Observable((subscriber) => { promise.then( (value) => { if (subscriber.closed) return; subscriber.next(value); subscriber.complete(); }, err => subscriber.error(err) ); return subscriber; // this line can removed, as per next comment });
und dann
import { onError } from 'apollo-link-error'; import promiseToObservable from './promiseToObservable'; export default (refreshToken: Function) => onError(({ forward, graphQLErrors, networkError = {}, operation, // response, }) => { if (networkError.message === 'UNAUTHORIZED') { // or whatever you want to check // note: await refreshToken, then call its link middleware again! return promiseToObservable(refreshToken()).flatMap(() => forward(operation)); } });
Ich benutze es und habe festgestellt, dass es nach der Aktualisierungs-Token-Anforderung immer noch ein altes Token verwendet. also ich versuche es wie folgt:
return promiseToObservable(refreshToken()).flatMap((value) => {
operation.setContext(({ headers = {} }) => ({
headers: {
// re-add old headers
// ...headers,
Authorization: `JWT ${value.token}`
}
}));
return forward(operation)
});
und es funktioniert.
Es gibt jedoch immer noch ein Problem, dass, wenn ich ...headers
hinzufüge (bedeutet, alte Header neu hinzuzufügen), etwas nicht stimmt, bevor die Weiterleitungsanfrage gesendet wird:
ERROR Error: Network error: Cannot read property 'length' of null
Ich denke, die Autorisierung in ...Headern kann mit der neuen Autorisierung kollidieren.
das obige problem ist in apollo-angular "apollo-angular-link-http": "^1.6.0",
und nicht in apollo-client "apollo-link-http": "^1.5.16",
während der link-error der gleiche ist "apollo-link-error": "^1.1.12",
eine andere Syntax :augen:
import Vue from 'vue'
import { Observable } from 'apollo-link'
import { onError } from 'apollo-link-error'
const onGraphqlError = async ({ graphQLErrors = [], observer, operation, forward }) => {
// here you could call the refresh query in case you receive an expired error
for (let error of graphQLErrors)
observer.next(forward(operation)) // this line would retry the operation
}
const onNetworkError = async ({ observer, networkError, operation, forward }) => { }
export const errorHandler = opt => new Observable(async observer => {
try {
const payload = { ...opt, observer }
await Promise.all([onGraphqlError(payload), onNetworkError(payload)])
if (observer.closed) return
observer.complete()
} catch (error) {
observer.error(error)
}
})
Hallo! Ich verwende den vollständigen Websocket-Transport und muss eine Tokenabfrage anfordern. Keine Ahnung wie das geht.
Ich möchte eine Empfangsanforderung ausführen, wenn der Server antwortet, dass accessToken
abgelaufen ist.
import { onError } from "apollo-link-error";
import gql from 'graphql-tag'
// Client: VUE APOLLO
const q = {
query: gql`query token { token { accessToken } }`,
manual: true,
result({ data, loading }) {
if (!loading) {
console.log(data)
}
},
}
const link = onError(({ graphQLErrors, networkError, operation, response, forward }) => {
if (networkError) {
switch (networkError.message) {
case 'accessTokenExpired':
console.log('accessTokenExpired')
return forward(q) // NOT WORKS, NEED HELP
case 'unauthorized':
return console.log('unauthorized')
default:
return forward(operation)
}
}
return forward(operation)
})
export default link
@nikitamarcius wir haben oben Workarounds gepostet, werfen Sie einen Blick auf Observables
Ich kann das Token nicht aktualisieren, kann jemand das Arbeitsbeispiel zur Verfügung stellen?
@ Ramyapriya24 hier ist der Code, den ich verwende.
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/link-context';
import AuthService from 'services/auth-service' // this is my implementation
const asyncAuthLink = setContext(async () => {
// this is an async call, it will be done before each request
const { token } = await AuthService.getCredentials();
return {
headers: {
authorization: token
},
};
},
);
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql',
});
export const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: asyncAuthLink.concat(httpLink),
});
@adrianosk können Sie den geschriebenen Servicecode bereitstellen
AuthService aus 'services/auth-service' importieren // das ist meine Implementierung
const { Token } = erwarten AuthService.getCredentials();
Wenn ich versuche, den Dienst zu importieren, erhalte ich Fehler
Das ist mein Service, es liest nur den AsyncStorage von Reactive-native, also setze ich nach dem Login den Wert dort und vor jeder Anfrage den Code einfach die Info abrufen und in den Header setzen, Sie können dasselbe tun oder localStorage verwenden, wenn Sie sind im Netz.
Wo speichern Sie die Informationen, die Sie verwenden möchten?
du kannst das einfach benutzen
//save the token after login or when it refreshes
localStorage.setItem('token', yourToken);
und benutze es
const asyncAuthLink = setContext(() => {
// grab token from localStorage
const token = localStorage.getItem('token');
return {
headers: {
authorization: token
},
};
},
);
@adrianolsk danke für die Erklärung, aber ich verwende Winkel. Ich kann den Dienst nicht in die Datei grapqh.module.ts importieren. Ich erhalte Fehler, wenn ich den Dienst verwende
Kann jemand wissen, wie man den Dienst in der Datei module.ts verwendet, ohne Klasse und Konstruktor zu verwenden?
Danke
Ich versuche, fromPromise
für die asynchrone Aktualisierung des Tokens zu verwenden.
Im Grunde nach der dritten Box aus diesem Beitrag
Ich erhalte und speichere die Token erfolgreich, aber weder catch
noch filter
oder flatMap
wird aufgerufen. Ich bin mir nicht sicher, wie ich das debuggen soll, daher sind einige Vorschläge hilfreich.
if (token && refreshToken) {
return fromPromise(
getNewToken(client)
.then(({ data: { refreshToken } }) => {
console.log("Promise data: ", refreshToken);
localStorage.setItem("token", refreshToken.token);
localStorage.setItem("refreshToken", refreshToken.refreshToken);
return refreshToken.token;
})
.catch((error) => {
// Handle token refresh errors e.g clear stored tokens, redirect to login, ...
console.log("Error after setting token: ", error);
return;
})
)
.filter((value) => {
console.log("In filter: ", value);
return Boolean(value);
})
.flatMap(() => {
console.log("In flat map");
// retry the request, returning the new observable
return forward(operation);
});
}
@adrianolsk : Dieser Ansatz scheint den Token immer zu aktualisieren , sogar bevor er abgelaufen ist, was im Fall einiger Authentifizierungsdienste (z. B. checkSession von Auth0 ) einen unnötigen Auth0-Server-Roundtrip für jede GraphQL-Anfrage macht.
Ich versuche,
fromPromise
für die asynchrone Aktualisierung des Tokens zu verwenden.
Im Grunde nach der dritten Box aus diesem BeitragIch erhalte und speichere die Token erfolgreich, aber weder
catch
nochfilter
oderflatMap
wird aufgerufen. Ich bin mir nicht sicher, wie ich das debuggen soll, daher sind einige Vorschläge hilfreich.if (token && refreshToken) { return fromPromise( getNewToken(client) .then(({ data: { refreshToken } }) => { console.log("Promise data: ", refreshToken); localStorage.setItem("token", refreshToken.token); localStorage.setItem("refreshToken", refreshToken.refreshToken); return refreshToken.token; }) .catch((error) => { // Handle token refresh errors e.g clear stored tokens, redirect to login, ... console.log("Error after setting token: ", error); return; }) ) .filter((value) => { console.log("In filter: ", value); return Boolean(value); }) .flatMap(() => { console.log("In flat map"); // retry the request, returning the new observable return forward(operation); }); }
Ich habe die Fehlerursache gefunden. Im obigen Code nicht zu sehen, aber ich habe eine map
Funktion verwendet, um jeden der resultierenden Fehler zuzuordnen. Dies führte dazu, dass onError
nichts zurückgab und die Observable wurde nicht für den Vorgang zur Tokenerneuerung abonniert.
Ziemlich verwirrend und ich habe so lange gebraucht, um es herauszufinden. Danke an den Autor des Blogbeitrags für die Hilfe.
ERROR Error: Network error: Cannot read property 'length' of null
@ WilsonLau0755 , ich hatte das gleiche Problem. Gelöst, indem alle null
Header auf eine leere Zeichenfolge ''
.
Warum ist onError nicht nur für die Verwendung mit Async-Await verfügbar?
Hilfreichster Kommentar
Es sieht so aus, als ob
onError
Callback keineaync
Funktion oderPromise
Rückgaben akzeptiert. Siehe Code https://github.com/apollographql/apollo-link/blob/59abe7064004b600c848ee7c7e4a97acf5d230c2/packages/apollo-link-error/src/index.ts#L60 -L74Dieses Problem wurde bereits gemeldet: #190
Ich denke, es würde besser funktionieren, wenn
apollo-link-error
mitPromise
umgehen kann, ähnlich wie esapollo-link-retry
hier tut: #436