Vuex - passing multiple parameters to mutation - vue.js

I am trying to authenticate a user using vuejs and laravel's passport.I am not able to figure out how to send multiple parameters to the vuex mutation via an action.
- store -
export default new Vuex.Store({
state: {
isAuth: !!localStorage.getItem('token')
},
getters: {
isLoggedIn(state) {
return state.isAuth
}
},
mutations: {
authenticate(token, expiration) {
localStorage.setItem('token', token)
localStorage.setItem('expiration', expiration)
}
},
actions: {
authenticate: ({
commit
}, token, expiration) => commit('authenticate', token, expiration)
}
})
- login method -
login() {
var data = {
client_id: 2,
client_secret: '**************************',
grant_type: 'password',
username: this.email,
password: this.password
}
// send data
this.$http.post('oauth/token', data)
.then(response => {
// send the parameters to the action
this.$store.dispatch({
type: 'authenticate',
token: response.body.access_token,
expiration: response.body.expires_in + Date.now()
})
})
}
I would be very thankful for any kind of help!

Mutations expect two arguments: state and payload, where the current state of the store is passed by Vuex itself as the first argument and the second argument holds any parameters you need to pass.
The easiest way to pass a number of parameters is to destruct them:
mutations: {
authenticate(state, { token, expiration }) {
localStorage.setItem('token', token);
localStorage.setItem('expiration', expiration);
}
}
Then later on in your actions you can simply
store.commit('authenticate', {
token,
expiration,
});

In simple terms you need to build your payload into a key array
payload = {'key1': 'value1', 'key2': 'value2'}
Then send the payload directly to the action
this.$store.dispatch('yourAction', payload)
No change in your action
yourAction: ({commit}, payload) => {
commit('YOUR_MUTATION', payload )
},
In your mutation call the values with the key
'YOUR_MUTATION' (state, payload ){
state.state1 = payload.key1
state.state2 = payload.key2
},

i think this can be as simple
let as assume that you are going to pass multiple parameters to you action as you read up there actions accept only two parameters context and payload which is your data you want to pass in action so let take an example
Setting up Action
instead of
actions: {
authenticate: ({ commit }, token, expiration) => commit('authenticate', token, expiration)
}
do
actions: {
authenticate: ({ commit }, {token, expiration}) => commit('authenticate', token, expiration)
}
Calling (dispatching) Action
instead of
this.$store.dispatch({
type: 'authenticate',
token: response.body.access_token,
expiration: response.body.expires_in + Date.now()
})
do
this.$store.dispatch('authenticate',{
token: response.body.access_token,
expiration: response.body.expires_in + Date.now()
})
hope this gonna help

Related

Lambda functions do not trigger after cognito events

I'm trying to implement an authentication workflow using AWS Cognito in order to sync my users table (Hasura graphql backend) with Cognito users, but the post confirmation Lambda does not trigger. The code of the Lambda function is as follow:
const axios = require('axios');
exports.handler = async (event,context,callback) => {
console.log(event);
const id=event.request.userAttributes.sub;
const name=event.request.userAttributes.username;
const email=event.request.userAttributes.email;
const hasuraAdminSecret="####"
const graphAPI="####"
const body = {
query: `
mutation insertUsers($userId: String!, $userName: String!, $userEmail: String!) {
insert_users(objects: {cognito_id: $userId, email: $userEmail, username: $userName}) {
affected_rows
}
}
`,
variables: {
userId:id,
userEmail:name,
userName:email
}
}
var response = {};
await axios.post(graphAPI, body, {
headers: {'content-type' : 'application/json', 'x-hasura-admin-secret': hasuraAdminSecret}
})
.catch(err => {
console.error(err.data);
response=err.data;
})
.then(res => {
console.log(res.data);
response = res.data;
})
callback(null,event);
}
The code for the signup and the confirmation pages are as follow:
import { Auth } from 'aws-amplify';
export default {
name:'Signin',
data(){
return {
username: undefined,
email: undefined,
password: undefined,
code: undefined,
user: undefined,
}
},
methods: {
confirm() {
// After retrieveing the confirmation code from the user
Auth.confirmSignUp(this.username, this.code, {
// Optional. Force user confirmation irrespective of existing alias. By default set to True.
forceAliasCreation: false
}).then(this.$router.push("/"))
.catch(err => console.log(err));
},
signup(){
Auth.signUp({
username:this.username,
password: this.password,
attributes: {
email: this.email,
name:this.username
},
validationData: [], // optional
})
.then(data => this.user = data.user)
.catch(err => console.log(err));
}
}
}
When signing up the user is created and confirmed in the AWS console, but the lambda function is not triggered (no logs in Cloudwatch and no errors from Cognito). Where should I look ?
Once the new user signup through aws-cognito you can call lambda functions using trigger
Step 1: Open your aws-cognito User Pools under general setting click on trigger
Step 2: You can customise the workflow with triggers. You can call your lambda function
Pre sign-up
Pre authentication
Custom message
Post authentication
Post confirmation
Define Auth Challenge
Create Auth Challenge
Verify Auth Challenge
User Migration
Pre Token Generation
Step 3: Select your workflow trigger Post confirmation and you can see the list of lambda functions. You have to select the lambda function.
Adding Cloudwatch:
Step 1: You have add role under Configuration under Permissions tab
Step 2: Edit the role and attach policy for CloudWatch and CloudWatch Logs

How can I store the current user id in the Vue store?

I wish to save the UserId when logging in, however, I cannot access the store using this.$store from the store itself, presumably because it is not a Vue component but a JS file.
EDIT: Solution based on an anwer in the code below
export default createStore({
state: {
user: null,
userId: null
},
mutations: {
setUser(state, user) {
state.user = user;
console.log("User set in store");
},
setUserId(state, userId) {
state.userId = userId;
console.log("User ID set in store");
}
},
getters: {
getUser: state => {
console.log('Retrieving user...')
return state.user;
}
},
actions: {
async login(store, credentials) {
let response = await(await fetch("http://localhost:4000/api/login", {
method: "POST",
body: JSON.stringify(credentials),
credentials: "include",
})).json();
store.commit('setUserId', response.userId);
},
},
});```
Your actions have access to the store reference via parameter async login(store, credentials)
So you can call the mutation setUserId from store reference when you get a response
.then(function (response) {
store.commit('setUserId', response.json().id);
})

Send silent request to refresh tokens

I am using Vue.js in frontend and JWT based authentication system. I have refresh tokens and access tokens. Access tokens have short amount of expiration time whereas refresh tokens have way longer. I want to send a request to server to refresh user's access token silently. I know when the access token will be expired. I want to refresh it 1 minute, or something, before it expires. How can I implement this? I thought to do it with putting a counter to my root component but I have no an exact solution. Thanks.
I have a similar problem as you do and found this Vue JWT Auth in the same search that pulled your answer. Implementation was a little more challenging than I had originally anticipated.
My application needs to have the JWT tokens refresh on page reloads and immediately before API calls. To do this I use axios to consume the APIs, allowing the use of an interceptor to check the validity of the tokens. To keep the UX smooth, I use the vuex store to maintain the tokens, escalating to localStorage, and then to making an external request for new tokens if each previous stage was not successful.
The components outside of the store look like:
src/utils/apiAxios.js: used to consume APIs
import axios from 'axios'
import config from '../../config'
import store from '../store'
const apiAxios = axios.create({
baseURL: `${config.dev.apiURL}api/`,
timeout: 1000,
headers: {'Content-Type': 'application/json'}
})
// before any API call make sure that the access token is good
apiAxios.interceptors.request.use(function () {
store.dispatch('isLoggedIn')
})
export default apiAxios
To src/main.js added these lines:
import store from './store'
router.beforeEach((to, from, next) => {
let publicPages = ['/auth/login/', '/auth/register/']
let authRequired = !publicPages.includes(to.path)
let loggedIn = store.dispatch('isLoggedIn')
if (authRequired && !loggedIn) {
return next('/auth/login/')
}
next()
})
src/store/index.js:
import Vue from 'vue'
import Vuex from 'vuex'
import createLogger from 'vuex/dist/logger'
import auth from './modules/auth'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({
modules: {
auth
},
strict: debug,
plugins: debug ? [createLogger()] : []
})
src/store/modules/auth.js:
import axios from 'axios'
import jwtDecode from 'jwt-decode'
import router from '../../utils/router'
import apiAxios from '../../utils/apiAxios'
import config from '../../../config'
export default {
state: {
authStatus: '',
jwt: {
refresh: '',
access: ''
},
endpoints: {
obtainJWT: config.dev.apiURL + 'auth/',
refreshJWT: config.dev.apiURL + 'auth/refresh/',
registerJWT: config.dev.apiURL + 'auth/register/',
revokeJWT: config.dev.apiURL + 'auth/revoke/',
verifyJWT: config.dev.apiURL + 'auth/verify/'
}
},
mutations: {
UPDATE_TOKEN (state, newToken) {
apiAxios.defaults.headers.common['Authorization'] = `Bearer ${newToken.access}`
localStorage.setItem('jwtAccess', newToken.access)
localStorage.setItem('jwtRefresh', newToken.refresh)
state.authStatus = 'success'
state.jwt = newToken
},
UPDATE_STATUS (state, statusUpdate) {
state.authStatus = statusUpdate
},
REVOKE_TOKEN (state) {
delete apiAxios.defaults.headers.common['Authorization']
localStorage.removeItem('jwtAccess')
localStorage.removeItem('jwtRefresh')
state.authStatus = ''
state.jwt = {
refresh: '',
access: ''
}
}
},
getters: {
authStatus: state => state.authStatus,
isLoggedIn: getters => {
// quick check of the state
return getters.authStatus === 'success'
}
},
actions: {
login ({ state, commit }, { email, password }) {
axios({
url: state.endpoints.obtainJWT,
method: 'POST',
data: {
email: email,
password: password
},
headers: {'Content-Type': 'application/json'}
})
.then((response) => {
commit('UPDATE_TOKEN', response.data)
})
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
},
register ({ state, commit }, { email, password, firstName, lastName }) {
axios({
url: state.endpoints.registerJWT,
method: 'POST',
data: {
email: email,
password: password,
first_name: firstName,
last_name: lastName
},
headers: {'Content-Type': 'application/json'}
})
.then((response) => {
commit('UPDATE_TOKEN', response.data)
})
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
},
logout ({ state, commit }) {
let refresh = localStorage.getItem('jwtRefresh')
axios({
url: state.endpoints.revokeJWT,
method: 'POST',
data: { token: refresh },
headers: {'Content-Type': 'application/json'}
})
.then(commit('REVOKE_TOKEN'))
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
},
refreshTokens ({ state, commit }) {
let refresh = localStorage.getItem('jwtRefresh')
axios({
url: state.endpoints.refreshJWT,
method: 'POST',
data: {refresh: refresh},
headers: {'Content-Type': 'application/json'}
})
.then((response) => {
this.commit('UPDATE_TOKEN', response.data)
})
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
},
verifyToken ({ state, commit, dispatch, getters }) {
let refresh = localStorage.getItem('jwtRefresh')
if (refresh) {
axios({
url: state.endpoints.verifyJWT,
method: 'POST',
data: {token: refresh},
headers: {'Content-Type': 'application/json'}
})
.then(() => {
// restore vuex state if it was lost due to a page reload
if (getters.authStatus !== 'success') {
dispatch('refreshTokens')
}
})
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
return true
} else {
// if the token is not valid remove the local data and prompt user to login
commit('REVOKE_TOKEN')
router.push('/auth/login/')
return false
}
},
checkAccessTokenExpiry ({ state, getters, dispatch }) {
// inspect the store access token's expiration
if (getters.isLoggedIn) {
let access = jwtDecode(state.jwt.access)
let nowInSecs = Date.now() / 1000
let isExpiring = (access.exp - nowInSecs) < 30
// if the access token is about to expire
if (isExpiring) {
dispatch('refreshTokens')
}
}
},
refreshAccessToken ({ dispatch }) {
/*
* Check to see if the server thinks the refresh token is valid.
* This method assumes that the page has been refreshed and uses the
* #verifyToken method to reset the vuex state.
*/
if (dispatch('verifyToken')) {
dispatch('checkAccessTokenExpiry')
}
},
isLoggedIn ({ getters, dispatch }) {
/*
* This method reports if the user has active and valid credentials
* It first checks to see if there is a refresh token in local storage
* To minimize calls it checks the store to see if the access token is
* still valid and will refresh it otherwise.
*
* #isLoggedIn is used by the axios interceptor and the router to
* ensure that the tokens in the vuex store and the axios Authentication
* header are valid for page reloads (router) and api calls (interceptor).
*/
let refresh = localStorage.getItem('jwtRefresh')
if (refresh) {
if (getters.isLoggedIn) {
dispatch('checkAccessTokenExpiry')
} else {
dispatch('refreshAccessToken')
}
return getters.isLoggedIn
}
return false
}
}
}
I'm using django for my backend and django-rest-framework-simplejwt for the tokens. The returned JSON is formatted like:
{
access: "[JWT string]",
refresh: "[JWT string]"
}
with a token structure of:
header:
{
"typ": "JWT",
"alg": "HS256"
}
payload:
{
"token_type": "access",
"exp": 1587138279,
"jti": "274eb43bc0da429a825aa30a3fc23672",
"user_id": 1
}
When accessing the refresh endpoint, SimpleJWT requires in the data the refresh token be named refresh; for the verification and the revocation (blacklisting) endpoints the refresh token needs to be named token. Depending on what you are using for your backend will require modification from what I did.
The access token is only used in the api Authentication header and is updated when the mutations are called.
To get the token so I could decode it I used a simple shell script:
#!/usr/bin/env bash
EMAIL="my#email.com"
PASSWORD="aReallyBadPassword"
echo "API Login Token"
JSON_FMT='{"email":"%s","password":"%s"}'
JSON_FMT=` printf "$JSON_FMT" "$EMAIL" "$PASSWORD" `
curl \
--request POST \
--header Content-Type:application/json \
--data $JSON_FMT \
http://localhost:8000/api/auth/
echo ""

How to obtain user from Django JWT token in Vue?

I am trying to retrieve the user from my JWT token which is being served from a Django-Rest API endpoint, /get-token/ and store the user in my Vuex Store.
I don't have an endpoint set up to return the logged in user, and while this could be a potential solution I see no reason to do that instead of retrieving the user from the JWT token.
I've tried retrieving the user directly from the JSON token via this.$store.commit('setAuthUser', response.data.user) and response.data.user.username
Login.Vue
axios.post(this.$store.state.endpoints.obtainJWT, payload)
.then((response) => {
this.$store.commit('updateToken', response.data.token)
this.$store.commit('setAuthUser', response.data.user) -------This line is not working
console.log(response.data)
})
store.js
export default new Vuex.Store({
state: {
authUser: {},
isAuthenticated: false,
jwt: localStorage.getItem('token'),
endpoints: {
obtainJWT: 'http://127.0.0.1:8000/get-token/',
refreshJWT: 'http://127.0.0.1:8000/refresh-token/',
baseUrl: 'http://127.0.0.1:8000/main/api/',
}
},
mutations: {
setAuthUser(state, {
authUser,
isAuthenticated
}) {
Vue.set(state, 'authUser', authUser)
Vue.set(state, 'isAuthenticated', isAuthenticated)
},
updateToken(state, newToken) {
localStorage.setItem('token', newToken);
state.jwt = newToken;
},
removeToken(state) {
localStorage.removeItem('token');
state.jwt = null;
}
}
});
Expected: Properly retrieved user and stored in Vuex with setAuthUser mutator
Actual: Error Cannot destructure property 'authUser' of 'undefined' or 'null'.

Request vuejs axios after auto login completed

How to execute all other request after auto login wass passed. Code example.
axios.get('personal/' + this.$store.state.username + '/config/', {headers: { Authorization: 'Token ' + this.$store.state.idToken }})
Sometimes request that receive user data (username and id) have no time to passed and commit to state, and i receive an error that username is null at state.
I have solve that problem by add in login func to save username and id in localstorage, and after in try auto login i have next code:
tryAutoLogin ({ commit, dispatch }) {
const token = localStorage.getItem('token')
if (!token) {
return
} else {
commit('getToken', {
key: token
})
const userId = localStorage.getItem('userId')
const username = localStorage.getItem('username')
if (!userId || !username) {
dispatch('getUser')
} else {
commit('getUserData', {
id: userId,
username: username.username
})
}
}
Is this way is ok? or there is any way to stop anny request to api, till the dispatch('getUser') will be passed succesfully.
example of getUser code:
getUser ({ commit, state }) {
if (!state.idToken) {
return
}
axios.get('rest-auth/user/', {headers: { Authorization: 'Token ' + state.idToken }})
.then(res => {
localStorage.setItem('username', res.data.username)
localStorage.setItem('userId', res.data.pk)
commit('getUserData', {
id: res.data.pk,
username: res.data.username
})
})
},
Plz, don't be strict i am new in FE vue js:)
First of all, make names of getters, actions, mutations and state more clean and obvious (getUser for getters and setUser for action, for example).
I recommend to create a separated auth module (place all auth logic in this module) and use it in Vuex actions or somewhere in application.
Such module should interact with the Store via Vuex getters, setters and actions (getting and setting current user auth status, for example). It makes authentification more incapsulated and clear.
In this way you'll be able to call this module's methods from any of application component and wait for the result.
In code bellow (http_auth.js) this.$auth is separated authentification module, that can set user state in Vuex and get current status. Also it use localStorage to check for saved token (user data) and tries to authorize with saved token (tryAutoLogin in your case). On fail, it makes redirect to login page.
...
methods: {
async loadInitialData () {
if (await this.$auth.init()) {
axios.get('initial-data-url').then(res => ...)
}
}
},
created () {
this.loadInitialData()
}
...
Auth methods are Promise-based, so you just can wait for resolving or rejecting before.
If you just want to use Vuex-only solution, you should use actions to call API-requests and wrap them in Promises. Also you can dispatch some action inside of other (for example, try login with saved token inside of basic login action).
Sample code (Vuex action):
LOAD_SOME_DATA ({ commit, state, getters }, id) {
return new Promise((resolve, reject) => {
if (!id) {
router.push('/')
return reject('Invalid ID passed.')
}
return axios.get(getters.GET_SOME_URL + id).then(response => {
commit('PUSH_SOME_DATA', response.data)
return store.dispatch('PROCESS_SOME_DATA').then(result => {
return resolve(response)
}, error => {
console.error('Error loading some data: ', error)
return reject(error)
})
}, error => {
router.push('/')
return reject(error)
})
})
}
Above we wrap in promise basic api-call (axios.get(getters.GET_SOME_URL + id)), then process received data (PROCESS_SOME_DATA).
Then we can use it in router, for example (or any other part of app):
store.dispatch('LOAD_SOME_DATA', to.params.id).then(result => ...)