Skip to content

Commit

Permalink
fix(frontend): refreshToken before graphql request (#192)
Browse files Browse the repository at this point in the history
* fix(frontend): update rereshToken

* wup

* clear token

* fix: auth

* fix

* remove logs

* remove logs
  • Loading branch information
lionelB committed Nov 23, 2020
1 parent 994d326 commit 1d98dfc
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 121 deletions.
1 change: 1 addition & 0 deletions targets/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@socialgouv/cdtn-sources": "^4.35.0",
"@socialgouv/matomo-next": "^1.1.2",
"@socialgouv/react-ui": "^4.17.0",
"@urql/exchange-auth": "^0.1.2",
"@zeit/next-source-maps": "0.0.4-canary.1",
"ace-builds": "^1.4.12",
"argon2": "^0.27.0",
Expand Down
8 changes: 6 additions & 2 deletions targets/frontend/src/hoc/CustomUrqlClient.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { withUrqlClient } from "next-urql";
import { authExchange } from "src/lib/auth/authTokenExchange";
import {
customAuthExchange,
customErrorExchange,
} from "src/lib/auth/exchanges";
import { cacheExchange, dedupExchange, fetchExchange } from "urql";

export const withCustomUrqlClient = (Component) =>
Expand All @@ -21,7 +24,8 @@ export const withCustomUrqlClient = (Component) =>
dedupExchange,
cacheExchange,
ssrExchange,
authExchange(ctx),
customErrorExchange(),
customAuthExchange(ctx),
fetchExchange,
].filter(Boolean),
requestPolicy: "cache-first",
Expand Down
2 changes: 1 addition & 1 deletion targets/frontend/src/hoc/UserProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const ProvideUser = ({ children, tokenData }) => {
}
const isAuth = Boolean(user);
const isAdmin = user?.roles.includes("admin");
console.log({ provideUser: user });

return (
<UserContext.Provider value={{ isAdmin, isAuth, logout, user }}>
{children}
Expand Down
109 changes: 0 additions & 109 deletions targets/frontend/src/lib/auth/authTokenExchange.js

This file was deleted.

94 changes: 94 additions & 0 deletions targets/frontend/src/lib/auth/exchanges.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { errorExchange, makeOperation } from "@urql/core";
import { authExchange } from "@urql/exchange-auth";
import { auth, getToken, isTokenExpired, setToken } from "src/lib/auth/token";

import { request } from "../request";

export function customAuthExchange(ctx) {
return authExchange({
addAuthToOperation: function addAuthToOperation({ authState, operation }) {
if (!authState?.token) {
return operation;
}
const fetchOptions =
typeof operation.context.fetchOptions === "function"
? operation.context.fetchOptions()
: operation.context.fetchOptions || {};

return makeOperation(operation.kind, operation, {
...operation.context,
fetchOptions: {
...fetchOptions,
headers: {
...fetchOptions.headers,
Authorization: `Bearer ${authState.token}`,
},
},
});
},

didAuthError: ({ error }) => {
// check if the error was an auth error (this can be implemented in various ways, e.g. 401 or a special error code)
return error.graphQLErrors.some(
(e) => e.extensions?.code === "invalid-jwt"
);
},

getAuth: async ({ authState }) => {
// for initial launch, fetch the auth state from storage (local storage, async storage etc)
console.log("getAuth", { authState });
if (!authState) {
const token = getToken() || (await auth(ctx));
if (token) {
return { token: token.jwt_token };
}
return null;
}

/**
* the following code gets executed when an auth error has occurred
* we should refresh the token if possible and return a new auth state
* If refresh fails, we should log out
**/

// if your refresh logic is in graphQL, you must use this mutate function to call it
// if your refresh logic is a separate RESTful endpoint, use fetch or similar
setToken(null);
const result = await auth(ctx);
console.log({ result });
if (result?.jwt_token) {
// return the new tokens
return { token: result.jwt_token };
}

return null;
},

willAuthError: ({ authState }) => {
// e.g. check for expiration, existence of auth etc
if (!authState || isTokenExpired()) return true;
return false;
},
});
}

export function customErrorExchange() {
return errorExchange({
onError: (error) => {
const { graphQLErrors } = error;
// we only get an auth error here when the auth exchange had attempted to refresh auth and getting an auth error again for the second time
const isAuthError = graphQLErrors.some(
(e) => e.extensions?.code === "invalid-jwt"
);
if (isAuthError) {
// clear storage, log the user out etc
// your app logout logic should trigger here
console.log("errorExchange", "logout");
request("/api/logout", {
credentials: "include",
mode: "same-origin",
});
}
},
});
}
2 changes: 1 addition & 1 deletion targets/frontend/src/lib/auth/jwt.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import jwt, { verify } from "jsonwebtoken";

const { HASURA_GRAPHQL_JWT_SECRET, JWT_TOKEN_EXPIRES = 15 } = process.env;
const { HASURA_GRAPHQL_JWT_SECRET, JWT_TOKEN_EXPIRES } = process.env;
const jwtSecret = JSON.parse(HASURA_GRAPHQL_JWT_SECRET);

export function generateJwtToken(user) {
Expand Down
12 changes: 6 additions & 6 deletions targets/frontend/src/lib/auth/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { setRefreshTokenCookie } from "./setRefreshTokenCookie";
let inMemoryToken;

export function getToken() {
return inMemoryToken ? inMemoryToken.jwt_token : null;
return inMemoryToken || null;
}

export function setToken(token) {
inMemoryToken = token;
}

export function isTokenExpired() {
Expand All @@ -16,7 +20,7 @@ export function isTokenExpired() {
}

export async function auth(ctx) {
console.log("[ auth ] ");
console.log("[ auth ] ", { ctxToken: ctx?.token }, inMemoryToken);
if (ctx?.token) {
return ctx.token;
}
Expand Down Expand Up @@ -71,7 +75,3 @@ export async function auth(ctx) {
}
}
}

export function setToken(token) {
inMemoryToken = token;
}
2 changes: 1 addition & 1 deletion targets/frontend/src/pages/api/refresh_token.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { v4 as uuidv4 } from "uuid";
import {
deletePreviousRefreshTokenMutation,
getRefreshTokenQuery,
} from "./refreshToken.gql";
} from "./refresh_token.gql";

export default async function refreshToken(req, res) {
const apiError = createErrorFor(res);
Expand Down
10 changes: 9 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3447,7 +3447,7 @@
"@typescript-eslint/types" "4.4.0"
eslint-visitor-keys "^2.0.0"

"@urql/core@^1.15.1":
"@urql/core@>=1.14.0", "@urql/core@^1.15.1":
version "1.15.1"
resolved "https://registry.yarnpkg.com/@urql/core/-/core-1.15.1.tgz#fa49909f2841d092796dd540cef6e9df89222560"
integrity sha512-a05ablx/aKNCUc9dEbx0GvE28UC0sJ1FmfsSfmJNqecYlYeb4XvSQW4FLVy0e/MjQeB9op/weiVIEw+za2ssGw==
Expand All @@ -3462,6 +3462,14 @@
dependencies:
wonka ">= 4.0.9"

"@urql/exchange-auth@^0.1.2":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@urql/exchange-auth/-/exchange-auth-0.1.2.tgz#67a76ef78ab4ea9dc51c050d1b79362e9abbffc6"
integrity sha512-SIrcnbom+nro0pALmqQmcMxUcy8qZUoykoPs5LezYVPmvZXx4ErXwly58g0XoU4eX10U2gakjCFPTyleF8Nt6Q==
dependencies:
"@urql/core" ">=1.14.0"
wonka "^4.0.14"

"@vercel/ncc@0.24.1", "@vercel/ncc@^0.24.1":
version "0.24.1"
resolved "https://registry.yarnpkg.com/@vercel/ncc/-/ncc-0.24.1.tgz#3ea2932c85ba87f4de6fe550d60e1bf5c005985e"
Expand Down

0 comments on commit 1d98dfc

Please sign in to comment.