I have this response with httpie when an user is logged:
chat-api$ http :3000/signup username=tomatito password=123
HTTP/1.1 201 Created
Cache-Control: max-age=0, private, must-revalidate
Content-Type: application/json; charset=utf-8
ETag: W/"01dfe24bd7415e252b5aee50e12198a3"
Transfer-Encoding: chunked
Vary: Origin
X-Request-Id: a095148b-592a-4347-820f-63e1efa0e409
X-Runtime: 0.347726
{
"auth_token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo1LCJleHAiOjE1MjEzMTg4NDV9.45JDA7vk-K8gUzCB1xABKMifi-IWGoVESedKykGiqGo",
"message": "Account created successfully"
}
The object is persisted in my database.
However when i make this request with axios from my vue.js form I get nothing in localStorage
this is my axios.js code:
import axios from 'axios'
const API_URL = process.env.API_URL || 'http://localhost:3000/'
export default axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.auth_token
}
})
the object is persisted in database right but i get Authorization:Bearer undefined
these are my headers:
Response:
Access-Control-Allow-Methods:GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD
Access-Control-Allow-Origin:http://localhost:8081
Access-Control-Expose-Headers:
Access-Control-Max-Age:1728000
Cache-Control:max-age=0, private, must-revalidate
Content-Type:application/json; charset=utf-8
ETag:W/"fdac439f3ada9e343d0815bb49dff277"
Transfer-Encoding:chunked
Vary:Origin
X-Request-Id:9e318050-ceca-480c-a847-d59f9ebb18b7
X-Runtime:0.447976
Request:
Accept:application/json, text/plain, */*
Accept-Encoding:gzip, deflate, br
Accept-Language:en-US,en;q=0.9
Authorization:Bearer undefined
Connection:keep-alive
Content-Length:44
Content-Type:application/json
Host:localhost:3000
Origin:http://localhost:8081
Referer:http://localhost:8081/
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.
Request payload
{username: "tomatito", password: "123456"}
password:"123456"username:"tomatito"
This is my vue script component:
<script>
export default {
name: 'SignUp',
data () {
return {
username: '',
password: '',
error: false
}
},
methods: {
signup () {
this.$http.post('/signup', { username: this.username, password: this.password })
.then(request => this.signupSuccessful(request))
.catch(() => this.signupFailed())
},
signupSuccessful (req) {
if (!req.data.token) {
this.signupFailed()
return
}
localStorage.token = req.data.token
this.error = false
this.$router.replace(this.$route.query.redirect || '/rooms')
},
signupFailed () {
this.error = 'Sign up failed!'
delete localStorage.token
}
}
}
</script>
I'm getting Sign up failed, However the object is persisted in database. My back-end is ruby on rails. How can i receive in my data.token in payload?
This is my main.js file
import Vue from 'vue'
import App from './App'
import router from './router'
import axios from './backend/vue-axios'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
axios,
template: '<App/>'
})
This is my vue-axios/index.js file:
import Vue from 'vue'
import VueAxios from 'vue-axios'
import axios from './axios'
Vue.use(VueAxios, axios)
Updated The problem was in req. it's res to receive the token instead of req
signup() {
this.$http
.post("/signup", { username: this.username, password: this.password })
.then(res => this.signupSuccessful(res))
.catch(() => this.signupFailed());
},
signupSuccessful(res) {
if (!res.data.auth_token) {
this.signupFailed();
return;
}
this.error = false;
localStorage.token = res.data.auth_token;
this.$store.dispatch("login");
this.$router.replace(this.$route.query.redirect || "/rooms");
},
.
.
.
.
Thank you
Try to set the token on every request with axios.interceptors
Put this on the main.js file so everywhere you import axios will have the config
axios.interceptors.request.use(config => {
const token= localStorage.getItem('my-token-key') // or where you have the token
config.headers.common['Authorization'] = 'Bearer ' + token
// you must return the config or it will not work
return config
})
I think that the problem is that axios.create instance is executed (created) just 1 time (then reference it), and not every time you import it, so if there was no token when the instance was created it will not work
Updated The problem was in req. it's res to receive the token instead of req
signup() {
this.$http
.post("/signup", { username: this.username, password: this.password })
.then(res => this.signupSuccessful(res))
.catch(() => this.signupFailed());
},
signupSuccessful(res) {
if (!res.data.auth_token) {
this.signupFailed();
return;
}
this.error = false;
localStorage.token = res.data.auth_token;
this.$store.dispatch("login");
this.$router.replace(this.$route.query.redirect || "/rooms");
},
.
.
.
.
Related
Getting 500 internal server error for my next authentication.
I implemented everything according to the docs from Next Auth.
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import axios from "axios";
import { API_URL } from "./../../../helpers/api/mutations";
const options = {
providers: [
CredentialsProvider({
name: "Credentials",
API works fine in a separate react component but fails only on Next Auth.
async authorize(credentials) {
const user = axios({
url: API_URL,
method: "post",
data: {
query: `mutation($mobileNumber:String,$mobileCountryCode:String,$otp:Int){
verifyPhoneOTP(
mobileNumber: $mobileNumber,
mobileCountryCode: $mobileCountryCode,
otp:$otp
)
{accessToken
expiresAt}
}`,
variables: {
mobileNumber: credentials.mobile,
mobileCountryCode: credentials.email,
otp: credentials.otpFormatt,
},
},
validateStatus: (status) => {
if (status == 500) {
console.log(status, "status axios log");
console.log(credentials, "credentials log");
return true;
}
},
headers: {
"User-Agent":
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36",
Accept: "application/json; charset=UTF-8",
},
})
.catch((error) => {
console.log(error.response.data, "axios fail calls");
})
.then((result) => {
Currently I can see this console log "axios calls" successfully displays itself on the terminal but the tokens and sessions aren't returned.
console.log(result.data, "axios calls");
console.log(credentials, "creds");
return result.data.data
});
if (user) {
return user;
} else {
return null;
}
},
}),
],
};
const callbacks = {
async jwt(token, user) {
if (user) {
token.accessToken = user.token;
}
return token;
},
secret:process.env.SECRET,
async session(session, token) {
session.accessToken = token.accessToken;
return session;
},
};
export default (req, res) => NextAuth(req, res, options);
Error 500 means server error. Check you api server whether it is running or stopped. Error 500 here has nothing to do with nextjs, it is all about the back-end server.
I've created API in node express and I'm running it on port :8000, I am consuming APIs through simple CRA on port :3000. I've created registration and login with setting httpOnly cookie. Furthermore, I've put middleware to check each endpoint in order to verify if it has that token.
When I test through Thunder/Postman everything works, after logging in I get the cookie in response, I set that cookie as auth token and make request to get data and I get the data.
When I log in through the React Frontend it succeeds and I can see in network tab that I have received the cookie in response. But when I make a request to protected endpoint, the request does not have a cookie in it (I log incoming requests on server and compare ones made with Thunder/Postman client and via app in Browser).
I use axios, and I've put {withCredentials: true} it doesn't work. I've used withAxios hook and it doesn't work either.
SERVER
index.js
...
const app = express()
app.use(cors({
credentials: true,
origin: 'http://localhost:3000',
}));
...
controllers/User.js
...
const loginUser = async(req, res) => {
const body = req.body
const user = await User.findOne({ email: body.email })
if(user) {
const token = generateToken(user)
const userObject = {
userId: user._id,
userEmail: user.email,
userRole: user.role
}
const validPassword = await bcrypt.compare(body.password, user.password)
if(validPassword) {
res.set('Access-Control-Allow-Origin', req.headers.origin);
res.set('Access-Control-Allow-Credentials', 'true');
res.set(
'Access-Control-Expose-Headers',
'date, etag, access-control-allow-origin, access-control-allow-credentials'
)
res.cookie('auth-token', token, {
httpOnly: true,
sameSite: 'strict'
})
res.status(200).json(userObject)
} else {
res.status(400).json({ error: "Invalid password" })
}
} else {
res.status(401).json({ error: "User doesn't exist" })
}
}
...
middleware.js
...
exports.verify = (req, res, next) => {
const token = req.headers.authorization
if(!token) res.status(403).json({ error: "please provide a token" })
else {
jwt.verify(token.split(" ")[1], tokenSecret, (err, value) => {
if(err) res.status(500).json({error: "failed to authenticate token"})
req.user = value.data
next()
})
}
}
...
router.js
...
router.get('/bills', middleware.verify, getBills)
router.post('/login', loginUser)
...
CLIENT
src/components/LoginComponent.js
...
const loginUser = (e) => {
setLoading(true)
e.preventDefault()
let payload = {email: email, password: password}
axios.post('http://localhost:8000/login', payload).then(res => res.status === 200
? (setLoading(false), navigate('/listbills')) : navigate('/register'))
}
...
src/components/ListBills.js
...
useEffect(() => {
fetch('http://localhost:8000/bills', {
method: 'get',
headers: {'Content-Type': 'application/json'},
credentials: 'include',
})
.then(response => {console.log(response)}).catch(err => console.log(err));
}, [])
...
I've also tried:
axios.get('http://localhost:8000/bills',{withCredentials: true})
.then((data) => console.log(data))
.then((result) => console.log(result))
.catch((err) => console.log('[Control Error ] ', err))
}
and
const [{ data, loading, error }, refetch] = useAxios(
'http://localhost:8000/bills',{
withCredentials: true,
headers: {'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json'
}})
Console.log error:
After I login I get this in Network tab:
However when I want to access the list:
=== UPDATE ===
So the cause of the issue is not having the httpOnly cookie passed in the request header. This is the log of the middleware I am using:
token undefined
req headers auth undefined
req headers {
host: 'localhost:8000',
connection: 'keep-alive',
'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"',
'sec-ch-ua-mobile': '?0',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36',
'sec-ch-ua-platform': '"macOS"',
'content-type': 'application/json',
accept: '*/*',
origin: 'http://localhost:3000',
'sec-fetch-site': 'same-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
referer: 'http://localhost:3000/',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-US,en;q=0.9,hr;q=0.8,sr;q=0.7,bs;q=0.6,de;q=0.5,fr;q=0.4,it;q=0.3'
}
token is read from headers.authorization but from the log of the headers it doesn't exist so my requests fail to be authorized.
Still not working.
After reading everything on CORS and httpOnly cookies I've managed to get it working.
First I removed sameSite and added domain prop according to documentation in controllers/User.js on SERVER
res.cookie('auth-token', token, {
httpOnly: true,
domain: 'http://localhost:3000'
})
Then I got a little yellow triangle in the console request view, it said that domain was invalid. Then I just changed domain to origin and the cookie appeared in the request log of the headers 🎉
res.cookie('auth-token', token, {
httpOnly: true,
origin: 'http://localhost:3000',
})
The cookie was not in the Authorization property of the headers but in the cookie so I had to change the code in the middleware.js since it expected format bearer xxyyzz but receiving auth-token=xxyyzz, it looks like this now:
exports.verify = (req, res, next) => {
const token = req.headers.cookie
if(!token) res.status(403).json({ error: "please provide a token" })
else {
jwt.verify(token.split("=")[1], tokenSecret, (err, value) => {
if(err) res.status(500).json({error: "failed to authenticate token"})
req.user = value.data
next()
})
}
}
I have already searched a lot, but none of the solutions found work: Cannot send content-type by axios. but if I use the postman interceptor and I 'send' the request generated by axios this time it works: the node.js / express server correctly receives the request and body-parser works normally!
React side:
const API_URL = "http://localhost:8800/auth/";
const headers = {
accept: 'application/json, text/plain, */*',
'content-type': 'application/json;charset=UTF-8'
};
class AuthService {
register(pseudo, email, password) {
return axios.post(API_URL + "signup/",
{ pseudo, email, password },
{ headers: headers})
.then(response => {
if (response.data.accessToken) {
localStorage.setItem("user", JSON.stringify(response.data));
}
return response.data;
});
}
server side
const app = express();
app.use(function (req, res, next) {
console.log( req.headers);
next();
});
app.use( bodyParser.urlencoded({ extended: true }), bodyParser.json());
Usually when I use axios I send the headers in a config variable like this and I stringify the body so it sends as JSON object and not a JS object.
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const body = JSON.stringify({arguments});
try {
const res = await axios.post(/url, body, config);
...
Here's a link to the docs for a little more reading about it:
https://github.com/axios/axios
I'm using apollo-server-express with apollo-rest-datasources. I'm trying to return multiple set-cookie header in response header to my client from formatResponse method
set-cookie: key1=value1
set-cookie: key2=value2
But the whenever I pass in array it just stringifies it and makes it comma separated
This is my formatResponse method
formatResponse: (response, requestContext) => {
Object.entries(requestContext.context.response.headers).forEach(([key, value]) => {
requestContext.response.http.headers.set(key, value);
});
This is my context object
context: ({ req, res }) => {
return {
response: {
headers: {
'set-cookie': ['key1=value1', 'key2=value2']
},
},
res,
};
},
the final response I get is of this form
set-cookie: key1=value1, key2=value2 and the browser can't recognise that there are 2 cookies to set and it just sets key1=value1 in cookies.
I use res of express which passed into apollo server context to set multiple cookies. E.g.
import { ApolloServer, gql } from 'apollo-server-express';
import express from 'express';
const typeDefs = gql`
type Query {
dummy: String!
}
`;
const resolvers = {
Query: {
dummy: () => 'hello world',
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }) => {
return {
response: {
headers: {
'set-cookie': ['key1=value1', 'key2=value2'],
},
},
res,
};
},
formatResponse: (response, requestContext: any) => {
// not working
// requestContext.response!.http!.headers.set('set-cookie', 'key1=value1');
// requestContext.response!.http!.headers.set('set-cookie', 'key2=value2');
// works fine
requestContext.context.res.set('set-cookie', ['key1=value1', 'key2=value2']);
return response!;
},
});
const app = express();
server.applyMiddleware({ app });
app.get('/', (req, res) => {
res.sendFile('index.html', { root: __dirname });
});
app.listen({ port: 4000 }, () => console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`));
Get the response header from browser:
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
set-cookie: key1=value1
set-cookie: key2=value2
Content-Type: application/json; charset=utf-8
Content-Length: 33
ETag: W/"21-wOMEaw/ExA+LOH3PCuP0vilR+4w"
Date: Wed, 22 Apr 2020 05:30:20 GMT
Connection: keep-alive
Get cookies by document.cookie:
document.cookie
"key1=value1; key2=value2"
source code: https://github.com/mrdulin/apollo-graphql-tutorial/tree/master/src/stackoverflow/61183199
I'm pretty much copy/pasting the example code from the Apollo GraphQL Network Layer Docs on Creating Middleware, except I'm pulling my dependencies from apollo-client-preset and I'm using a simple utility library to return the jwt token. Here's the link code...
import { ApolloClient, HttpLink, InMemoryCache, ApolloLink, concat } from 'apollo-client-preset';
import { getSessionToken } from './api/localStorage';
const httpLink = new HttpLink({ uri: 'http://localhost:4000/' });
const authMiddleware = new ApolloLink((operation, forward) => {
const token = getSessionToken();
const authorizationHeader = token ? `Bearer ${token}` : null;
operation.setContext({
headers: {
authorization: authorizationHeader,
}
});
return forward(operation);
})
const client = new ApolloClient({
link: concat(authMiddleware, httpLink),
});
And here's the ./api/localStorage method in play...
export const getSessionToken = () => {
return AsyncStorage.getItem(AUTH_TOKEN);
}
Executing the above code results in an HTTP request with the following headers...
POST / HTTP/1.1
Host: localhost:4000
Content-Type: application/json
User-Agent: Expo/2.2.0.1011489 CFNetwork/893.14 Darwin/17.3.0
Connection: keep-alive
Accept: */*
Accept-Language: en-us
Authorization: Bearer [object Object]
Accept-Encoding: gzip, deflate
Content-Length: 174
Notice the Authorization: Bearer [object Object] bit
I think the problem is pretty obvious. AsyncStorage is an async function, and I need to wait for the promise to be resolved before I assemble the authorization string. The solution, however, is less obvious. I've tried various methods, but no love so far.
I've seen a couple examples similar to the above. Here's another in the How to GraphQL Authentication Module. Neither shows how to wait for the asyncstorage promise to resolve first. I know Apollo waits for the promises for you automagically sometimes, so I thought that might be the case here. But doesn't seem to be in my experience.
So what's the solution?
Here's a couple things I've tried, but that have failed.
1. Promise.then()
const middlewareAuthLink = new ApolloLink((operation, forward) => {
getSessionToken().then(token => {
const authorizationHeader = token ? `Bearer ${token}` : null
operation.setContext({
headers: {
authorization: authorizationHeader
}
})
});
return forward(operation)
})
This does construct the authorization string properly, but doesn't seem to create the middleware because this does not create the authorization header. To me, this seems like my best bet, but I must be doing it wrong for reasons which are not obvious to me.
POST / HTTP/1.1
Host: localhost:4000
Content-Type: application/json
Connection: keep-alive
Accept: */*
User-Agent: Expo/2.2.0.1011489 CFNetwork/893.14 Darwin/17.3.0
Content-Length: 174
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Notice the lack of Authorization header
2. async/await
I thought this would be pretty straight forward, turning this into an async/await function, but not so much. Here's the code
const middlewareAuthLink = new ApolloLink(async (operation, forward) => {
const token = await getSessionToken();
const authorizationHeader = token ? `Bearer ${token}` : null;
operation.setContext({
headers: {
authorization: authorizationHeader,
}
});
return forward(operation)
})
But that resulted in this super ugly red-screen-of-death error...
Unhandled (in react-apollo:Apollo(EventList)), ApolloError#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:106405:36
currentResult#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:106516:43
dataForChild#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:101608:79
render#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:101659:49
finishClassComponent#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:4528:102
performUnitOfWork#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:5547:33
workLoop#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:5566:142
_invokeGuardedCallback#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:2707:23
invokeGuardedCallback#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:2681:41
performWork#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:5602:41
scheduleUpdateImpl#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:5723:105
enqueueForceUpdate#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:4341:179
forceUpdate#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:8265:38
forceRenderChildren#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:101579:58
next#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:101554:50
error#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:109797:25
forEach#[native code]
error#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:106747:44
http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:107277:47
http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:107649:29
forEach#[native code]
http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:107648:27
forEach#[native code]
broadcastQueries#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:107644:33
http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:107239:51
tryCallOne#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:10901:14
http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:10987:25
_callTimer#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:12157:15
_callImmediatesPass#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:12193:17
callImmediates#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:12397:31
__callImmediates#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:2301:30
http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:2179:32
__guard#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:2287:11
flushedQueue#http://localhost:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&strict=false&minify=false&hot=false&assetPlugin=/Users/ChrisGeirman/dev/mobile/playground/exp/frogquest-app/node_modules/expo/tools/hashAssetFiles:2178:19
flushedQueue#[native code]
invokeCallbackAndReturnFlushedQueue#[native code]
So how do I wait for the result of AsyncStorage to return before setting the header?
There may be other solutions, or better solutions even, but this finally worked for me so I'm posting it as solved.
import { setContext } from 'apollo-link-context';
const httpLink = new HttpLink({ uri: 'http://localhost:4000/'});
const authHeader = setContext(
request =>
new Promise((success, fail) => {
getSessionToken().then(token => success({ headers: { authorization: `Bearer ${token}` }}))
})
)
const client = new ApolloClient({
link: concat(authHeader, httpLink),
cache: new InMemoryCache(),
})
Here is an alternative which loads the token from an in memory cache which should perform better than fetching from AsyncStorage. Alternatively you could simply have a getter wrapper function which caches the response from AsyncStorage but having this as part of apollo cache has the added benefit of re-rendering when you update the token.
import gql from 'graphql-tag'
import ApolloClient from 'apollo-client'
import { setContext } from 'apollo-link-context'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloLink } from 'apollo-link'
import AsyncStorage from '#react-native-community/async-storage'
const authLink = setContext(async (_, { headers }) => {
const context = await client.query({
query: gql`
query AuthQuery {
authToken #client
}
`
})
const {
data: { authToken: token }
} = context
let extra = {}
if (token) {
extra = { authorization: `Bearer ${token}` }
}
return {
headers: {
...headers,
...extra
}
}
})
const httpLink = createHttpLink({
uri: 'http://localhost:4000/'
})
const cache = new InMemoryCache()
const client = new ApolloClient({
link: ApolloLink.from([authLink, httpLink]),
cache
})
client.writeData({ data: { authToken: null } })
AsyncStorage.getItem('token').then((token: string | null) => {
if (token) {
client.writeData({ data: { authToken: token } })
}
})
export { client }