Password reset and sign up in vue js 3 - vue.js

I am trying to connect my vue js application to a msal b2c backend the login is done but now i need to make a button for password reset and sign up i cant find a sign up popup anywhere and i dont know how to set up a password reset popup.
When i click the password reset link in the login popup the popup closes and nothing happens after that
The way i currently have it set up is:
LoginView.vue
<template>
<div class="grid grid-cols-12">
<div class="default-container">
<p>u bent nog niet ingelogd log nu in</p>
<button type="button" #click="login">Login</button>
</div>
</div>
</template>
<script setup>
import useAuthStore from '../stores/AuthStore';
function login() {
useAuthStore().login();
}
</script>
Authstore.js
import { defineStore } from 'pinia';
import AuthService from '../services/AuthService';
const clientId = import.meta.env.VITE_APP_CLIENT_ID;
const authority = import.meta.env.VITE_APP_AUTHORITY;
const scopes = [import.meta.env.VITE_APP_SCOPE_READ];
const authService = new AuthService(clientId, authority, scopes);
const useAuthStore = defineStore('AuthStore', {
state: () => ({
isAuthenticated: !!localStorage.getItem('apiToken'),
apiToken: localStorage.getItem('apiToken'),
user: JSON.parse(localStorage.getItem('userDetails')),
error: null,
}),
getters: {
isLoggedIn: (state) => state.isAuthenticated,
currentApiToken: (state) => state.apiToken,
currentUser: (state) => state.user,
currentError: (state) => state.error,
},
actions: {
async login() {
try {
const token = await authService.login();
this.apiToken = token.apiToken;
localStorage.setItem('apiToken', token.apiToken);
this.isAuthenticated = true;
fetch(`${import.meta.env.VITE_APP_API_URL}/account/current`, {
headers: {
Authorization: `Bearer ${token.apiToken}`,
},
})
.then((response) => response.json())
.then((data) => {
localStorage.setItem('userDetails', JSON.stringify(data));
this.user = data;
});
} catch (error) {
this.error = error;
}
},
async logout() {
try {
await authService.logout();
this.user = null;
this.apiToken = null;
this.isAuthenticated = false;
localStorage.removeItem('apiToken');
localStorage.removeItem('userDetails');
} catch (error) {
this.error = error;
}
},
},
});
export default useAuthStore;
AuthService.js
import * as Msal from 'msal';
export default class AuthService {
constructor(clientId, authority, scopes) {
this.app = new Msal.UserAgentApplication({
auth: {
clientId,
authority,
postLogoutRedirectUri: window.location.origin,
redirectUri: window.location.origin,
validateAuthority: false,
},
cache: {
cacheLocation: 'localStorage',
},
});
this.scopes = scopes;
}
async login() {
const loginRequest = {
scopes: this.scopes,
prompt: 'select_account',
};
const accessTokenRequest = {
scopes: this.scopes,
};
let token = {};
try {
await this.app.loginPopup(loginRequest);
} catch (error) {
return undefined;
}
try {
const acquireTokenSilent = await this.app.acquireTokenSilent(accessTokenRequest);
token = {
apiToken: acquireTokenSilent.accessToken,
expiresOn: acquireTokenSilent.expiresOn,
};
} catch (error) {
try {
const acquireTokenPopup = await this.app.acquireTokenPopup(accessTokenRequest);
token = {
apiToken: acquireTokenPopup.accessToken,
expiresOn: acquireTokenPopup.expiresOn,
};
} catch (errorPopup) {
return undefined;
}
}
return token;
}
logout() {
this.app.logout();
}
}

Related

Button takes 2 clicks before store update Vue and Pinia

I have been messing around with Vue and trying to learn it. On the first click of the button in LoginForm.vue token and user_data are both null. On the second click it finally gets updated. How can I get the live reactive state of the variables?
I am new to Vue so if there are better common practices please let me know.
store/login.js
import { defineStore } from 'pinia'
import axios from 'axios'
export const useUsers = defineStore('users', {
state: () => ({
token: null,
user_data: null
}),
actions: {
async loginUser(data) {
try {
let response = await axios.post("users/login", data)
// Object.assign(this.token, response.data.token)
this.token = response.data.token
axios.defaults.headers.common['user-token'] = this.token
} catch (error) {
return error
}
},
async logout() {
// Object.assign(this.token, null)
// Object.assign(this.user_data, null)
this.token = null
this.user_data = null
// localStorage.removeItem('user');
delete axios.defaults.headers.common['user-token']
},
async login_and_get_user_data(data) {
axios.post("users/login", data).then(response => {
this.token = response.data.token
axios.defaults.headers.common['user-token'] = this.token
axios.get("users/user").then(response2 => {
this.user_data = response2.data.user
})
})
},
async get_user_data() {
console.log(JSON.parse(localStorage.getItem('user'))['token'])
axios.defaults.headers.common['user-token'] = JSON.parse(localStorage.getItem('user'))['token']
let response = await axios.get("users/user")
// Object.assign(this.user_data, response.data.user)
this.user_data = response.data.user
}
}
})
components/LoginForm.vue
<script>
import { useUsers } from '#/stores/login'
import { mapActions } from 'pinia'
import { storeToRefs } from 'pinia'
import { isProxy, toRaw } from 'vue';
export default {
setup() {
const store = useUsers()
store.$subscribe((mutation, state) => {
localStorage.setItem('user', JSON.stringify(state))
})
},
data() {
return {
email: "",
password: ""
}
},
methods: {
...mapActions(useUsers, ['loginUser']),
...mapActions(useUsers, ['get_user_data']),
...mapActions(useUsers, ['logout']),
on_click() {
var data = new FormData();
data.append('email', this.email);
data.append('password', this.password);
const store = useUsers()
this.loginUser(data)
this.get_user_data()
const { token } = storeToRefs(store)
const { user_data } = storeToRefs(store)
console.log(token.value)
console.log(toRaw(user_data.value))
},
logout_click() {
this.logout().then(
console.log(JSON.parse(localStorage.getItem('user')))
)
}
}
}
</script>
<template>
<input type="email" v-model="email" placeholder="youremail#mail.com">
<br>
<input type="password" v-model="password">
<br>
<button #click="on_click">Submit</button>
<br>
<button #click="logout_click">Logout</button>
</template>
Your method on_click is calling async methods like loginUser or get_user_data without waiting them to be finished.
So by the time your are logging console.log(token.value) your http request is probably not finished yet and your token is still null.
You need to await the methods that are doing those requests.
async on_click() {
var data = new FormData();
data.append('email', this.email);
data.append('password', this.password);
const store = useUsers()
await this.loginUser(data)
await this.get_user_data()
const { token } = storeToRefs(store)
const { user_data } = storeToRefs(store)
console.log(token.value)
console.log(toRaw(user_data.value))
},
Keep in mind that you will probably need to display a loader to give the user a feedback because the on_click is now asynchronous and take a bit more time

NextAuth authentication not working on Deployment on Vercel (Working on localhost)

Used credentials for authentication with nextauth. My code is not working on vercel deployment but working on localhost.
I used the basic cresdentials for authentication username and password but not able to get the session object after signing up from SignUpPage from the pages folder
[...nextauth].js
export default NextAuth({
session: {
jwt: true,
},
providers: [
Credentials({
async authorize(credentials) {
const client = await MongoClient.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const usersCollection = client.db().collection("users");
const user = await usersCollection.findOne({
username: credentials.username,
});
if (!user) {
client.close();
throw new Error("No user found!");
}
const isValid = await compare(credentials.password, user.password);
if (!isValid) {
client.close();
throw new Error("Could not log you in!");
}
client.close();
return { username: user.username };
},
}),
],
callbacks: {
jwt: async ({ token, user }) => {
if (user) {
token.user = user;
}
return token;
},
session: async ({ session, token }) => {
if (token) {
session.user = token.user;
}
return session;
},
},
secret: process.env.SECRET,
jwt: {
secret: process.env.SECRET,
encryption: true,
},
});
pages/api/auth/signup.js
async function handler(req, res) {
if (req.method !== "POST") {
return;
}
const data = req.body;
const { username, password } = data;
if (!username || !password || password.trim().length < 7) {
res.status(422).json({
message:
"Invalid input - password should also be at least 7 characters long.",
});
return;
}
const client = await MongoClient.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const db = client.db();
const existingUser = await db
.collection("users")
.findOne({ username: username });
if (existingUser) {
res.status(422).json({ message: "User exists already!" });
client.close();
return;
}
const hashedPassword = await hash(password, 12);
const result = await db.collection("users").insertOne({
username: username,
password: hashedPassword,
});
res.status(201).json({ message: "Created user!" });
client.close();
}
export default handler;
pages/SignUpPage.js
useEffect(() => {
getSession().then((session) => {
if (session) {
router.push("/");
} else {
setIsLoading(false);
}
});
}, [router]);
if (isLoading) {
return <p>Loading...</p>;
}
const createUser = async (username, password) => {
const response = await fetch("/api/auth/signup", {
method: "POST",
body: JSON.stringify({ username, password }),
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || "Something went wrong!");
}
return data;
};
const submitHandle = async (username, password) => {
if (login) {
const result = await signIn("credentials", {
redirect: false,
username: username,
password: password,
});
if (!result.error) {
router.push("/");
}
} else {
try {
const result = await createUser(username, password);
console.log("result", result);
router.push("/");
} catch (error) {
console.log(error);
}
}
};
Looks like you have used environment variables. Make sure you have updated them with relevant values.
for example,
NEXTAUTH_URL=http://localhost:3000
to
NEXTAUTH_URL=https://youdomain.com

Vue API data is gone on window refresh

When I login I am redirected to secret page which needs JWT authentication. Data is loaded on secret page. And when I refresh the window - data is lost. How can I fix it?
I use eventBus to send a JWT token to sibling template.
Login view method on submit:
submitSignin() {
console.log("submit!");
this.submitted = true;
this.$v.$touch();
if (this.$v.$invalid) {
return; // stop here if form is invalid
}
axios
.post("http://localhost:3000/auth/login", this.authData)
.then((res) => {
this.token = res.data.token;
this.authData.email = "";
this.authData.password = "";
this.$v.$reset();
this.successMsg = "You Sign in Successfully!";
this.$router.push({ path: "/auth/all-users" });
this.$nextTick(() => {
eventBus.$emit("sendtoken", this.token);
});
})
.catch((err) => {
console.log(err.response.data.message);
this.errorMsg = err.response.data.message;
});
},
SecretPage view:
<script>
export default {
name: "SecretPage",
data() {
return {
users: [],
};
},
methods: {
loadUsers() {
let self = this;
eventBus.$on("sendtoken", (token) => {
axios
.get("http://localhost:3000/auth/all-users", {
headers: {
Authorization: `Bearer ${token}`,
},
})
.then((response) => {
console.log(token);
console.log(response.data.users);
self.users = response.data.users;
})
.catch((err) => {
console.log(err);
});
});
},
},
mounted() {
this.loadUsers();
},
};
</script>
loaded users

vue axios.get to fetch images from imgur

While trying to fetch all images from the Galleries template I am receiving the following error/warn notification: "Property "token" was accessed during render but is not defined on instance". instead of displaying the images I receive the following: <img src="function link() { [native code] }"> What am I missing?
Galleries.vue
<template>
<div>
<img v-for="image in allImages" :src="image.link" :key="image.id" />
</div>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
export default {
name: "Galleries",
computed: mapGetters(["allImages"]),
methods: mapActions(["fetchImages"]),
created() {
this.fetchImages();
}
};
</script>
imgur.js
import qs from 'qs';
import axios from 'axios';
const CLIENT_ID = 'e28971925a8d43c';
const ROOT_URL = 'https://api.imgur.com';
export default {
login() {
const querystring = {
client_id: CLIENT_ID,
response_type: 'token',
};
window.location = `${ROOT_URL}/oauth2/authorize?${qs.stringify(querystring)}`
},
fetchImages(token) {
return axios.get(`${ROOT_URL}/3/account/me/image/`, {
headers: {
Authorization: `Bearer ${token}`
}
});
},
uploadImages(images, token) {
const promises = Array.from(images).map(image => {
const formData = new FormData();
formData.append('image', image);
return axios.post(`${ROOT_URL}/3/image`, formData, {
headers: {
Authorization: `Bearer ${token}`
}
});
});
return Promise.all(promises);
}
};
images.js
import api from '../../api/imgur';
import { router } from '../../main';
const state = {
images: []
};
const getters = {
allImages: state => state.images
};
const mutations = {
setImages: (state, images) => {
state.images = images;
}
};
const actions = {
async fetchImages({ rootState, commit }) {
const { token } = rootState.auth;
const response = await api.fetchImages(token);
commit('setImages', response.data.data);
},
async uploadImages({ rootState }, images) {
// Get the access token
const { token } = rootState.auth;
// Call our API module to do the upload
await api.uploadImages(images, token);
// Redirect use to the gallery page
router.push('/galleries');
}
};
export default {
state,
getters,
mutations,
actions
}
auth.js
import api from '../../api/imgur';
import qs from 'qs';
import { router } from '../../main';
const state = {
token: window.localStorage.getItem('imgur_token')
};
const getters = {
isLoggedIn: state => !!state.token // turn a value into boolean
};
const actions = {
login: () => {
api.login();
},
finalizeLogin({ commit }, hash) {
const query = qs.parse(hash.replace('#', ''));
commit('setToken', query.access_token);
window.localStorage.setItem('imgur_token', query.access_token);
router.push('/');
},
logout: ({ commit }) => {
commit('setToken', null);
window.localStorage.removeItem('imgur_token');
router.push('/');
}
};
const mutations = {
setToken: (state, token) => {
state.token = token;
}
};
export default {
state,
getters,
actions,
mutations
};
// Galaries.vue
// You didn't pass token when calling function `fetchImages`.
created() {
this.fetchImages(); // missing token here
}
I recommend use token as environment variable for security reason. Never public your token. Should NOT pass it as an argument of a function. Store your token in a .env file. You can rewrite your fetchImages as below.
fetchImages(token) {
return axios.get(`${ROOT_URL}/3/account/me/image/`, {
headers: {
Authorization: `Bearer ${process.env.TOKEN}`
}
});
},

NextJs/ Apollo Client/ NextAuth issue setting authorization Bearer Token to headers correctly

I cannot correctly set my jwt token from my cookie to my Headers for an authenticaed gql request using apollo client.
I believe the problem is on my withApollo.js file, the one that wraps the App component on _app.js. The format of this file is based off of the wes bos advanced react nextjs graphql course. What happens is that nextauth saves the JWT as a cookie, and I can then grab the JWT from that cookie using a custom regex function. Then I try to set this token value to the authorization bearer header. The problem is that on the first load of a page with a gql query needing a jwt token, I get the error "Cannot read property 'cookie' of undefined". But, if I hit browser refresh, then suddenly it works and the token was successfully set to the header.
Some research led me to adding a setcontext link and so that's where I try to perform this operation. I tried to async await setting the token value but that doesn't seem to have helped. It just seems like the headers don't want to get set until on the refresh.
lib/withData.js
import { ApolloClient, ApolloLink, InMemoryCache } from '#apollo/client';
import { onError } from '#apollo/link-error';
import { getDataFromTree } from '#apollo/react-ssr';
import { createUploadLink } from 'apollo-upload-client';
import withApollo from 'next-with-apollo';
import { setContext } from 'apollo-link-context';
import { endpoint, prodEndpoint } from '../config';
import paginationField from './paginationField';
const getCookieValue = (name, cookie) =>
cookie.match(`(^|;)\\s*${name}\\s*=\\s*([^;]+)`)?.pop() || '';
let token;
function createClient(props) {
const { initialState, headers, ctx } = props;
console.log({ headers });
// console.log({ ctx });
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError)
console.log(
`[Network error]: ${networkError}. Backend is unreachable. Is it running?`
);
}),
setContext(async (request, previousContext) => {
token = await getCookieValue('token', headers.cookie);
return {
headers: {
authorization: token ? `Bearer ${token}` : '',
},
};
}),
createUploadLink({
uri: process.env.NODE_ENV === 'development' ? endpoint : prodEndpoint,
fetchOptions: {
credentials: 'include',
},
headers,
}),
]),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
// TODO: We will add this together!
// allProducts: paginationField(),
},
},
},
}).restore(initialState || {}),
});
}
export default withApollo(createClient, { getDataFromTree });
page/_app.js
import { ApolloProvider } from '#apollo/client';
import NProgress from 'nprogress';
import Router from 'next/router';
import { Provider, getSession } from 'next-auth/client';
import { CookiesProvider } from 'react-cookie';
import nookies, { parseCookies } from 'nookies';
import Page from '../components/Page';
import '../components/styles/nprogress.css';
import withData from '../lib/withData';
Router.events.on('routeChangeStart', () => NProgress.start());
Router.events.on('routeChangeComplete', () => NProgress.done());
Router.events.on('routeChangeError', () => NProgress.done());
function MyApp({ Component, pageProps, apollo, user }) {
return (
<Provider session={pageProps.session}>
<ApolloProvider client={apollo}>
<Page>
<Component {...pageProps} {...user} />
</Page>
</ApolloProvider>
</Provider>
);
}
MyApp.getInitialProps = async function ({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
pageProps.query = ctx.query;
const user = {};
const { req } = ctx;
const session = await getSession({ req });
if (session) {
user.email = session.user.email;
user.id = session.user.id;
user.isUser = !!session;
// Set
nookies.set(ctx, 'token', session.accessToken, {
maxAge: 30 * 24 * 60 * 60,
path: '/',
});
}
return {
pageProps,
user: user || null,
};
};
export default withData(MyApp);
api/auth/[...nextAuth.js]
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import axios from 'axios';
const providers = [
Providers.Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
Providers.Credentials({
name: 'Credentials',
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' },
},
authorize: async (credentials) => {
const user = await axios
.post('http://localhost:1337/auth/local', {
identifier: credentials.username,
password: credentials.password,
})
.then((res) => {
res.data.user.token = res.data.jwt;
return res.data.user;
}) // define user as res.data.user (will be referenced in callbacks)
.catch((error) => {
console.log('An error occurred:', error);
});
if (user) {
return user;
}
return null;
},
}),
];
const callbacks = {
// Getting the JWT token from API response
async jwt(token, user, account, profile, isNewUser) {
// WRITE TO TOKEN (from above sources)
if (user) {
const provider = account.provider || user.provider || null;
let response;
let data;
switch (provider) {
case 'google':
response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/auth/google/callback?access_token=${account?.accessToken}`
);
data = await response.json();
if (data) {
token.accessToken = data.jwt;
token.id = data.user._id;
} else {
console.log('ERROR No data');
}
break;
case 'local':
response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/auth/local/callback?access_token=${account?.accessToken}`
);
data = await response.json();
token.accessToken = user.token;
token.id = user.id;
break;
default:
console.log(`ERROR: Provider value is ${provider}`);
break;
}
}
return token;
},
async session(session, token) {
// WRITE TO SESSION (from token)
// console.log(token);
session.accessToken = token.accessToken;
session.user.id = token.id;
return session;
},
redirect: async (url, baseUrl) => baseUrl,
};
const sessionPreferences = {
session: {
jwt: true,
},
};
const options = {
providers,
callbacks,
sessionPreferences,
};
export default (req, res) => NextAuth(req, res, options);