Skip to content

Commit 6ea0474

Browse files
authored
devops: Docs refactored heavily and provided meta data. (#743)
1 parent c7f9242 commit 6ea0474

8 files changed

+664
-322
lines changed

docs/configuring-graphql-client-for-user-session.md

Lines changed: 130 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1+
---
2+
title: "Configuring GraphQL Client for User Session"
3+
description: "A step-by-step guide on how to configure your GraphQL client to handle user sessions, authentication, and more when working with WooGraphQL and WPGraphQL."
4+
keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, user session, authentication, configuration, client"
5+
author: "Geoff Taylor"
6+
---
7+
18
# Configuring a GraphQL Client for WooCommerce User Session Management
29

310
In this comprehensive guide, we'll walk you through the process of configuring a GraphQL client to manage user sessions and credentials when working with WooGraphQL. By following the steps outlined in this tutorial, you'll learn how to create a GraphQL client that maintains a valid WooCommerce session in the `woocommerce_sessions` DB table. This knowledge will enable you to build robust applications that interact smoothly with WooCommerce while providing a seamless experience for your users and shortening development time.
411

512
By properly handling the session token, you can implement session pass-off functionality, allowing you to fallback on the cart page, my-account page, or any other page living in WordPress that relies on user sessions. Note that implementing the session pass-off functionality is out of the scope of this guide. So, let's dive in and explore the intricacies of setting up a GraphQL client that effectively manages user sessions for your e-commerce store!
613

7-
When using WooGraphQL cart and customer functionality, there are certain prerequisites. A WooGraphQL session token, distributed by the QL Session Handler, must be passed as an HTTP header with the name `woocommerce-session`, prefixed with `Session`. This header should be included in all session data-altering mutations. Note that the required name `woocommerce-session` can be changed using WordPress filters.
14+
## Sending the `woocommerce-session` HTTP request header
15+
16+
When using WooGraphQL cart and customer functionality, there are certain prerequisites. A WooGraphQL session token, distributed by the QL Session Handler, must be passed as an HTTP header with the name `woocommerce-session`, prefixed with `Session `. This header should be included in all session data-altering mutations. Note that the required name `woocommerce-session` can be changed using WordPress filters.
817

918
For simple requests using `fetch`, this is quite easy to implement. Here's an example of a WooGraphQL request executed with `fetch`, performing a cart query and passing the woocommerce-session header with a value of `Session ${sessionToken}`. The `sessionToken` is read from `localStorage`.
1019

@@ -41,7 +50,9 @@ fetch(endpoint, {
4150
.then((data) => console.log(data));
4251
```
4352

44-
However, if you're using a library or framework like Apollo, configuring a middleware layer is required, which can be confusing if not explained or demonstrated effectively. In this guide, we'll walk you through setting up the Apollo Client and its middleware to work with WooGraphQL.
53+
This works for simple streamlined applications that don't rely heavily on cart functionality. Note that this example also does not retrieve the updated token from the `woocommerce-session` HTTP response header.
54+
55+
And if you're using a library or framework like Apollo, configuring middleware and afterware layers are required, which makes things more confusing if not explained or demonstrated effectively. In this guide, we'll walk you through setting up the Apollo Client and its middleware/afterware to work with WooGraphQL.
4556

4657
## Creating the Apollo Client instance
4758

@@ -56,12 +67,15 @@ const client = new ApolloClient({
5667
link: from([
5768
createSessionLink(),
5869
createErrorLink(),
70+
createUpdateLink(),
5971
new HttpLink({ uri: endpoint }),
6072
]),
6173
cache: new InMemoryCache(),
6274
});
6375
```
6476

77+
In the example you see the creation of our `client`. It include middleware/afterware callbacks managed by the `ApolloLink` class. For those not familiar with it, the `ApolloLink` class is allowed you to customize the flow of data by defining you networks behavior as a chain of link object. I'm stating this so you know the order of the callbacks is also important and it will be understood why as we define this callbacks themselves.
78+
6579
## Defining the `createSessionLink` function
6680

6781
Next, define the `createSessionLink` function as follows:
@@ -83,8 +97,22 @@ function createSessionLink() {
8397
return {};
8498
});
8599
}
100+
101+
//...rest of code
86102
```
87103

104+
And that's our callback for applying the our session token to each request made through our client. Note that I am using the shorthand method of importing the `setContext` function, however most examples you will find will use the `ApolloLink` class directly to define the link object.
105+
106+
```javascript
107+
mport { ApolloLink } from '@apollo/client';
108+
109+
const consoleLink = new ApolloLink((operation, forward) => {
110+
return operation.setContext(/* our callback */);
111+
});
112+
```
113+
114+
And this works fine too, but is more verbose and kinda overkill if your just make a stateless link like we are here. `Stateless` links are middleware callbacks that don't care to know anything about the context of the operation and just does it own thing regardless of what operation Apollo is about to execute.
115+
88116
## About the environment variables
89117

90118
Before we dive into the guide, it's important to note that the `process.env.*` variables used throughout the tutorial are simply string values stored in an `.env` file and loaded using the [**dotenv**](https://www.npmjs.com/package/dotenv) package. As a reader, you can replace these variables with any values that suit your needs.
@@ -99,12 +127,25 @@ AUTH_KEY_TIMEOUT=30000
99127
GRAPHQL_ENDPOINT=http://woographql.local/graphql
100128
```
101129

102-
## Defining the `getSessionToken` and `fetchSessionToken` functions
130+
With a .env file created you will be ready to move on to what's next, which is defining the `getSessionToken` function.
103131

104-
Here are the `getSessionToken` and `fetchSessionToken` functions:
132+
## Defining the `getSessionToken` function
133+
134+
```javascript
135+
export async function getSessionToken(forceFetch = false) {
136+
let sessionToken = localStorage.getItem(process.env.SESSION_TOKEN_LS_KEY as string);
137+
if (!sessionToken || forceFetch) {
138+
sessionToken = await fetchSessionToken();
139+
}
140+
return sessionToken;
141+
}
142+
```
143+
144+
The function is rather simple. It attempt to retrieve the `sessionToken` from `localStorage`, and if that fails or `forceFetch` is passed it fetches a new one using `fetchSessionToken()`. And `fetchSessionToken` is defined.
105145

106146
```javascript
107147
import { GraphQLClient } from 'graphql-request';
148+
import { GetCartDocument } from './graphql'
108149

109150
// Session Token Management.
110151
async function fetchSessionToken() {
@@ -127,18 +168,11 @@ async function fetchSessionToken() {
127168
return sessionToken;
128169
}
129170

130-
export async function getSessionToken(forceFetch = false) {
131-
let sessionToken = localStorage.getItem(process.env.SESSION_TOKEN_LS_KEY as string);
132-
if (!sessionToken || forceFetch) {
133-
sessionToken = await fetchSessionToken();
134-
}
135-
return sessionToken;
136-
}
137-
```
171+
// ...rest of code
138172

139-
Defining the `GetCartDocument`
173+
```
140174
141-
Here's the `GetCartDocument`:
175+
For this example this works for most case but typically you want the obscure the retrieval of the token and the endpoint from the end-user, especially if dealing with authenticated users. There are a number of a ways to do this like serverless functions or Next.js API routes and they should be doing exactly what is done here retrieve the sessionToken and/or user authentication tokens and nothing else. See the `GetCartDocument` below in `./graphql`.
142176
143177
```javascript
144178
import { gql } from '@apollo/client';
@@ -153,7 +187,7 @@ export const GetCartDocument = gql`
153187

154188
```
155189
156-
It's highly recommended to retrieve the session token outside of the Apollo Client for better control of its value. To ensure no request gets sent without a session token, we must define an error link to capture failed queries caused by an invalid or expired session token, delete the current session, and retrieve a new one. Here's the `createErrorLink` function:
190+
By separating retrieval of the `sessionToken` it also enables better control of its value. We can take this further by ensuring no request gets sent without a session token or with an invalid session token. This is where our `createErrorLink` error handling middleware and `createUpdateLink` token updating afterware come into play. First `createErrorLink`, to capture failed queries caused by an invalid or expired session tokens, deleting the currently stored session token, and retrieve a new one.
157191
158192
```javascript
159193
import { onError } from '@apollo/client/link/error';
@@ -202,14 +236,93 @@ function createErrorLink() {
202236
});
203237
});
204238
}
205-
return message;
206239
});
207240
}
208241
return observable;
209242
});
210243
}
211244
```
212245
213-
With the creation of the error link, we now have an Apollo Client that completely manages the WooCommerce session. Note that this doesn't account for all use cases, specifically dealing with registered WooCommerce customers. In such cases, you'll need to use a second JWT for identifying their WordPress account, called an Authentication Token or auth token for short. For handling user authentication, auth tokens, and refresh tokens, refer to the next guide.
246+
There is a lot going on here but is not very complex once broken down.
247+
248+
```javascript
249+
const targetErrors = [
250+
'The iss do not match with this server',
251+
'invalid-secret-key | Expired token',
252+
'invalid-secret-key | Signature verification failed',
253+
'Expired token',
254+
'Wrong number of segments',
255+
];
256+
```
257+
258+
This our the error messages we are targeting. Each are exclusively results of an invalid tokens.
259+
260+
```javascript
261+
let observable;
262+
if (graphQLErrors?.length) {
263+
graphQLErrors.map(({ debugMessage, message }) => {
264+
if (targetErrors.includes(message) || targetErrors.includes(debugMessage)) {
265+
observable = new Observable((observer) => {
266+
getSessionToken(true)
267+
.then((sessionToken) => {
268+
operation.setContext(({ headers = {} }) => {
269+
const nextHeaders = headers;
270+
271+
if (sessionToken) {
272+
nextHeaders['woocommerce-session'] = `Session ${sessionToken}`;
273+
} else {
274+
delete nextHeaders['woocommerce-session'];
275+
}
276+
277+
return {
278+
headers: nextHeaders,
279+
};
280+
});
281+
})
282+
```
283+
284+
This is the scary looking part if you are not familar with observables, but don't be. Observables are similar to Promises, but instead of handling a single asynchronous event, they handle multiple events over time. While Promises resolve only once and return a single value, Observables emit multiple values and can be canceled, providing greater control over asynchronous data streams.
285+
Our usage here is to tell Apollo to retry the last operation after we have retrieved a new token with `getSessionToken` if the current `graphQLError` matches any of our targetted errors, otherwise `observable` is left as a `undefined` value and Apollo continues as normal.
286+
287+
Next is the `createUpdateLink` callback, responsible for retrieving an updated `sessionToken` from the `woocommerce-session` HTTP response token. The reason for this is the session token generated by WooGraphQL is self-managing and a new token with an updated expiration time of 14 days from the last action is generated on each request that a `woocommerce-session` HTTP request header is sent. To retrieve a store this updated token we use Apollo afterware.
288+
289+
## Defining the `createUpdateLink` function
290+
291+
Next, define the `createUpdateLink` function as follows:
292+
293+
```javascript
294+
import { setContext } from '@apollo/client/link/context';
295+
296+
function createUpdateLink(operation, forward) => {
297+
return forward(operation).map((response) => {
298+
/**
299+
* Check for session header and update session in local storage accordingly.
300+
*/
301+
const context = operation.getContext();
302+
const { response: { headers } } = context;
303+
const oldSessionToken = localStorage.getItem(process.env.SESSION_TOKEN_LS_KEY as string);
304+
const sessionToken = headers.get('woocommerce-session');
305+
if (sessionToken) {
306+
if ( oldSessionToken !== session ) {
307+
localStorage.setItem(process.env.SESSION_TOKEN_LS_KEY as string, sessionToken);
308+
}
309+
}
310+
311+
return response;
312+
});
313+
}
314+
```
315+
316+
This is an our Apollo afterware callback, and if you are wondering how does this differ from Apollo middleware look at the following.
317+
318+
```javascript
319+
return forward(operation).map((response) => {
320+
```
321+
322+
By calling `.map()` on the result of `forward()`, we're telling Apollo to execute this after operation completion, you can even take it a further by modifying the `response` object if necessary. It is not here, but I figured I should at least state that fact.
323+
324+
We also put after the `createErrorLink` callback in our `from()` call when defining the `ApolloClient` to ensure it's never executed on a request failed due to an invalid token.
325+
326+
And with the creation of the `createUpdateLink` link, we now have an Apollo Client that completely manages the WooCommerce session. Note that this doesn't account for all use cases, specifically dealing with registered WooCommerce customers. In such cases, you'll need to use a second JWT for identifying their WordPress account, called an Authentication Token or auth token for short. For handling user authentication, auth tokens, and refresh tokens, refer to the next guide.
214327
215328
This should provide you with a solid foundation for setting up a GraphQL client that effectively manages user sessions in your e-commerce application. By following the steps outlined, you'll be able to create a seamless experience for your users when interacting with both WooCommerce, ultimately saving development time and effort.

0 commit comments

Comments
 (0)