apollo client , adding subscriptions while not breaking http link that uses jwt - vue.js

I currently have a graphql api that handles HTTP requests, I've migrated to apollo-client and now I want to add subscriptions. The problem is, that I can't figure out how to combine my HTTP link (that has a JWT auth middleware).
I have no code errors to share, BUT, the issue is in the way i use the link split method. why? because when I remove it and just use authLink.concat(httpLink) everything works smoothly (except for that in this case I don't check if the connection is WS or HTTP...).
Here's my code:
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
// import ApolloClient from "apollo-boost"; // migrated to apollo-client
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';
// subscriptions imports
import { split } from 'apollo-link'
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import VueApollo from "vue-apollo";
// links composition
const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql',
});
const wsLink = new WebSocketLink({
uri: `ws://localhost:5000`,
options: {
reconnect: true
}
});
// this is the sketchy section
const link = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query)
return kind === 'OperationDefinition' && operation === 'subscription'
},
wsLink,
httpLink
)
// JWT middleware
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('token');
return {
headers: {
...headers,
authorization: token ? token : ''
}
}
});
const wsPlusHttpWithJWT = authLink.concat(link)
export const apolloClient = new ApolloClient({
// link: authLink.concat(httpLink), // this worked
link: wsPlusHttpWithJWT,
cache: new InMemoryCache()
});
Vue.use(VueApollo);
const apolloProvider = new VueApollo({ defaultClient: apolloClient });
Vue.config.productionTip = false;
new Vue({
apolloProvider,
router,
render: h => h(App),
}).$mount("#app");

Related

How to set authentications headers with Vue apollo and composition api?

I've build my app with Vite. I read many documents on web about the topic but I'm still very confused. I've a login form that send credentials to a protected view. When post the data I set the headers and store the Bearer token in the local storage.
The problem is that it doesn't work cause the Bearer token result equal to null.
Only when I logout the token is set in the headers.
That's how is the header when I log in
And here how it's set when I log out...
My main.js code is this:
import { createApp, provide, h } from "vue";
import {
ApolloClient,
createHttpLink,
InMemoryCache,
} from "#apollo/client/core";
import { DefaultApolloClient } from "#vue/apollo-composable";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";
import { provideApolloClient } from "#vue/apollo-composable";
const authToken = localStorage.getItem("auth-token");
const httpLink = createHttpLink({
uri: "http://localhost/graphql",
headers: {
Authorization: "Bearer " + authToken,
},
});
const cache = new InMemoryCache();
const apolloClient = new ApolloClient({
link: httpLink,
cache,
});
provideApolloClient(apolloClient);
const app = createApp({
setup() {
provide(DefaultApolloClient, apolloClient);
},
render: () => h(App),
});
app
.use(router)
.use(createPinia())
.mount("#app");
and this is my routes.js
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
const isAuthenticated = localStorage.getItem('auth-token');
if(requiresAuth && isAuthenticated===null){
next('/auth/login');
}else {
next();
}
});
I'm surely making some mistakes in my main.js but I cannot understand what's wrong. I'm very confused :-/
Thanks to who'll be able to help me.
Try using a helper function to get the token from local storage; I'm using this method and it's working fine for me. To get your code more organized, create a separate folder to define the apollo client. Here is the code:
// apolloClient.ts
import { ApolloClient, InMemoryCache, HttpLink } from "#apollo/client/core";
function getHeaders() {
const headers: { Authorization?: string; "Content-Type"?: string } = {};
const token = localStorage.getItem("access-token");
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
headers["Content-Type"] = "application/json";
return headers;
}
// Create an http link:
const httpLink = new HttpLink({
uri: `${import.meta.env.VITE_API_URL}/graphql`,
fetch: (uri: RequestInfo, options: RequestInit) => {
options.headers = getHeaders();
return fetch(uri, options);
},
});
// Create the apollo client
export const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: httpLink,
defaultOptions: {
query: {
errorPolicy: "all",
},
mutate: {
errorPolicy: "all",
},
},
});
Then you can use it in your main.ts like this:
// main.ts
import { createApp, h } from "vue";
import { provideApolloClient } from "#vue/apollo-composable";
import App from "./App.vue";
import { apolloClient } from "./apolloClient";
const app = createApp({
setup() {
provideApolloClient(apolloClient);
},
render: () => h(App),
});
app.mount("#app");

Data appears in console but does not render when I use a V-for Loop( Vuex and GraphQl)

I currently have my project set up in GraphQL and Vuex in my vue JS project, I was able to call my data from my Django backend but, I get errors when I try to render the data using a V-for Loop in Vue Js. The element just disappears.
Here is my GraphQL setup.
// Apollo client Setup
import Vue from 'vue'
import { ApolloClient } from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloLink } from 'apollo-link'
import VueApollo from 'vue-apollo'
// import { USER_ID, AUTH_TOKEN } from './constants/settings'
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
const token = localStorage.getItem(AUTH_TOKEN)
operation.setContext({
headers: {
authorization: token ? `JWT ${token}` : null
}
})
return forward(operation)
})
// HTTP connection to the API
const httpLink = createHttpLink({
// You should use an absolute URL here
uri: 'http://127.0.0.1:8000/graphql',
})
// Cache implementation
const cache = new InMemoryCache()
// Create the apollo client
export const apolloClient = new ApolloClient({
link: httpLink,
cache,
connectToDevTools: true,
})
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
})
Vue.use(VueApollo)
export default apolloProvider
Here is the setup in my Vuex store
import { apolloClient } from '../../apollo'
import {PORTFOLIO_LIST} from '../../graph/queries.js'
const state = {
portfolios:[],
portfolio:'',
};
const getters = {
allPortfolios:(state) => state.portfolios,
};
const mutations = {
SET_PORTFOLIOS(state, portfolios){
state.portfolios = portfolios
},
SET_PORTFOLIO(state, portfolio){
state.portfolio = portfolio
}
};
const actions = {
// The Portfolio list API
async portfolioList({commit}){
try{
const response = await apolloClient.query({query: PORTFOLIO_LIST})
const portfolios = JSON.stringify(response.data.portfolios)
commit('SET_PORTFOLIOS', portfolios)
console.log(portfolios)
}
catch(e){
console.log(e)
}
},
// The Portfolio Detail API
async portfolioDetail({commit}, ){
try{
const response = await apolloClient.query()
const portfolio = JSON.stringify(response.data)
console.log(portfolio)
commit ('SET_PORTFOLIO', portfolio)
}
catch(e){
console.log(e)
}
},
//The Portfolio Create API
// async portfolioCreate({commit}, ...portfolioDetail){
// },
// The Portfolio Delete API
// async portfolioDelete({commit}){
// },
};
export default {
state,
getters,
mutations,
actions,
}
And finally here is where I call my Vue Js for loop
<template>
<div class="px-6 lg:px-32 h-screen">
<section v-for ="portfolio in portfolios" :key="portfolio.id" class="mt-24 grid lg:grid-cols-3" >
<!-- The card section -->
<div>
{{portfolio.about}}
</div>
<!-- End of the card section -->
</section>
</div>
</template>
<script>
import { mapState} from 'vuex';
export default {
computed:{
...mapState(['portfolios']),
},
created(){
this.$store.dispatch('portfolioList');
}
}
</script>
I get a response in Vue JS dev tools, but it does not iterate in my Vue js code
When I open my console, The section where I iterated over, just disappears

How can i assign token in async storage to headers in app.js after login. in react-native

I am getting access token after login and I want to assign that token to authorization in headers in my App.js
I am using async storage to store the access token in local storage and I want to assign to headers in my App.js file How can I do it. I want to assign token to authorization in headers so that I can make graphql API calls
MY App.js file
import React, {Component} from 'react';
import {AppRegistry,SafeAreaView, StyleSheet} from 'react-native';
import Navigator from "#Navigation"
import AsyncStorage from '#react-native-community/async-storage';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-boost';
const httpLink = createHttpLink({
uri: 'https://graphql.sample.com/graphql',
});
const token = AsyncStorage.getItem('#accessToken')
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: token ,
}
}
});
const link = authLink.concat(httpLink)
const cache = new InMemoryCache();
const defaultOptions = {
query: {
fetchPolicy: "network-only",
errorPolicy: "all"
}
};
const client = new ApolloClient({
link,
cache,
defaultOptions
});
class App extends Component {
render() {
return (
<ApolloProvider client={client}>
<Navigator/>
</ApolloProvider>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
});
export default App;
Changing the authLink function to work with async/await worked for me! Hope this helps;
const authLink = setContext(async (_, { headers }) => {
// get the authentication token from async storage if it exists
const accessToken = await AsyncStorage.getItem('#accessToken');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: accessToken ? `Bearer ${accessToken}` : '',
},
};
});
You should await AsyncStorage.getItem('#accessToken') to get your accessToken before passing it to authorization: token. This is a post relating to Waiting for AsyncStorage.getItem() in React Native.Try this:
...
const token = await AsyncStorage.getItem('#accessToken')
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: token ,
}
}
});
...

How to access '$apollo' outside vue component?

How to make apollo accessible outside vue component.
I am verifying if the user exist and then allow the route to proceed further.
{
path: '/:username',
name: 'userProfilePage',
component: userProfilePage,
beforeEnter(routeTo, routeFrom, next) {
userExist(routeTo.params.username)
next()
}
Passing the username as a parameter to the userExist function.
import gql from "graphql-tag"
export default function userExist(username) {
this.$apollo
.query({
query: gql`
query($username: String!) {
login(username: $username) {
username
email
}
}
`,
variables: {
username: username
}
})
.then(res => {
console.log(res);
return res
})
.catch(err => {
console.log(err);
return err
});
}
But it is outputting the error:
Apollo client code
import Vue from 'vue'
import App from './App.vue'
import VueApollo from 'vue-apollo';
import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import router from './routes.js'
Vue.config.productionTip = false
const httpLink = new HttpLink({
uri: process.env.VUE_APP_DB_URL,
})
const cache = new InMemoryCache()
const apolloClient = new ApolloClient({
link: httpLink,
cache
})
Vue.use(VueApollo)
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
})
new Vue({
render: h => h(App),
router,
apolloProvider
}).$mount('#app')
So instead of initializing the apollo client in the App.vue file, initialize it in another file.
Something like clients.js, and export that client:
const httpLink = new HttpLink({
uri: process.env.VUE_APP_DB_URL,
})
const cache = new InMemoryCache()
export const apolloClient = new ApolloClient({
link: httpLink,
cache
})
Once done, import that in App.vue file like this:
import { apolloClient } from './clients.js';
Vue.use(VueApollo)
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
})
new Vue({
render: h => h(App),
router,
apolloProvider
}).$mount('#app')
Once done, import that client in any other file you want:
import { apolloClient } from './client.js';
import gql from "graphql-tag"
export default function userExist(username) {
apolloClient
.query({
query: gql`
query($username: String!) {
login(username: $username) {
username
email
}
}
`,
variables: {
username: username
}
})
.then(res => {
console.log(res);
return res
})
.catch(err => {
console.log(err);
return err
});
}

Add JWT to all GraphQL/AppSynce requests when logged in with AWS Amplify

I have an AppSync app that uses IAM auth (connect to Cognito User and Identity pools). When using IAM auth, the $event.context.identity is a Cognito Identity Pool object that doesn't have the user's info (no username, sub, email, etc.)
I believe that I need to pass the UserPoolID JWT (which is available in the client side via Amplify) to AppSync whenever I make a graphQL request. But I haven't been able to figure out how to add JWT to (presumably) the header.
-------------EDIT--------------
AppSyncClient is the client (built on apollo). The App.js looks like
import React from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import appSyncConfig from "./AppSync";
import { ApolloProvider } from "react-apollo";
import AWSAppSyncClient from "aws-appsync";
import { Rehydrated } from "aws-appsync-react";
import { Auth } from 'aws-amplify'
import AWS from'aws-sdk';
import AllPosts from './Components/AllPosts';
// more routes
const Home = () => (
<div > <AllPosts /> </div>
);
const App = () => (
<div> <Router> <div>
<Route exact={true} path="/" component={Home} />
//more routes
</div> </Router> </div>
);
const client = new AWSAppSyncClient({
url: appSyncConfig.graphqlEndpoint,
region: appSyncConfig.region,
auth: {
type: appSyncConfig.authenticationType, //AWS_IAM
apiKey: appSyncConfig.apiKey,
credentials: () => Auth.currentCredentials(),
});
const WithProvider = () => (
<ApolloProvider client={client}>
<Rehydrated>
<App />
</Rehydrated>
</ApolloProvider>
);
export default WithProvider;
Assuming your GraphQL client is Apollo, the key is to use setContext as your authLink from the apollo-link-context library.
Example:
import ApolloClient from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { setContext } from 'apollo-link-context';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { withClientState } from 'apollo-link-state';
import { clientState } from './clientState';
import { Auth } from 'aws-amplify';
const cache = new InMemoryCache();
//TODO: need to cache token
const authLink = setContext((request) => new Promise( (resolve, reject) => {
Auth.currentSession()
.then(session => {
const token = session.idToken.jwtToken;
resolve({
headers: { Authorization: token }
});
})
}));
const stateLink = withClientState({ ...clientState, cache });
const client = new ApolloClient({
cache,
link: ApolloLink.from([
authLink,
stateLink, //near end but before HttpLink
new HttpLink({uri: process.env.REACT_APP_GRAPHQL_ENDPOINT })
])
});
export default client;
(Code from: https://github.com/aws/aws-amplify/issues/434#issuecomment-372349010)