Not authorized to access mutation in GraphQl - vue.js

I am new to graphql. In my Vuejs application, which uses AWS Amplify, I am trying to save a user to my database after a new user signs up for an account. I took care of the AWS Amplify flow to add this new user to our Cognito pool. I wrote this code to store the user data in our database.
<template>
<div>
A page for testing graphQl queries and mutations
<button #click.prevent="addUser()">Execute query</button>
</div>
</template>
<script>
import { API } from 'aws-amplify'
export const createUser = `
mutation createUser {
createUser(input: {
firstName: "Tina",
email: "Tina#gmail.com"
}) {
id firstName
}
}
`
export default {
name: 'TestGraphQL',
data() {
return {
data: null,
}
},
methods: {
addUser() {
console.log('executing query')
API.graphql({ query: createUser }).then(
(response) => (this.data = response.data.createUser),
).catch(error => console.log('error: ' + error))
},
},
}
</script>
As you can see, for now I have hardcoded some user data in the function just to make sure I can add a user to the database. After this works, I plan to modify the mutation code to allow input variables that contain real user data.
I have run into an issue with authorization. Here is the error message I am seeing:
"Not Authorized to access createUser on type User"
If I log into AWS Appsync > Queries with this user account, I can execute this same mutation and see the same error.
No authorization should be required to add a user to the database. How do I modify my schema or mutation code to not require authorization?
This documentation describes the effect of the #auth decorator. https://docs.amplify.aws/lib/datastore/setup-auth-rules/q/platform/js
But where can I find this file in my repository? Or is this a file that I can edit through the AWS dashboard? I think it is called the 'schema'.

Related

How do I prevent removal of auth0 session cookie on refresh?

I have a simple demo app based on SvelteKit in which I am attempting to use auth0 for authentication. The initial authentication works as expected, and is executed like this (I also have a stores.js file where writable stores are created to hold the state of user, isAutheniticated, and popupOpen, and an auth_config.js file to hold my auth0 credentials.):
// authService.js
import { createAuth0Client } from "#auth0/auth0-spa-js";
import { user, isAuthenticated, popupOpen } from "./store";
import config from "../auth_config";
async function createClient() {
let auth0Client = await createAuth0Client({
domain: config.domain,
clientId: config.clientId
});
return auth0Client;
}
async function loginWithPopup(client, options) {
let c = await client.loginWithPopup(options);
user.set(await client.getUser());
isAuthenticated.set(true);
}
function logout(client) {
return client.logout();
}
//+layout.svelte
<script>
import { onMount } from "svelte";
import auth from "../authService";
import { isAuthenticated, user } from "../store";
let auth0Client;
onMount(async () => {
auth0Client = await auth.createClient();
isAuthenticated.set(await auth0Client.isAuthenticated());
user.set(await auth0Client.getUser());
});
function login() {
auth.loginWithPopup(auth0Client);
}
function logout() {
auth.logout(auth0Client);
}
</script>
{#if $isAuthenticated}
<a class="nav-link" href="/#" on:click="{logout}">Log Out</a>
{:else}
<a class="nav-link" href="/#" on:click="{login}">Log In</a>
{/if}
<slot />
And when I login I see a cookie named auth0.MY_CLIENT_ID.is.authenticated and its value is "true".
However, when I refresh the page, the cookie is removed. When I click the login link, the auth0 popup briefly displays but then closes itself and I am logged in again, and the cookie is set again.
It seems to me I am doing something wrong, since the auth0 client knows I am logged in and does not ask for my credentials again, but my app is not aware I am logged in. What is the proper way to hang on to the session cookie so that it does not appear that I am logged out when I refresh?
Researching some more, I came across this very helpful post from the auth0 message boards.
Allowing 3rd party cookies "solved" my problem, but obviously I don't want my end users to have to do that. Instead, I enabled refresh tokens within my application settings on auth0.com, and then within my client I enabled local storage and refresh tokens:
async function createClient() {
let auth0Client = await createAuth0Client({
domain: config.domain,
clientId: config.clientId,
cacheLocation: 'localstorage',
useRefreshTokens: true
});
return auth0Client;
}
Now my authentication survives a refresh (even with 3rd party cookies blocked).

Nuxt 3 JWT authentication using $fetch and Pinia

I'm discovering Nuxt 3 since a few days and I'm trying to do a JWT authentication to a distinct API.
As #nuxtjs/auth-next doesn't seem to be up to date and as I read it was possible to use the new global method fetch in Nuxt 3 instead of #nuxtjs/axios (not up to date also), I thought it won't be too hard to code the authentication myself! But it stays a mystery to me and I only found documentation on Vue project (using Pinia to keep user logged in) and I'm a bit at a lost.
What I would like to achieve:
a login page with email and password, login request send to API (edit: done!)
get JWT token and user info from API (edit: done!) and store both (to keep user logged even if a page is refresh)
set the JWT token globally to header $fetch requests (?) so I don't have to add it to each request
don't allow access to other pages if user is not logged in
Then I reckon I'll have to tackle the refresh token subject, but one step at a time!
It will be really awesome to have some help on this, I'm not a beginner but neither a senior and authentication stuff still frightens me :D
Here is my login.vue page (I'll have to use Vuetify and vee-validate after that but again one step at a time!)
// pages/login.vue
<script setup lang="ts">
import { useAuthStore } from "~/store/auth";
const authStore = useAuthStore();
interface loginForm {
email: string;
password: string;
}
let loginForm: loginForm = {
email: "",
password: "",
};
function login() {
authStore.login(loginForm);
}
</script>
<template>
<v-container>
<form #submit.prevent="login">
<label>E-mail</label>
<input v-model="loginForm.email" required type="email" />
<label>Password</label>
<input v-model="loginForm.password" required type="password" />
<button type="submit">Login</button>
</form>
</v-container>
</template>
The store/auth.ts for now.
// store/auth.ts
import { defineStore } from 'pinia'
import { encodeURL } from '~~/services/utils/functions'
export const useAuthStore = defineStore({
id: 'auth,
state: () => ({
// TODO Initialize state from local storage to enable user to stay logged in
user: '',
token: '',
})
actions: {
async login(loginForm) {
const URL_ENCODED_FORM = encodeURL({
email: loginForm.email,
password: loginForm.password,
});
return await $fetch('api_route', {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
method: 'POST',
body: URL_ENCODED_FORM
}
}
}
})
i'm gonna share everything, even the parts you marked as done, for completeness sake.
Firstly, you will need something to generate a JWT in the backend, you can do that plainly without any packages, but i would recommend this package for that. Also i'll use objection.js for querying the database, should be easy to understand even if you don't know objection.js
Your login view needs to send a request for the login attempt like this
const token = await $fetch('/api/login', {
method: 'post',
body: {
username: this.username,
password: this.password,
},
});
in my case it requests login.post.ts in /server/api/
import jwt from 'jsonwebtoken';
import { User } from '../models';
export default defineEventHandler(async (event) => {
const body = await useBody(event);
const { id } = await User.query().findOne('username', body.username);
const token: string = await jwt.sign({ id }, 'mysecrettoken');
return token;
});
For the sake of simplicity i didn't query for a password here, this depends on how you generate a user password.
'mysecrettoken' is a token that your users should never get to know, because they could login as everybody else. of course this string can be any string you want, the longer the better.
now your user gets a token as the response, should just be a simple string. i'll write later on how to save this one for future requests.
To make authenticated requests with this token you will need to do requests like this:
$fetch('/api/getauthuser', {
method: 'post',
headers: {
authentication: myJsonWebToken,
},
});
i prefer to add a middleware for accessing the authenticated user in my api endpoints easier. this middleware is named setAuth.ts and is inside the server/middleware folder. it looks like this:
import jwt from 'jsonwebtoken';
export default defineEventHandler(async (event) => {
if (event.req.headers.authentication) {
event.context.auth = { id: await jwt.verify(event.req.headers.authentication, 'mysecrettoken').id };
}
});
What this does is verify that if an authentication header was passed, it checks if the token is valid (with the same secret token you signed the jwt with) and if it is valid, add the userId to the request context for easier endpoint access.
now, in my server/api/getauthuser.ts endpoint in can get the auth user like this
import { User } from '../models';
export default defineEventHandler(async (event) => {
return await User.query().findById(event.context.auth.id)
});
since users can't set the requests context, you can be sure your middleware set this auth.id
you have your basic authentication now.
The token we generated has unlimited lifetime, this might not be a good idea. if this token gets exposed to other people, they have your login indefinitely, explaining further would be out of the scope of this answer tho.
you can save your auth token in the localStorage to access it again on the next pageload. some people consider this a bad practice and prefer cookies to store this. i'll keep it simple and use the localStorage tho.
now for the part that users shouldnt access pages other than login: i set a global middleware in middleware/auth.global.ts (you can also do one that isnt global and specify it for specific pages)
auth.global.ts looks like this:
import { useAuthStore } from '../stores';
export default defineNuxtRouteMiddleware(async (to) => {
const authStore = useAuthStore();
if (to.name !== 'Login' && !localStorage.getItem('auth-token')) {
return navigateTo('/login');
} else if (to.name !== 'Login' && !authStore.user) {
authStore.setAuthUser(await $fetch('/api/getauthuser', {
headers: authHeader,
}));
}
});
I'm using pinia to store the auth user in my authStore, but only if the localstorage has an auth-token (jwt) in it. if it has one and it hasnt been fetched yet, fetch the auth user through the getauthuser endpoint. if it doesnt have an authtoken and the page is not the login page, redirect the user to it
With the help of #Nais_One I managed to do a manual authentication to a third-party API with Nuxt 3 app using client-side rendering (ssr: false, target: 'static' in nuxt.config.ts)
I still have to set the API URL somewhere else and to handle JWT token refresh but the authentication works, as well as getting data from a protected API route with the token in header and redirection when user is not logged.
Here are my finals files:
// pages/login.vue
<script setup lang="ts">
import { useAuthStore } from "~/store/auth";
const authStore = useAuthStore();
const router = useRouter();
interface loginForm {
email: string;
password: string;
}
let loginForm: loginForm = {
email: "",
password: "",
};
/**
* If success: redirect to home page
* Else display alert error
*/
function login() {
authStore
.login(loginForm)
.then((_response) => router.push("/"))
.catch((error) => console.log("API error", error));
}
</script>
<template>
<v-container>
<form #submit.prevent="login">
<label>E-mail</label>
<input v-model="loginForm.email" required type="email" />
<label>Password</label>
<input v-model="loginForm.password" required type="password" />
<button type="submit">Login</button>
</form>
</v-container>
</template>
For the auth store:
// store/auth.ts
import { defineStore } from 'pinia'
const baseUrl = 'API_URL'
export const useAuthStore = defineStore({
id: 'auth',
state: () => ({
/* Initialize state from local storage to enable user to stay logged in */
user: JSON.parse(localStorage.getItem('user')),
token: JSON.parse(localStorage.getItem('token')),
}),
actions: {
async login(loginForm) {
await $fetch(`${baseUrl}/login`, {
method: 'POST',
body: loginForm
})
.then(response => {
/* Update Pinia state */
this.user = response
this.token = this.user.jwt_token
/* Store user in local storage to keep them logged in between page refreshes */
localStorage.setItem('user', JSON.stringify(this.user))
localStorage.setItem('token', JSON.stringify(this.token))
})
.catch(error => { throw error })
},
logout() {
this.user = null
this.token = null
localStorage.removeItem('user')
localStorage.removeItem('token')
}
}
})
I also use the middleware/auth.global.ts proposed by Nais_One.
And this fetch-wrapper exemple I found here as well to avoid having to add token to every requests: https://jasonwatmore.com/post/2022/05/26/vue-3-pinia-jwt-authentication-tutorial-example and it seems to work perfectly. (I just didn't test yet the handleResponse() method).
Hope it can help others :)
That temporary alternative https://www.npmjs.com/package/#nuxtjs-alt/auth is up to date
And that https://www.npmjs.com/package/nuxtjs-custom-auth and https://www.npmjs.com/package/nuxtjs-custom-http work with Nuxt 3 $fetch and no need to use axios
Recently a new package was released that wraps NextAuth for Nuxt3. This means that it already supports many providers out of the box and may be a good alternative to look into.
You can install it via:
npm i -D #sidebase/nuxt-auth
Then it is pretty simple to add to your projects as you only need to include the module:
export default defineNuxtConfig({
modules: ['#sidebase/nuxt-auth'],
})
And configure at least one provider (like this example with Github):
import GithubProvider from 'next-auth/providers/github'
export default defineNuxtConfig({
modules: ['#sidebase/nuxt-auth'],
auth: {
nextAuth: {
options: {
providers: [GithubProvider({ clientId: 'enter-your-client-id-here', clientSecret: 'enter-your-client-secret-here' })]
}
}
}
})
Afterwards you can then get access to all the user data and signin/signup functions!
If you want to have a look at how this package can be used in a "real world" example, look at the demo repo in which it has been fully integrated:
https://github.com/sidebase/nuxt-auth-example
I hope this package may be of help to you and others!
Stumbling on the same issue for a personal project and what I do is declare a composable importing my authStore which is basically a wrapper over $fetch
Still a newb on Nuxt3 and Vue but it seems to work fine on development, still have to try and deploy it though
import { useAuthStore } from "../store/useAuthStore";
export const authFetch = (url: string, opts?: any | undefined | null) => {
const { jwt } = useAuthStore();
return $fetch(url, {
...(opts ? opts : {}),
headers: {
Authorization:`Bearer ${jwt}`,
},
});
};
And then I can just use it in my actions or components
// #/store/myStore.ts
export const useMyStore = defineStore('myStore', () => {
async getSomething() {
...
return authFetch('/api/something')
}
})
// #components/myComponent.vue
...
<script setup lang="ts">
const handleSomething = () => {
...
authFetch('/api/something')
}
</script>
Hope it helps someone !

Keep Vuex state data without vuex-persist

weird question but i don't find an answer anywhere..
I return user data from an API call to Vuex. I save my user object into the Vuex state, along with a Token. (User object and Token are created and send back from Server to Vuex at the same time.)
Everything runs perfect and on the initialization of the component i fetch with a getter the user name etc.
But when i refresh i loose the user object from the state. But, i do not loose the Token. Which is weird cause i create them and return them together.
The question is, how can i keep the user in the state until i logout?
I don't need to keep them in localStorage or inside a cookie cause they are sensitive data (user). I just want to get them through a getter from my store. Which is the correct way to do it.
So vuex-persist is not an option..
Below you see my code:
store.js:
state: {
status: '',
token: localStorage.getItem('token'),
user: {}
},
mutations: {
auth_success(state, { token, user }) {
state.status = 'success';
state.token = token;
state.user = user;
},
actions: {
login({ commit }, user) {
return new Promise((resolve, reject) => {
commit('auth_request');
axios({
url: 'http://localhost:8085/login',
data: user,
method: 'POST'
.then((resp) => {
const token = resp.data.token;
const user = resp.data.user;
axios.defaults.headers.common['Authorization'] = token;
commit('auth_success', { token, user });
})
.catch((err) => {
commit('auth_error');
localStorage.removeItem('token');
reject(err);
});
}
},
getters: {
isLoggedIn(state) {
return state.token;
},
getUser(state){
return state.user;
}
User.vue:
<template>
<v-container>
<v-layout row wrap>
Welcome {{this.user.fullName}}
</v-layout>
</v-container>
</template>
<script>
export default {
data: function() {
return {
user: {}
}
},
mounted() {
this.getUser();
},
methods: {
getUser() {
return (this.user = this.$store.getters.getUser);
}
}
}
</script>
So to sum up:
Token stays in Vuex, user data does not. How to keep them in state without local Storage or cookies?
Any help would be greatly appreciated!
Basically, as Sang Đặng mentioned, if you want to have user data in your vuex (without storing it on the user side) you need to fetch them after every refresh. Refreshing the page means that whole Vue application (and your Vuex state) is removed from the memory (user's browser), which causes that you lose your current store data. token is also removed from the memory, but you load it on your store initialisation:
state: {
token: localStorage.getItem('token'),
...
}
Because of this you are seeing token "kept" in store, while other user data not. There are many ways to fetch user data after refresh - like mentioned beforeRouteEnter. Basically if you want to fetch them on the application load, so you can use Vue.created hook for example. You can also use lazy-loading in your getUser method - if there is no user data - fetch them from your API. Here you can read more about authentication patterns in SPA - for example using OAuth.

Nuxt js <nuxt-link /> authentication

I have a route in nuxt that has to be accessible only by logged in users: /dashboard/secret.
In /dashboard page I have a link like this:
<nuxt-link to="/dashboard/secret">Link to "secret" page</nuxt-link>
When clicked, nuxt will fetch that page from
myapp.com/_nuxt/pages_dashboard_secret.js
How can I add authentication for that nuxt route to stop people from manually going to that url and reading the contents?
Yes the actual secret data will be taken from external api which will validate user token, but still it seems wrong that people can see even the html of this page
if you just want to protect a js file, it would be wrong to do it like this. But if you mean you just want to protect a route from being accessed manually by the users, you must try Nuxt Middlewares and write a middleware for authentication and user fetching.
The middleware structure can be as simple as this:
export default function ({ store, redirect }) {
// If the user is not authenticated
if (!store.state.authenticated) {
return redirect('/login')
}
}
and you can simply use it like this in your root (or secretPage) layout:
<template>
<h1>Secret page</h1>
</template>
<script>
export default {
middleware: 'authenticated'
}
</script>
You can use nuxt/auth package, that is the case for your work and can be used as a plugin and module, you can check has it for the be accessible page or not, it runs automatically and has semantic structure.
You cannot keep your secret on client side (in your JS code) everyone using your application can get it from his browser. You need to keep secret keys on server side and make some validation endpoint to provide if user is valid or not or just keep his session after login.
you can use middleware in nuxt framework. Also, route has a information about url and request. You can make a logic by using them.
https://nuxtjs.org/docs/directory-structure/middleware/
middleware/auth.js
export default async function ({store, from, route, req}) {
if (process.client) {
if (route.name === 'dashboard-room-id' && from.name === route.name)
return
else await store.dispatch('checkSession', route)
}
}
save the token in the store on nuxtServerInit or whenever you get it.
on /dashboard/secret page check in the fetch method if there is a token set.
if token is set, fetch your data otherwise redirect the use somewhere else
https://nuxtjs.org/examples/auth-routes/#redirect-user-if-not-connected
For such a guard of pages, the middleware is the sure way to do it.
Create a middleware file in the middleware directory
Add your middleware logic as described here https://nuxtjs.org/api/pages-middleware/
Then add the middleware option in your page component
as it is mentioned that the routing should be done on the server, in case you just want to handle it if I have this
store/index.js action
async nuxtServerInit({ dispatch, commit }, { req }) {
try {
if (process.server && process.static) { return }
if (!req.headers.cookie) {
console.log('return ')
return
}
const parsed = cookieparser.parse(req.headers.cookie)
const accessTokenCookie = parsed.__session
if (!accessTokenCookie) { return }
const decoded = JWTDecode(accessTokenCookie)
if (userData.exists) {
commit('setState', { name: 'user',
value: {
uid: decoded.user_id,
email: decoded.email,
...userData.data()
} })
}
} catch (e) {
console.log(e)
}
},
//Login firebase
async fireLogin({ dispatch }, { singInWith, account }) {
const resp = await this.$firebase.auth()signInWithEmailAndPassword(account.email, account.password)
const token = await resp.user.getIdToken()
Cookie.set('__session', token)
return { email: resp.user.email, uid: resp.user.uid }
}
Middleware/auth.js
export default function({ store, route, redirect }) {
const user = store.state.user
const blockedRoute = /\/admin\/*/g
const homeRoute = '/'
if (!user && route.path.match(blockedRoute)) {
redirect('/')
}
/*if (user && route.path === homeRoute) {
redirect('/admin')
}*/
}
nuxt.config
router: {
middleware: [
'authenticated'
]
},
you can set the middleware for current page
middle ware
export default context => {
//set Condition and logic
};
route page :
middleware: 'name of middle ware'
i can suggest three solutions:
1.Get pathname in your js codes and then check the url that client using to access your page , for example if pathname is
/dashboard/secret and user is logged in then show the page
for checking pathname u can use these cods:
$nuxt.$route.path
//or good old pure js ;)
window.location.pathname
2.check if user truly logged in (backend & frontend)
for that u can use nuxt-auth and sync it to your backend as well.
for example if you using laravel , u can use laravel passport ,
in that case when the request sended to the backend route, you can check if user is logged in to the backend as well.
Ps:This way is more secure and of course in every backend language this process can be different, but surely all of them will have the same capability.
3.using .htaccess :
Do not allow the user to view the file directly from the server path
Read more

Vuex store module state access in component

I am new in this world of Vue and Vuex, but I want to know if you can help me with this problem that I have right know, and actually don't really know why.
Note: I am using an api developed in laravel.
The scenario is this:
I have a common component that I have created which displays a list (data table) of users.
users/component/Users.vue
<template>
....
</template>
<script>
import { mapActions } from 'vuex'
import { mapGetters } from 'vuex'
export default {
data () {
return {
items: [],
user: {}
}
},
methods: {
...mapActions({
getUsers: 'users/getUsers'
})
},
mounted () {
***this.user = {
...mapGetters({
user: 'auth/user'
})
}
this.user = {
...mapGetters({
user: 'auth/user'
})
}
var routeName = this.$route.name // users or admins
this.getUsers(route, user)
this.items = this.$store.state.users
}
}
</script>
Right now my application will have three roles: regular users, administrator and super administrators.
A super administrator will have two modules for display users:
List of all regular users.
List of all administrators.
This two modules would be in different routes, which is why I reused the common component that displays a list of users.
So, I have in Vuex right now two modules, the first one is auth module that in its state file will contain the information of the user that is logged in: name, email, role. And the second module is users module that in its state will contain the list of users passed via api depending on the route I am entering and the user's role that this one has.
The users module has three actions, the first one is getRegularUsers(), getAdmins() and getUsers(routeName, role). The last action is invoked in users component that received the route (url the user is currently in) and the user's role.
The definition of getUsers() is:
users/store/actions.js
...
export const getUsers = (routeName, role) => {
if (routeName == 'admins') {
if (role === 'SuperAdmin')
this.getAdmins()
}
else
this.getRegularUsers();
}
....
As you can see, this action is considering the route and the role of the current user that is logged in and depending on the route or role, this action invoked the other actions that I created. This two other actions fetch via commit (mutation) the users state, that is an array of user objects:
users/store/state.js
export default {
users = []
}
The problem:
Looking at the chrome console, this is the error:
The undefined or null error, means that this.user.role is udenfined
And If I look at vuex-devtools you will see that user has the role assigned, even in getters and state: User role - getter and state
The question:
I have done anything in actions, such as this.$store.getters.user.role and nothing happens.
So, hope you can help me with this. I think I have explained carefully the situation I am having.
Thanks for your support.
Get the users of the role:
var routeName = this.$route.name // users or admins
this.getUsers(route, this.user.role)
set mapGetters in computed object:
computed: {
...mapGetters({
user: 'auth/user'
})
}
and use vuex getters for this line
this.items = this.$store.state.users