How to run an async function and set headers per each request in Apollo? - apollo

Let's say I want to get the firebase auth token and set it to each and every request. To fetch the firebase auth token I need to send an async call to the firebase server. Only when it completes I get the token. I tried to set it as shown below. But apollo sends the request before I get the token from firebase. How can I fix this? How can I make apollo wait?
export const client = new ApolloClient({
uri: 'http://localhost:4000/',
request: async operation => {
await firebase.auth().onAuthStateChanged(async user => {
if (user) {
const token = await firebase.auth().currentUser.getIdToken(/* forceRefresh */ true);
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : ''
}
});
}
});
}
});

Related

Set identity cookie in axios

I have setup a project with core identity and successfully register users via my endpoint but I am unable to get the claims principal to my client browser.
using postman to make a post:
https://localhost:7273/signin?username=test&password=Test123!
I can then do the following get in postman
https://localhost:7273/getuserinfo
To successfully get my userclaims from the cookie and retrieve the userinfo.
However when I do the same requests in my nuxt application with axios no cookie is ever set and my response.headers is undefined.
const res = await this.$axios.$post("https://localhost:7273/signin", {}, { params : {username : "test", password: "Test123!"}},
{withCredentials: true}).then(function(response) {
console.log({ headers: response.headers
});
});
In my backend I get signinResult = succeeded
[Route("Signin")]
[HttpPost]
public async Task<IActionResult> Signin(string username, string password)
{
var signinResult = await _signinManager.PasswordSignInAsync(username, password, true, false);
var principal = HttpContext.User as ClaimsPrincipal;
return Ok();
}
What link am I missing that is making this work in postman but not in my webapp?

Nuxt Vuex Helper not sending Client Cookies to API

Okay, I have the bad feeling that I'm missing a key concept in what I'm doing. Hope someone can help me out with a hint.
I'm using Nuxt and Vuex Store Modules. Every fetch a Module Action does is wrapped in a helper Function (saveFetch) that I imported to decrease repetitive code, like this:
export const actions = {
async sampleAction(context, data){
...
await saveFetch(context, 'POST', '/pages', data)
...
}
}
The helper simple checks if the users accessToken is still valid, refreshes it if not and then sends the request:
export const saveFetch = async (context, method = 'POST', path, data) => {
const accessTokenExpiry = context.rootGetters['auth/getAccessTokenExpiry']
let accessToken = context.rootGetters['auth/getAccessToken']
// If the client provides an accessToken and the accessToken is expired,
// refresh the token before making the "real" fetch.
if (accessToken && accessTokenExpiry < new Date() && path !== '/auth/refresh-token') {
if (process.client) {
// Works fine
await window.$nuxt.$store.dispatch('auth/refreshToken')
} else {
// This is where the trouble starts
await context.dispatch('auth/refreshToken', null, { root: true })
}
accessToken = rootGetters['auth/getAccessToken']
}
return fetch(path, {
method,
headers: { ... },
body: JSON.stringify(data),
}
}
If the accessToken is expired the helper function dispatches a Vuex Action to refresh it. This works well on the client side, but not if that process happens on the server side.
The Problem that's coming up on the server side is, that the user has to provide a refreshToken to get a refreshed accessToken from the API. This refreshToken is stored as a HttpOnly Cookie in the Client. When logging the Nuxt request details on the API side of things I noticed, that Nuxt is not sending that cookie.
My current workaround looks like this:
export const actions = {
async refreshToken(context){
...
let refreshToken
if (process?.server && this?.app?.context?.req?.headers?.cookie) {
const parsedCookies = cookie.parse(
this.app.context.req.headers.cookie
)
refreshToken = parsedCookies?.refreshToken
}
const response = await saveFetch(context, 'POST', '/auth/refresh-token', {
refreshToken,
})
...
}
...
}
If on server side, access the req object, get the cookies from it and send the refreshToken Cookie Content in the requests body.
This looks clearly bad to me and I would love to get some feedback on how to do this better. Did I maybe miss some key concepts that would help me not get into this problem in the first place?

Unable to set authorization header using Apollo's setContext()

So I'm trying to pass an authorization header to Apollo according to the docs on their site.
https://www.apollographql.com/docs/react/networking/authentication/?fbclid=IwAR17YAJ0VA5G8InmAJ_PxcukXKNQxFLFI8aeT4oB7wYE3DjWNaB_F67__zs
However, when I try to log request.headers on my server I don't see the authorization property on there. Next I checked whether or not the function setContext() was being run at all by putting a console.log() within it and found that it didn't run.
const httpLink = new HttpLink({ uri: 'http://localhost:4000' });
const authLink = setContext((request, { headers }) => {
const token = localStorage.getItem('library-user-token')
console.log(token) //doesn't log at all
return ({
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null,
}
})
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
I have almost identical code in my project and it works. Are you passing the client to your ApolloProvider?

Stop subsequent queries in apollo after 401 has been returned?

I am using apollo client to make a query in my Component. It is composed with 2 queries. How do i stop it from sending another query to it after it has given me a 401 error. I am using a onError Apollo Link Error to listen for errors. However it dispatches both queries and i cannot stop the next one.
Apollo Link Error allows you to intercept and handle query or network errors. It doesn't however provide an opportunity to manage subsequent requests. For this you will need to create your own link.
I've used something like the following in the past. The example below specifically handles bearer auth with refresh tokens but the same principle could be used to handle any auth failure.
import { ApolloLink, Observable } from 'apollo-link';
const isAuthError = (statusCode: number) => [401, 403].includes(statusCode);
const authLink = new ApolloLink((operation, forward) => {
// Set outgoing Authorization headers
const setHeaders = () =>
operation.setContext(({ store, headers, ...rest }) => {
// 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 {
...rest,
store,
headers: {
...headers,
authorization: `Bearer ${token}`
}
};
});
setHeaders();
return new Observable(obs => {
const subscriber = {
next: obs.next.bind(obs),
// Handle auth errors. Only network or runtime errors appear here.
error: error => {
if (isAuthError(error.statusCode)) {
// Trigger an auth refresh.
refreshTokenOrLogin()
.then(setHeaders)
.then(() => forward(operation).subscribe(subscriber));
}
}
});
} else {
obs.error(error);
}
},
complete: obs.complete.bind(obs)
};
forward(operation).subscribe(subscriber);
});
});
The first portion sets the auth context as documented by Apollo. You should replace this with whichever auth mechanism you are using.
operation.setContext(({ store, headers, ...rest }) => {
// 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 {
...rest,
store,
headers: {
...headers,
authorization: `Bearer ${token}`
}
};
});
Non terminating links like this must return an observable. This allows us to catch any network errors just as Apollo Link Error does except we can now handle what happens subsequently. In this case we create and return a new observable with an error handler that will trigger an auth token refresh and then retry the request. The next and completion handlers are passed through to the next link untouched.
new Observable(obs => {
const subscriber = {
next: obs.next.bind(obs),
// Handle auth errors. Only network or runtime errors appear here.
error: error => {
if (isAuthError(error.statusCode)) {
// Trigger an auth refresh.
refreshTokenOrLogin()
.then(setHeaders)
.then(() =>
// We can now retry the request following a successful token refresh.
forward(operation).subscribe(subscriber)
);
}
}
});
} else {
obs.error(error);
}
},
complete: obs.complete.bind(obs)
};
forward(operation).subscribe(subscriber);
});
It might be easier to think of this as 2 links. One that sets the outgoing auth context and the other that captures the response and handles the auth errors.

How to unit test file upload with Supertest -and- send a token?

How can I test a file upload with a token being sent? I'm getting back "0" instead of a confirmation of upload.
This is a failed test:
var chai = require('chai');
var expect = chai.expect;
var config = require("../config"); // contains call to supertest and token info
describe('Upload Endpoint', function (){
it('Attach photos - should return 200 response & accepted text', function (done){
this.timeout(15000);
setTimeout(done, 15000);
config.api.post('/customer/upload')
.set('Accept', 'application.json')
.send({"token": config.token})
.field('vehicle_vin', "randomVIN")
.attach('file', '/Users/moi/Desktop/unit_test_extravaganza/hardwork.jpg')
.end(function(err, res) {
expect(res.body.ok).to.equal(true);
expect(res.body.result[0].web_link).to.exist;
done();
});
});
});
This is a Working test:
describe('Upload Endpoint - FL token ', function (){
this.timeout(15000);
it('Press Send w/out attaching photos returns error message', function (done){
config.api.post('/customer/upload')
.set('Accept', 'application.json')
.send({"token": config.token })
.expect(200)
.end(function(err, res) {
expect(res.body.ok).to.equal(false);
done();
});
});
Any suggestions are appreciated!
With supertest 4.0.2 I was able to set the token and attach the file:
import * as request from 'supertest';
return request(server)
.post('/route')
.set('Authorization', 'bearer ' + token)
.attach('name', 'file/path/name.txt');
And even better, per the docs, you can make a Buffer object to attach:
const buffer = Buffer.from('some data');
return request(server)
.post('/route')
.set('Authorization', 'bearer ' + token)
.attach('name', buffer, 'custom_file_name.txt');
It seems like the token field is overrided when attaching a file. My workaround is to add token to URL query parameter:
describe('Upload Endpoint - FL token ', function (){
this.timeout(15000);
it('Press Send w/out attaching photos returns error message', function (done){
config.api.post('/customer/upload/?token='+config.token)
.attach('file', '/Users/moi/Desktop/unit_test_extravaganza/hardwork.jpg')
.expect(200)
.end(function(err, res) {
expect(res.body.ok).to.equal(false);
done();
});
});
Your authentication middleware must be set to extract the JWT from URL query parameter. Passport-JWT performs this extraction on my server.
The official document states
When you use .field() or .attach() you can't use .send() and you must not set Content-Type (the correct type will be set for you).
So replace
.send({"token": config.token})
with
.field("token", config.token)