Accessing Mutation Result in Angular Apollo Graphql - graphql

I am new to Graphql and I am using the Apollo client with Angular 7.
I have a mutation in the server that I am using for authentication.This mutation generates returns an access token and a refresh token:
#Injectable({
providedIn: "root"
})
export class LoginTokenAuthGQL extends Apollo.Mutation<
LoginTokenAuth.Mutation,
LoginTokenAuth.Variables
> {
document: any = gql`
mutation loginTokenAuth($input: LoginAuthInput!) {
loginTokenAuth(input: $input) {
accessToken
refreshToken
}
}
`;
}
I am running this mutation in my sign-in component like this:
onLoginSubmit() {
const email = this.loginForm.controls['userEmail'].value;
const password = this.loginForm.controls['userPassword'].value;
console.log('Sending mutation with', email, password);
this.loginGQL.mutate({
input: {
email,
password,
userType: AuthUserType.Crm
}
}).pipe(
map((response) => response.data )
).subscribe(
(output: LoginTokenAuth.Mutation) => {
console.log('Access token', output.loginTokenAuth.accessToken);
console.log('Refresh token', output.loginTokenAuth.refreshToken);
console.log(this.apollo.getClient().cache);
},
((error: any) => {
console.error(error);
})
);
}
Once I get the access token I will need to add it as header on my requests.
From what I read from the Apollo Client all results from queries and mutations are cached locally in the client. But it is not clear to me how can I access them and add it to the apollo-link.
To be more clear I would like to do this in my Graphql module:
const http = httpLink.create({uri: '/graphql'});
const auth = setContext((_, { headers }) => {
// get the authentication token from the cache
const token = ???
if (!token) {
return {};
} else {
return {
headers: headers.append('Authorization', `Bearer ${token}`)
};
}
});

Even official docs of Apollo Client suggest you store this token as usually - to localStorage.
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';
const httpLink = createHttpLink({
uri: '/graphql',
});
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});

Related

Authentication in graphql

I'm trying to add authentication in graphql, so only authenticated users can make a request to my server. But when I try to send the id in the Authorization header it doesn't send the information I want. It's a next project with ssr.
import { ApolloClient, InMemoryCache, createHttpLink } from '#apollo/client';
import { setContext } from '#apollo/client/link/context';
import { getAuth } from 'firebase/auth';
const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql',
});
const authLink = setContext((_, { headers }) => {
const user = getAuth().currentUser;
return {
headers: {
...headers,
authorization: user?.uid ?? "null"
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
export default client;
I have the id of the user in a redux context, so I tried to use store.getState(), but it didn't work. Also I've tried with firebase getAuth, so I send the uid.

Laravel / SvelteKit sending serverside request with Cookie header

I am making authentication with SvelteKit and Laravel. This is the flow i currently have:
User logs in with correct credentials.
User login route has no middleware enabled on the Laravel side.
This login request returns a JWT token, which gets send back to the Sveltekit server.
I set this token as a cookie using this code:
const headers = {
'Set-Cookie': cookie.serialize(variables.authCookieName, body.token, {
path: '/',
httpOnly: true,
sameSite: 'lax'
})
}
return {
headers,
body: {
user
}
}
The cookie is correctly set after that, verified.
So the authentication is handled correctly. But now i want to send that cookie with Axios to the Laravel server and authenticate the user but that doesn't work. The Laravel server never receives the cookie. The Axios withCredentials setting also never sends that cookie to the Laravel server. How can i make it work so that the cookie header is sent with Axios to Laravel? I have 0 CORS errors in my browser so i don't think that is the issue.
My API Class in SvelteKit:
import axios from 'axios'
import { variables } from '$lib/variables'
const headers: Record<string, string | number | boolean> = {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
class Api {
constructor() {
axios.defaults.baseURL = variables.apiUrl
axios.defaults.withCredentials = true
axios.interceptors.response.use(
response => response.data,
error => Promise.reject(error.response.data)
)
}
get(url: string) {
return axios.get(url, { headers })
}
post(url: string, data?: unknown) {
return axios.post(url, data, { headers })
}
patch(url: string, data: Record<string, unknown>) {
return axios.patch(url, data, { headers })
}
}
const api = new Api()
export default api
My Userservice:
import api from '$core/api'
const resource = '/users'
const userService = () => {
const getAll = async () => {
return await api.get(resource)
}
return {
getAll
}
}
export default userService
The Index endpoint (routes/dashboard/index.ts)
import services from '$core/services'
export async function get() {
return await services.user.getAll()
.then(({ data }) => {
return {
body: { users: data.users }
}
}).catch((err) => {
return {
body: { error: err.message }
}
})
}
My Hooks.index.ts (maybe for reference)
import * as cookie from 'cookie'
import jwt_decode from 'jwt-decode'
import type { GetSession, Handle } from '#sveltejs/kit'
import type { User } from '$interfaces/User'
// This is server side
/** #type {import('#sveltejs/kit').Handle} */
export const handle: Handle = async ({ event, resolve }) => {
const { jwt } = cookie.parse(event.request.headers.get('cookie') || '')
if (jwt) {
const { user } = jwt_decode<{ user: User }>(jwt)
if (user) {
event.locals.user = user
}
}
return resolve(event)
}
export const getSession: GetSession = async (request) => {
return {
user: request.locals.user
}
}
Can someone help or explain why Axios has no idea if the cookie is set or not, or how i can send the Cookie with the request to the Laravel Server?

Is there a way to get a hash out of an Apollo request

After trying for hours to try implementing a Hmac token on a react Apollo client request, I just couldn't find a way to retrieve the final request body that is being sent.
Is there a way to get the request body sent to the server before the client sends it or no way at all ? Not being able to get body hash causes for some security concerns for Mitm attacks since using a jwt the way Apollo docs show it doesn't provide with a way of signing the content.
Based on this, I came up with something
import React from "react";
import { render } from "react-dom";
import {
ApolloClient,
ApolloProvider,
HttpLink,
ApolloLink,
InMemoryCache,
concat,
useQuery,
gql,
} from "#apollo/client";
import CryptoJS from "crypto-js";
import { print } from "graphql/language/printer";
const httpLink = new HttpLink({ uri: "http://localhost:3000/dev/query" });
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
const { query, variables, operationName } = operation,
payload = {
variables,
operationName,
query: print(query),
};
console.log(payload);
// custom headers
const token = getAuthHeader(payload, "key"), // create HMAC here
headers = {
hmac: token,
};
operation.setContext({
headers: {
...headers,
},
});
return forward(operation);
});
const client = new ApolloClient({
cache: new InMemoryCache({
addTypename: false, //quite important
}),
link: concat(authMiddleware, httpLink),
});
function getAuthHeader(payload_string, sk) {
var str = JSON.stringify(payload_string);
console.log(str);
var hmac = CryptoJS.HmacSHA256(str, sk).toString(CryptoJS.enc.Hex);
return hmac;
}
function MakeRequest() {
const request = `
query {
items {
_id
}
}
`;
const { loading, error, data } = useQuery(gql(request));
//Whatever you wanna do with it.
}
function App() {
return (
<div>
<MakeRequest />
</div>
);
}
render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById("root")
);
Seems to work correctly.

Apollo client QUERIES not sending headers to server but mutations are fine

I hooked up a front end to a graphql server. Most if not all the mutations are protected while all the queries are not protected. I have an auth system in place where if you log in, you get an access/refresh token which all mutations are required to use. And they do which is great, backend receives the headers and everything!
HOWEVER. There is one query that needs at least the access token to distinguish the current user! BUT the backend does not receive the two headers! I thought that the middlewareLink I created would be for all queries/mutations but I'm wrong and couldn't find any additional resources to help me out.
So here's my setup
apollo-client.js
import { InMemoryCache } from "apollo-cache-inmemory"
import { persistCache } from "apollo-cache-persist"
import { ApolloLink } from "apollo-link"
import { HttpLink } from "apollo-link-http"
import { onError } from "apollo-link-error"
import { setContext } from "apollo-link-context"
if (process.browser) {
try {
persistCache({
cache,
storage: window.localStorage
})
} catch (error) {
console.error("Error restoring Apollo cache", error)
}
}
const httpLink = new HttpLink({
uri: process.env.GRAPHQL_URL || "http://localhost:4000/graphql"
})
const authMiddlewareLink = setContext(() => ({
headers: {
authorization: localStorage.getItem("apollo-token") || null,
"x-refresh-token": localStorage.getItem("refresh-token") || null
}
}))
const afterwareLink = new ApolloLink((operation, forward) =>
forward(operation).map(response => {
const context = operation.getContext()
const {
response: { headers }
} = context
if (headers) {
const token = headers.get("authorization")
const refreshToken = headers.get("x-refresh-token")
if (token) {
localStorage.setItem("apollo-token", token)
}
if (refreshToken) {
localStorage.setItem("refresh-token", refreshToken)
}
}
return response
})
)
const errorLink = onError(({ graphQLErrors, networkError }) => {
...
// really long error link code
...
})
let links = [errorLink, afterwareLink, httpLink]
if (process.browser) {
links = [errorLink, afterwareLink, authMiddlewareLink, httpLink]
}
const link = ApolloLink.from(links)
export default function() {
return {
cache,
defaultHttpLink: false,
link
}
}
Is there a way to target ALL mutations/queries with custom headers not just mutations? Or apply some headers to an individual query since I could probably put that as an app middleware?
edit: Haven't solved the SSR portion of this yet.. will re-edit with the answer once I have.

apolloClient.query not using middleware, while <Query /> does

I have an apolloclient with middleware that console.logs a bearer token, because I am not always authenticated when I should be.
For some reason, it appears that queries from the react-apollo <Query /> object use this middleware -- I see my console message -- but queries that I trigger programmatically with: apolloClient.query do not log anything (there's no way for the code to do this, the console log is at the top of the authLink middleware).
I started my project with apollo-boost before switching to apolloclient, so I thought perhaps node_modules was not correctly set up after the switch. But I've removed and reinstalled with yarn, it should not have any vestiges of apollo-boost in there now.
additionally, if I copy the code that I use to create apolloclient into my transaction, making it use that local copy instead of the global one, the middleware DOES fire.
ie:
export const relayBBNToGraphcool = async () => {
/* BEGIN without this code, WHICH IS ALREADY in the instantiation of apolloClient, the result is `user: null` */
const authLink = setContext(async (req, { headers }) => {
// get the authentication token from local storage if it exists
let getToken = async () => await AsyncStorage.getItem(/*'access_token'*/'graphcool_token')
const token = await getToken()
console.trace('token for connection to graphcool is currently', token, req.operationName)
// return the headers to the context so httpLink can read them
return token
? {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null,
}
}
: { headers }
})
const httpLink = new HttpLink(config)
const link = ApolloLink.from([/* retryLink, */ authLink, httpLink])
const cache = new InMemoryCache()
// overriding apolloClient in the global scope of this module
const apolloClient = new ApolloClient({
link,
cache
})
/* END */
apolloClient.query({ query: User.self, forceFetch: true })
.then(authneticatedUser => {
console.trace('response', authneticatedUser)
if(authneticatedUser.data.user === null)
throw ('no user')
apolloClient is configured from apollo-client not apollo-boost. It is attached to its provider in App.js:
return (
<ApolloProvider client={this.state.apolloClient}>
that is loaded from a different file with getApolloClient() -- which sets a local variable apolloClient:
var apolloClient //...
export const getApolloClient = () => { // ...
apolloClient = new ApolloClient({
link,
cache
}) //...
return apolloClient
all calls to .query or .mutate are done from exported functions in this same file, and they use that same var apolloClient. I do not ever instantiate more than one apollo-client. Why is it that some of my queries are firing the middleware, but others are not ?
edit:
per request, the actual links used:
// from src: https://github.com/kadikraman/offline-first-mobile-example/blob/master/app/src/config/getApolloClient.js
export const getApolloClient = async () => {
const retryLink = new RetryLink({
delay: {
initial: 1000
},
attempts: {
max: 1000,
retryIf: (error, _operation) => {
if (error.message === 'Network request failed') {
//if (_operation.operationName === 'createPost')
// return true
}
return false
}
}
})
// from: https://www.apollographql.com/docs/react/recipes/authentication.html
const authLink = setContext(async (req, { headers }) => {
// get the authentication token from local storage if it exists
let getToken = async () => await AsyncStorage.getItem(/*'access_token'*/'graphcool_token')
const token = await getToken()
console.trace('token for connection to graphcool is currently', token, req.operationName)
// return the headers to the context so httpLink can read them
return token
? {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null,
}
}
: { headers }
})
const httpLink = new HttpLink(config)
const link = ApolloLink.from([retryLink, authLink, httpLink])
const cache = new InMemoryCache()
apolloClient = new ApolloClient({
link,
cache
})
try {
await persistCache({
cache,
storage: AsyncStorage
})
} catch (err) {
console.error('Error restoring Apollo cache', err) // eslint-disable-line no-console
}
return apolloClient
}
It turns out that the problem has something to do with cache -- this section in the getApolloClient method:
try {
await persistCache({
cache,
storage: AsyncStorage
})
} catch (err) {
console.error('Error restoring Apollo cache', err) // eslint-disable-line no-console
}
It works if I change the code to save apolloClient before that change is applied to the copy sent to ApolloProvider, like this:
export var apolloClient
// from src: https://github.com/kadikraman/offline-first-mobile-example/blob/master/app/src/config/getApolloClient.js
export const getApolloClient = async () => {
apolloClient = await getRawClient()
try {
await persistCache({
cache,
storage: AsyncStorage
})
} catch (err) {
console.error('Error restoring Apollo cache', err) // eslint-disable-line no-console
}
return apolloClient
}
export const getRawClient = async () => {
const retryLink = new RetryLink({
delay: {
initial: 1000
},
attempts: {
max: 1000,
retryIf: (error, _operation) => {
if (error.message === 'Network request failed') {
//if (_operation.operationName === 'createPost')
// return true
}
return false
}
}
})
// from: https://www.apollographql.com/docs/react/recipes/authentication.html
const authLink = setContext(async (req, { headers }) => {
// get the authentication token from local storage if it exists
let getToken = async () => await AsyncStorage.getItem(/*'access_token'*/'graphcool_token')
const token = await getToken()
console.trace('token for connection to graphcool is currently', token, req.operationName)
// return the headers to the context so httpLink can read them
return token
? {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null,
}
}
: { headers }
})
const httpLink = new HttpLink(config)
const link = ApolloLink.from([/* retryLink, */ authLink, httpLink])
const cache = new InMemoryCache()
return new ApolloClient({
link,
cache
})
}
Then, I also refactor the query & mutate code out of this file, importing apolloClient. That works... which is kinda bizarre, but whatever.

Resources