Import Vuex Store into Axios interceptors - javascript

The main use of this is to store a new token that will be refreshed on every request automatically in the Vuex store.
However, I don't seem to understand how I can import the store, so that I can store.dispatch('STORE_TOKEN') from the interceptor.
Note that my app is SSR and that the Vue APP itself is being created by the factory method createApp().
How can I import the store and make it work?
app.js
export function createApp () {
// create store and router instances
const store = createStore()
const router = createRouter()
// sync the router with the vuex store.
// this registers `store.state.route`
sync(store, router)
// create the app instance.
// here we inject the router, store and ssr context to all child components,
// making them available everywhere as `this.$router` and `this.$store`.
const app = new Vue({
router,
store,
render: h => h(App)
})
// expose the app, the router and the store.
// note we are not mounting the app here, since bootstrapping will be
// different depending on whether we are in a browser or on the server.
return { app, router, store }
}
axios-instance.js
import axios from 'axios';
var instance = axios.create();
instance.defaults.baseURL = 'http://localhost:8000';
instance.interceptors.response.use((response) => {
console.log("interceptop", response);
console.log(this.$store);
return response;
});
export default instance;
api.js
import axios from 'axios'
export function loginUser() {
return new Promise((resolve, reject) => {
axios({
'method': 'post',
'url': 'http://localhost:8000/auth/login',
'data': {
'email': 'user1#example.com',
'password': '1234'
}
}).then((response) => {
console.log(response);
return response.data;
}).then(
(result) => {
console.log("Response", result);
resolve(result)
},
(error) => {
reject(error)
}
)
})
}

Related

BrowserAuthError: interaction_in_progress when trying to get accesstoken in React

I am trying to used Azure AD to integrate my application, but I keep getting this error
AuthError.ts:49 Uncaught (in promise) BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API. For more visit: aka.ms/msaljs/browser-errors.
at BrowserAuthError.AuthError [as constructor] (AuthError.ts:49:1)
at new BrowserAuthError (BrowserAuthError.ts:195:1)
at BrowserAuthError.createInteractionInProgressError (BrowserAuthError.ts:276:1)
at BrowserCacheManager.setInteractionInProgress (BrowserCacheManager.ts:1000:1)
at ClientApplication.preflightInteractiveRequest (ClientApplication.ts:837:1)
at ClientApplication.preflightBrowserEnvironmentCheck (ClientApplication.ts:820:1)
at PublicClientApplication.<anonymous> (ClientApplication.ts:272:1)
at step (vendors~main.chunk.js:217:19)
at Object.next (vendors~main.chunk.js:147:14)
at vendors~main.chunk.js:119:67
at new Promise (<anonymous>)
at __awaiter (vendors~main.chunk.js:98:10)
at ClientApplication.acquireTokenRedirect (ClientApplication.ts:268:1)
at index.tsx:50:1
This is my index.tsx file:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import '#scuf/common/honeywell/theme.css';
import '#scuf/datatable/honeywell/theme.css';
import store from './stores';
import { Provider } from 'mobx-react';
import createRouter from './router';
import './index.scss';
import { msalConfig } from "./stores/authConfig";
import { MsalProvider, MsalAuthenticationTemplate } from "#azure/msal-react";
import { InteractionRequiredAuthError, AuthError } from "#azure/msal-common";
import { PublicClientApplication, InteractionType } from "#azure/msal-browser";
const msalInstance = new PublicClientApplication(msalConfig);
msalInstance.handleRedirectPromise()
.then((redirectResponse) => {
if (redirectResponse !== null) {
// Acquire token silent success
let accessToken = redirectResponse.accessToken;
console.log(accessToken)
// Call your API with token
} else {
// MSAL.js v2 exposes several account APIs, logic to determine which account to use is the responsibility of the developer
const activeAccount = msalInstance.getActiveAccount();
const accounts = msalInstance.getAllAccounts();
if (!activeAccount && accounts.length === 0) {
console.error("User not logged in!!!");
}
const accessTokenRequest = {
scopes: ["user.read", "openid"],
account: activeAccount || accounts[0],
// roles: ["rca.approver"],
};
msalInstance
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
// Acquire token silent success
// Call API with token
let accessToken = accessTokenResponse.accessToken;
console.log(accessToken)
// Call your API with token
})
.catch(function (error) {
//Acquire token silent failure, and send an interactive request
console.log(error);
if (error instanceof InteractionRequiredAuthError || error instanceof AuthError) {
msalInstance.acquireTokenRedirect(accessTokenRequest);
}
});
}
})
// Here we are importing our stores file and spreading it across this Provider. All stores added to this will be accessible via child injects
const wrappedApp = (
<MsalProvider instance={msalInstance}>
<MsalAuthenticationTemplate interactionType={InteractionType.Redirect}>
<Provider store={store}>
<App />
</Provider>
</MsalAuthenticationTemplate>
</MsalProvider>
);
// Here the router is bootstrapped
const router = createRouter();
router.start(() => {
ReactDOM.render(wrappedApp, document.getElementById('root') as HTMLElement);
});
and this is my authConfig.js:
export const msalConfig = {
auth: {
clientId: "XXX",
authority: "https://login.microsoftonline.com/YYY",
redirectUri: "http://localhost:3000",
postLogoutRedirectUri: "http://localhost:3000",
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
}
};
// Add scopes here for ID token to be used at Microsoft identity platform endpoints.
export const loginRequest = {
scopes: ["User.Read","openid"],
redirectUri: "http://localhost:3000",
};
// Add the endpoints here for Microsoft Graph API services you'd like to use.
export const graphConfig = {
graphMeEndpoint: "https://graph.microsoft.com/v1.0/me"
};
I have tried the solution in net but it still gives me the same error. These are the only two files in my project folder that deals with MSAL packages. Did I miss anything? As I learnt from the documenatation, interactionType redirects to AD authentication on which token is generated which could then be sent to APIs. Please correct me if I am wrong.

FeathersJS Twitch OAuth 401 Unauthorized

I'm new to FeathersJS. I tried to set up OAuth login with Twitch. I created a twitch oauth application and did the same as here with github login. I want to save the user in my MongoDB database, but I'm getting redirected to http://localhost:3030/#error=401%20Unauthorized after logging in on twitch. I've generated a fresh application with the feathers cli. What am I doing wrong?
config/default.json
...
"oauth": {
"redirect": "/",
"twitch": {
"key": ""*****************",",
"secret": ""***************************************"",
"scope": ["user:read:email"]
},
"github": {
"key": "*****************",
"secret": "***************************************"
}
}...
This is because the built-in strategy is attempting to fetch the user profile, yet it is not including your Client ID in the headers (see Twitch API - Getting Started.
To solve this you need to create your own strategy that implements getProfile. I used the Facebook demo from the feathersjs cookbook as a reference which can be found here.
Here is my implementation:
./strategies/TwitchStrategy.ts
import { Params } from '#feathersjs/feathers'
import { AuthenticationRequest } from '#feathersjs/authentication'
import { OAuthStrategy, OAuthProfile } from '#feathersjs/authentication-oauth'
import axios from 'axios'
import { Application } from '../declarations'
export class TwitchStrategy extends OAuthStrategy {
// we need a reference to the app instance
app: Application = {} as Application
// when the strategy is initialized this method is called with an app instance
setApplication(appInstance: Application): void {
this.app = appInstance
}
// this method is used to get the user profile after they authorize with the provider
async getProfile(authResult: AuthenticationRequest, _params: Params) {
const accessToken = authResult.access_token
const { data } = await axios.get('https://api.twitch.tv/helix/users', {
headers: {
Authorization: `Bearer ${accessToken}`, //our users access token to look them up
'Client-ID': this.app.get('authentication').oauth.twitch.key //we need to send the Client-ID
},
params: {
fields: 'id,name,email'
}
})
console.log(data)
return data
}
async getEntityData(profile: OAuthProfile, existing: any, params: Params) {
// `profile` is the data returned by getProfile
const baseData = await super.getEntityData(profile, existing, params)
return {
...baseData,
email: profile.email
}
}
}
./authentication.ts
import { ServiceAddons } from '#feathersjs/feathers'
import { AuthenticationService, JWTStrategy } from '#feathersjs/authentication'
import { LocalStrategy } from '#feathersjs/authentication-local'
// import our strategy
import { TwitchStrategy } from './strategies/TwitchStrategy'
import { expressOauth } from '#feathersjs/authentication-oauth'
import { Application } from './declarations'
declare module './declarations' {
interface ServiceTypes {
authentication: AuthenticationService & ServiceAddons<any>
}
}
export default function (app: Application): void {
const authentication = new AuthenticationService(app)
authentication.register('jwt', new JWTStrategy())
// register our custom strategy
authentication.register('twitch', new TwitchStrategy())
authentication.register('local', new LocalStrategy())
app.use('/authentication', authentication)
app.configure(expressOauth())
}

How to structure API code in a Vue Single-Page App?

I'm building a fairly large SPA using Vue (and Laravel for RESTful API). I'm having a hard time finding resources about this online - what's a good practice to organise the code that communicates with the server?
Currently I have src/api.js file, which uses axios and defines some base methods as well as specific API endpoints (truncated):
import axios from 'axios';
axios.defaults.baseURL = process.env.API_URL;
const get = async (url, params = {}) => (await axios.get(url, { params }));
const post = async (url, data = {}) => (await axios.post(url, data));
export const login = (data) => post('users/login', data);
And then in my component, I can do
...
<script>
import { login } from '#/api';
...
methods: {
login() {
login({username: this.username, password: this.password})
.then() // set state
.catch() // show errors
}
}
</script>
Is this a good practice? Should I split up my endpoints into multiple files (e.g. auth, users, documents etc.)? Is there a better design for this sort of thing, especially when it comes to repetition (e.g. error handling, showing loading bars etc.)?
Thanks!
If you're just using Vue and expect to be fetching the same data from the same component every time, it's generally idiomatic to retrieve the data and assign it using the component's mounted lifecycle hook, like so:
<template>
<h1 v-if="name">Hello, {{name}}!</h1>
</template>
<script>
export default {
data() {
return {
name: '',
}
},
mounted() {
axios.get('https://example.com/api')
.then(res => {
this.name = res.data.name;
})
.catch(err =>
// handle error
);
},
};
</script>
If you're going to be using Vuex as mentioned in one of your comments, you'll want to put your API call into the store's actions property.
You'll end up with a Vuex store that looks something like this:
const store = new Vuex.Store({
state: {
exampleData: {},
},
mutations: {
setExampleData(state, data) {
state.exampleData = data;
},
},
actions: {
async getExampleData() {
commit(
'setExampleData',
await axios.get('https://www.example.com/api')
.then(res => res.data)
.catch(err => {
// handle error
});
);
},
}
});
Of course, breaking out your state, actions, and mutations into modules as your app grows is good practice, too!
If you use Vue CLI it will setup a basic project structure. With a HelloWorld component. You will want to break your vue app into components. Each component should have a defined role that ideally you could then unit test.
For example lets say you want to show list of products then you should create a product list component.
<Products :list="products" />
In your app you would do something like
data() {
return {
prodcuts: []
}
},
mounted() {
axios.get('/api/products').then(res => {
this.products = res.data
})
}
Whenever you see something that "is a block of something" make a component out of it, create props and methods and then on the mounted hook consume the api and populate the component.

How to use vue-resource ($http) and vue-router ($route) in a vuex store?

Before I was getting movie detail from the component's script. The function first check whether the movie ID of the store is same as of the route's param movie ID. If its same then don't get the movie from the server API, or else get the movie from the server API.
It was working fine. But now I am trying to get the movie details from the store's mutation. However I am getting error
Uncaught TypeError: Cannot read property '$route' of undefined
How to use vue-router ($route) to access the params and vue-resource ($http) to get from the server API in vuex store?
store.js:
export default new Vuex.Store({
state: {
movieDetail: {},
},
mutations: {
checkMovieStore(state) {
const routerMovieId = this.$route.params.movieId;
const storeMovieId = state.movieDetail.movie_id;
if (routerMovieId != storeMovieId) {
let url = "http://dev.site.com/api/movies/movie-list/" + routerMovieId + "/";
this.$http.get(url)
.then((response) => {
state.movieDetail = response.data;
})
.catch((response) => {
console.log(response)
});
}
},
},
});
component script:
export default {
computed: {
movie() {
return this.$store.state.movieDetail;
}
},
created: function () {
this.$store.commit('checkMovieStore');
},
}
To use $http or $router in your vuex store, you would need to use the main vue instance. Although I don't recommend using this, I'll add what I recommend after answering the actual question.
In your main.js or wherever you are creating your vue instance like:
new Vue({
el: '#app',
router,
store,
template: '<App><App/>',
components: {
App
}
})
or something similar, you might also have added the vue-router and vue-resource plugins too.
Doing a slight modification to this:
export default new Vue({
el: '#app',
router,
store,
template: '<App><App/>',
components: {
App
}
})
I can now import it in vuex stores like so:
//vuex store:
import YourVueInstance from 'path/to/main'
checkMovieStore(state) {
const routerMovieId = YourVueInstance.$route.params.movieId;
const storeMovieId = state.movieDetail.movie_id;
if (routerMovieId != storeMovieId) {
let url = "http://dev.site.com/api/movies/movie-list/" + routerMovieId + "/";
YourVueInstance.$http.get(url)
.then((response) => {
state.movieDetail = response.data;
})
.catch((response) => {
console.log(response)
});
}
}
and as the answer by Austio goes, this method should be an action as mutations are not designed to handle async.
Now coming to the recommended way of doing it.
Your component can access the route params and provide it to the action.
methods: {
...mapActions({
doSomethingPls: ACTION_NAME
}),
getMyData () {
this.doSomethingPls({id: this.$route.params})
}
}
The action then makes the call through an abstracted API service file (read plugins)
[ACTION_NAME]: ({commit}, payload) {
serviceWhichMakesApiCalls.someMethod(method='GET', payload)
.then(data => {
// Do something with data
})
.catch(err => {
// handle the errors
})
}
Your actions do some async job and provide the result to a mutation .
serviceWhichMakesApiCalls.someMethod(method='GET', payload)
.then(data => {
// Do something with data
commit(SOME_MUTATION, data)
})
.catch(err => {
// handle the errors
})
Mutations should be the only ones to modify your state.
[SOME_MUTATION]: (state, payload) {
state[yourProperty] = payload
}
Example
A file which contains a list of endpoints, you might need it if you have different stages of deployment which have different api endpoints like: test, staging, production, etc.
export const ENDPOINTS = {
TEST: {
URL: 'https://jsonplaceholder.typicode.com/posts/1',
METHOD: 'get'
}
}
And the main file which implements Vue.http as a service:
import Vue from 'vue'
import { ENDPOINTS } from './endpoints/'
import { queryAdder } from './endpoints/helper'
/**
* - ENDPOINTS is an object containing api endpoints for different stages.
* - Use the ENDPOINTS.<NAME>.URL : to get the url for making the requests.
* - Use the ENDPOINTS.<NAME>.METHOD : to get the method for making the requests.
* - A promise is returned BUT all the required processing must happen here,
* the calling component must directly be able to use the 'error' or 'response'.
*/
function transformRequest (ENDPOINT, query, data) {
return (ENDPOINT.METHOD === 'get')
? Vue.http[ENDPOINT.METHOD](queryAdder(ENDPOINT.URL, query))
: Vue.http[ENDPOINT.METHOD](queryAdder(ENDPOINT.URL, query), data)
}
function callEndpoint (ENDPOINT, data = null, query = null) {
return new Promise((resolve, reject) => {
transformRequest(ENDPOINT, query, data)
.then(response => { return response.json() })
.then(data => { resolve(data) })
.catch(error => { reject(error) })
})
}
export const APIService = {
test () { return callEndpoint(ENDPOINTS.TEST) },
login (data) { return callEndpoint(ENDPOINTS.LOGIN, data) }
}
The queryAdder in case it is important, I was using this to add params to the url.
export function queryAdder (url, params) {
if (params && typeof params === 'object' && !Array.isArray(params)) {
let keys = Object.keys(params)
if (keys.length > 0) {
url += `${url}?`
for (let [key, i] in keys) {
if (keys.length - 1 !== i) {
url += `${url}${key}=${params[key]}&`
} else {
url += `${url}${key}=${params[key]}`
}
}
}
}
return url
}
So a few things, the $store and $route are properties of the Vue instance, which is why accessing them inside of Vuex instance is not working. Also, mutations are synchonous what you need are actions
Mutations => A function that given state and some arguments mutates the state
Action => Do async things like http calls and then commit results to a mutation
So create an action that dispatches the http. Keep in mind this is pseudocode.
//action in store
checkMovieStore(store, id) {
return $http(id)
.then(response => store.commit({ type: 'movieUpdate', payload: response })
}
//mutation in store
movieUpdate(state, payload) {
//actually set the state here
Vue.set(state.payload, payload)
}
// created function in component
created: function () {
return this.$store.dispatch('checkMovieStore', this.$route.params.id);
},
Now your created function dispatches the checkMovieStore action with the id, which does the http call, once that is complete it updates the store with the value.
In your vuex store:
import Vue from 'vue'
Vue.http.post('url',{})
Not like in normal vue components:
this.$http.post(...)
I highly recommend importing axios on the vuex module (store and submodules), and using it for your http requests
To access the vue instance in the store use this._vm.
But as Amresh advised do not use things like $router in vuex

Testing a Redux login Action

I'm hoping that I could ask for some help on how to test a Redux Action that involves a login API call. I've looked at some examples of testing an async Action, but I haven't wrapped my head around how to test the code below.
As a starting point, I would like to test that a)AUTH_USER is called if the .post request returns a 200 and b)localStorage` contains the token from the API call.
I've looked at using redux-mock-store, fetch-mock and isomorphic-fetch in order to mock the API calls to make sure I always receive the expected API response, but I have no idea where to start with the test.
Any help would be highly appreciated on a starting point for the tests! Even some help on just testing that 200 will return AUTH_USER would be appreciated!
Note: Elsewhere for other tests I'm using, redux-mock-store, enzyme, chai, expect, fetch-mock, isomorphic-fetch
import axios from 'axios';
import { browserHistory } from 'react-router';
import { API_URL } from 'config';
import {
AUTH_USER
} from './types';
export function loginUser({ email, password }) {
return function (dispatch) {
axios.post(`${API_URL}/auth/login`, { email, password })
.then((response) => {
dispatch({ type: AUTH_USER });
localStorage.setItem('token', response.data.token);
browserHistory.push('/feature');
})
.catch(() => {
dispatch(authError('Bad Login Info'));
});
};
}
Async Test Motivation
We want to ensure that a AUTHENTICATION_SUCCESS action is dispatched by our redux thunk middleware if the login is successful a AUTHENTICATION_FAILED action if the log in fails.
Remember we are not testing the Redux Thunk middleware, instead we are only testing our Thunk Action creator.
Testing a Redux Thunk action creator which queries an API
Create a mock store for each unit test with redux-thunk middleware
Use a mocking library such as nock to intercept http requests to test which actions are dispatched for a given type of request. Since we are testing a login request obvious cases here are http responses signalling login success and failure.
Verify the correct action was dispatched to the store for a given http response.
Example
Tests
Here is an example of two tests for login success and failure using nock to mock the api call and the expect library for test assertions.
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import nock from 'nock'
import expect from 'expect' // You can use any testing library
// modify these imports to suit your project
import * as actions from '../../actions/TodoActions'
import * as types from '../../constants/ActionTypes'
import {
AUTH_USER, AUTH_ERROR
} from './types';
const API_URL = 'www.api-example.com'
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('async actions', () => {
afterEach(() => {
nock.cleanAll()
})
it('creates AUTH_USER action when user is logged in', () => {
nock(API_URL)
.post(/auth/login)
.reply(200, { data: 'Logged in successfully'] }})
const expectedActions = [
{ type: AUTH_USER }
]
const store = mockStore({ })
return store.dispatch(actions.loginUser({'example#x.com','password'}))
.then(() => { // return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})
it('creates AUTH_ERROR if user login fails', () => {
nock(API_URL)
.post(/auth/login)
.reply(404, { data: {error: 404 }] }})
const expectedActions = [
{ type: AUTH_ERROR }
]
const store = mockStore({ })
return store.dispatch(actions.loginUser({'example#x.com','password'}))
.then(() => { // return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})
})
Now to make the example work you need to add a return statement inside the function returned by your thunk action creator.
By eventually returning the promise given to us by axios.post we can add the .then call inside our test to make assertions about which actions have been dispatched after the promise has resolved.
Thunk action creator
import axios from 'axios';
import { browserHistory } from 'react-router';
import { API_URL } from 'config';
import {
AUTH_USER
} from './types';
export function loginUser({ email, password }) {
return function (dispatch) {
return axios.post(`${API_URL}/auth/login`, { email, password })
.then((response) => {
dispatch({ type: AUTH_USER });
localStorage.setItem('token', response.data.token);
browserHistory.push('/feature');
})
.catch(() => {
dispatch(authError('Bad Login Info'));
});
};
}

Categories

Resources