Vue Apollo auto refresh token implementation - vue.js

I am trying to implement JWT auth to my SPA, login and logout are done, but i can`t figure out the proper (or just working) way to implement refresh token, any help will be appreciated!
I couldn`t find any tutorial about handling refresh token via Vue Appollo.
There is "Vue CLI Apollo plugin" with
createApolloClient({
...
// Custom pre-auth links
// Useful if you want, for example, to set a custom middleware for refreshing an access token.
preAuthLinks: [],
...})
But there are no example about implementing it, and looks like "Vue CLI Apollo plugin" is deprecated, it gets bo updates for a long time and doesn`t work with VUE CLI 4.
Current frontend stack is:
Vue-cli 4.5,
Apollo/client 3.5.8
Vue-apollo-option 4.0.0
Graphql 16.3.0
Vuex 4.0
My vue-apollo.js config:
import { createApolloProvider } from "#vue/apollo-option";
import { AUTH_TOKEN } from "#/constants/settings";
import {
ApolloClient,
createHttpLink,
InMemoryCache,
ApolloLink,
} from "#apollo/client/core";
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
const token = localStorage.getItem(AUTH_TOKEN);
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : null,
},
});
return forward(operation);
});
const httpLink = createHttpLink({ uri: "http://127.0.0.1:8000/graphql" });
const cache = new InMemoryCache();
export const apolloClient = new ApolloClient({
link: authMiddleware.concat(httpLink),
cache,
});
export function createProvider() {
const apolloProvider = createApolloProvider({
defaultClient: apolloClient,
defaultOptions: {
$query: {
fetchPolicy: "cache-and-network",
},
},
errorHandler(error) {
console.log("%Appollo error!", error.message);
},
});
return apolloProvider;
}
So, right now a can login, store token, paste token with every request with help of authMiddleware, but where and how to implement refreshing of token? Thanks in advance for any help!

Related

How to consume Next.JS Rest Endpoints secured with Amplify from a React Native app

Background:
My current stack is a Next server to use as an admin portal and REST API for a Mobile App running with Expo - React Native. The Next Server is currently hosted as a Lambda#Edge.
I have secured both the Next server and the React Native app with AWS Amplify's withAuthenticator wrapper. (I also tried specific auth packages like Next-Auth and Expo's auth package)
Problem:
However, I can't figure out how to add the Auth info (Access_token) to my REST Requests from Mobile app -> Next Server
I tried adding the tokens as bearer headers to the API without luck after that I was fairly sure it all has to be set up and sent via cookies.
BUT I am stuck on how to actually implement these cookies properly. I was hoping the endpoints:[] config could be used to set up my own domain to post to and handle the cookies. Reading the request on the server showed that it contained no Auth info when posted with this method.
Likewise using RTK Query (Preferably I add all the Auth to this instead of Amplify's API setup) I don't have the correct info to make an Authorized api request
Here are some snippets of the working page Authentication for both apps
API Endpoint /api/version:
import type { NextApiRequest, NextApiResponse } from 'next'
import { withSSRContext } from 'aws-amplify'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data | Error>,
) {
const { Auth } = withSSRContext({req})
try {
const user = await Auth.currentAuthenticatedUser()
return res.status(200).json({
version: '1.0.0',
user: user.username,
})
} catch (err) {
console.log(err)
return res.status(200).json({
message: 'Unauthenticated',
})
}
}
Mobile App Config:
import {
useAuthenticator,
withAuthenticator,
} from '#aws-amplify/ui-react-native'
import { Amplify, Auth } from 'aws-amplify'
import awsconfig from './aws-exports'
Amplify.configure({
...awsconfig,
API: {
endpoints: [
{
name: 'MyApi',
endpoint: 'http://NextIP:NextPort/api/',
},
],
},
})
Auth.configure(awsconfig)
export default withAuthenticator(App)
Mobile Screen:
import { API } from 'aws-amplify'
function getData() {
const apiName = 'MyApi'
const path = '/version'
const myInit = {
headers: {}, // OPTIONAL
}
return API.get(apiName, path, myInit)
}
export default function ModalScreen() {
// Get token / Cookie for auth
// const { data, isLoading, error } = useGetApiVersionQuery(null) // RTK Query
getData() // Amplify
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error.response)
})
return ( <></>)}
I found a solution, however, could not get the Next-Auth middleware to fire when the token was sent using the Bearer token in headers. Which is my ideal way of handling the routes.
I wrapped the getToken({req}) call so that if there is no JWT Web token it would try encode the token separately
Lastly ChatGpt somehow got me onto the package aws-jwt-verify which has everything you need to verify a token generated by aws-amplify/auth, in my case from react-native.
components/utils/auth.utils.ts
import { NextApiRequest } from 'next'
import { CognitoJwtVerifier } from 'aws-jwt-verify'
import { getToken } from 'next-auth/jwt'
// Verifier that expects valid token:
const verifier = CognitoJwtVerifier.create({
userPoolId: process.env.COGNITO_USERPOOL_ID ?? '',
tokenUse: 'id',
clientId: process.env.COGNITO_CLIENT_ID ?? '',
issuer: process.env.COGNITO_ISSUER ?? '',
})
export async function getMobileToken(req: NextApiRequest) {
let token = null
try {
token = await getToken({ req })
} catch (error) {
console.log('Could not get JWT Web Token')
}
try {
if (!token)
token = await getToken({
req,
async decode({ token }) {
if (!token) return null
const decoded = await verifier.verify(token)
return decoded
},
})
} catch (error) {
return null
}
console.log('Mobile Token:', token)
return token
}

Next.js: React Apollo Client Not sending cookies?

I'm using Apollo Client as a graphql client on my next.js application, Here is the function that creates a client for me:
let client: ApolloClient<any>;
export const __ssrMode__: boolean = typeof window === "undefined";
export const uri: string = "http://localhost:3001/graphql";
const createApolloClient = (): ApolloClient<any> => {
return new ApolloClient({
credentials: "include",
ssrMode: __ssrMode__,
link: createHttpLink({
uri,
credentials: "include",
}),
cache: new InMemoryCache(),
});
};
Surprisingly, when I make a mutation to the graphql server I'm able to set the cookies but, I'm not able to get the cookies from the client. What may be possibily the problem?
I came to the same problem, my solution was to create a client every time a server-side rendering is made, maybe it's not ideal to have a client to execute GraphQL calls in the browser and others in the server but it's what worked best for me. This is the code:
import { ApolloClient, createHttpLink, InMemoryCache } from '#apollo/client';
import { NextPageContext } from 'next';
import { setContext } from '#apollo/client/link/context';
export const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql',
credentials: 'include',
});
const CreateClient = (ctx: NextPageContext | null) => {
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
cookie:
(typeof window === 'undefined'
? ctx?.req?.headers.cookie || undefined
: undefined) || '',
},
};
});
return new ApolloClient({
credentials: 'include',
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
ssrMode: true,
});
};
export default CreateClient;
So, what I do is pass the context from the getServerSideProps and see if I have some cookies there, if so I just set the cookies, you can also send the authorization token if it's in the cookie. To call it is very simple:
export async function getServerSideProps(context: NextPageContext) {
const client = CreateClient(context);
const { data } = await client.query({
query: SOME_QUERY,
});
return {
props: {
data,
},
};
}
You can also do a HOC as in the Ben Awad tutorial Apollo Client HOC but I think it was too much for what I was trying to do. Hope it helped you or helps someone there :)
Also, I'm using Next 12.1.5 and React 18

How do I pull data out of response or cache for apollo client 3

I am trying to pull a jwt out of a loginUser mutation and store it in a variable then use the apollo-link to setContext of the header via "Authorization: Bearer ${token} for authentification as all my other mutations and queries require the token. I have been slamming the docs for days on Apollo Client(React) -v 3.3.20. I have been through all the docs and they show all these examples of client.readQuery & writeQuery which frankly seem to just refetch data? I don't understand how you actually pull the data out of the response and store it in a variable.
The response is being stored in the cache and I have no idea how to take that data and store it in a token variable as I stated above. Which remote queries I can just access the returned data via the data object from the useQuery hook, however on the useMutation hook data returns undefined. The only thing I could find on this on stack overflow was the my data type may be custom or non-traditional type but not sure if that is the problem.
[Cache in apollo dev tools][1]
[Mutation in apollo dev tools][2]
[Response in network tab][3]
Here is my ApolloClient config:
const httpLink = createHttpLink({ uri: 'http://localhost:4000/',
// credentials: 'same-origin'
});
const authMiddleware = new ApolloLink((operation, forward) => {
const token = localStorage.getItem('token');
// add the authorization to the headers
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
authorization: `Bearer ${token}` || null,
}
}));
return forward(operation);
})
const client = new ApolloClient({
cache: new InMemoryCache(),
link: concat(authMiddleware, httpLink),
});
The header works obviously I just can't grab the token to pass so the header just sends Authorization: Bearer.
For the login I have this:
const LOGIN_USER = gql`
mutation($data:LoginUserInput!) {
loginUser(
data: $data
) {
user {
id
name
}
token
}
}
`;
const [loginUser, { data, loading, error }] = useMutation(LOGIN_USER);
if (loading) return 'Submitting...';
if (error) return `Submission error! ${error.message}`;
Originally I was just calling
onClick={loginUser( { variables })}
For the login but onComplete never works and everywhere I look I see lots of posts about it with no solutions. So I tried slamming everything into a function that I then called with loginUser inside it:
const submit = async () => {
loginUser({ variables})
// const { user } = await client.readQuery({
// query: ACCESS_TOKEN,
// })
// console.log(`User : ${JSON.stringify(user)}`)
const token = 'token';
const userId = 'userId';
// console.log(user);
// localStorage.setItem(token, 'helpme');
// console.log({data});
}
At this point I was just spending hours upon hours just trying mindless stuff to potentially get some clue on where to go.
But seriously, what does that { data } in useMutation even do if it's undefined. Works perfectly fine for me to call data.foo from useQuery but useMutation it is undefined.
Any help is greatly appreciated.
[1]: https://i.stack.imgur.com/bGcYj.png
[2]: https://i.stack.imgur.com/DlzJ1.png
[3]: https://i.stack.imgur.com/D0hb3.png

Why there is no data after the first login?

Dear vue and apollo users;
I am dealing with the first time install problem.
When I first launch the app, I don't get results.
I am using ApolloClient, InMemoryCache, HttpLink from "apollo-boost"
I store my userID and JWT in ApplicationSettings(local storage)
How to set token dynamically?
Vue.use(VueApollo);
const httpLink = new HttpLink({
uri: "https://sebapi.com/graphql"
});
const authLink = setContext((_, { headers }) => {
// get the authentication token from ApplicationSettings if it exists
var tokenInAppSettings = ApplicationSettings.getString("token");
// return the headers to the context so HTTP link can read them
return {
headers: {
...headers,
authorization: tokenInAppSettings
? `Bearer ${tokenInAppSettings}`
: null
}
};
});
export const apolloClient = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
const apolloProvider = new VueApollo({
defaultClient: apolloClient
});
I have created a GitHub repo reproducing problem
and a youtube video of the problem
There is no error during login but after navigating to the list page for the first time I got following errors...
JS: [Vue warn]: Invalid prop: type check failed for prop "items". Expected Array, Object, got Undefined
JS: Error sending the query 'birds' ServerError: Response not successful: Received status code 400
IT SEEMS APOLLO DOES NOT HAVE userID during first query.
NOTE: You can easily clear user data by using yarn cl script
# debug app without HMR
yarn devn
# clear user data of app
yarn cl
Solution repo using vuex:
https://github.com/kaanguru/data-firstlogin/tree/user-in-vuex
Move userID into vue instance
+welcome.vue+
//const userId = ApplicationSettings.getNumber("userID");
// I have moved userID into vue.
export default {
data() {
return {
birds:[],
bird:{
id: null,
isim: "",
bilezik: ""
},
userId: ApplicationSettings.getNumber("userID")
};
},
apollo: {
birds: {
query: gql`
query myBirds($userId: ID!) {
birds(where: { user: $userId }) {
id
isim
bilezik
}
}
`,
variables() {
return {
userId: this.userId,
};
},
},
},
};

How to pass Additional Header when calling mutation in React native apollo client?

How to pass Additional Header when calling mutation in React native apollo client ?
my Client is here:
import { HttpLink } from 'apollo-link-http';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
const makeApolloClient = (token) => {
// create an apollo link instance, a network interface for apollo client
const link = new HttpLink({
uri: 'http://x.x.x.x:xxxx/xxxx',
headers: {
Authorization: `Bearer ${token}`
},
});
// create an inmemory cache instance for caching graphql data
const cache = new InMemoryCache();
// instantiate apollo client with apollo link instance and cache instance
const client = new ApolloClient({
link,
cache
});
return client;
};
export default makeApolloClient;
If i need to add additional header to this same client when using query or mutation how can i do it ?
Is it possible with "apollo-link-context" ?
You haven't specified your React version however assuming you use Hooks you do it as follows. If you aren’t using hooks change the doc version for the links at the bottom of this answer using the drop down in the top left.
Where you have your query:
const GET_USER = gql`
query getUser{
node {
name
age
}
}
`;
You’ll want to run a query with the useQuery hook:
const { loading, error, data } = useQuery(GET_USER, {
context: {
headers: {
"Content-Type": "application/json"
}
}
})
Docs:
You can find the docs for each here:
- UseQuery: https://www.apollographql.com/docs/react/essentials/queries/
- Context Headers: https://www.apollographql.com/docs/link/links/http/#passing-context-per-query
This can be done by receiving the context which is set in mutation/query.
Setting Custom header in mutation
const [addTodo] = useMutation(ADD_TODO, {
refetchQueries: [{ query: GET_TODO }], //updating the list of todos list after adding
context: {
headers: {
"x-custom-component-add": "kkk-add",
"x-origin-server": "pure-react"
}
}
});
receiving context in middle ware which set in mutation/query
const httpLink = new HttpLink({ uri: "https://sxewr.sse.codesandbox.io/" });
const authMiddleware = new ApolloLink((operation, forward) => {
const customHeaders = operation.getContext().hasOwnProperty("headers") ? operation.getContext().headers : {};
console.log(customHeaders);
operation.setContext({
headers: {
...customHeaders
//we can also set the authorization header
// authorization: localStorage.getItem('jjjjjj'),
}
});
return forward(operation);
});
Finally passing the middleware in Apoolo Client
const client = new ApolloClient({
cache: new InMemoryCache(),
link: from([authMiddleware, httpLink])
});
Here is the working sample.
https://codesandbox.io/s/passing-custom-header-in-graphql-mutation-query-l332g?file=/src/index.js
Custom header look like this