Authenticate users against AWS cognito with Vue3 amplify - amazon-cognito

I am using vue3 and have set up an AWS user pool (amazoncognito.com).
The goal is to authenticate users with a username and password against Cognito and receive the OAuth2 token to authenticate API requests made against the AWS API gateway.
Challenge:
It seems that AWS amplify is not working with the latest vue3 release. It only shows me that the user is not logged in but not displaying any login or logout buttons -regardless of the configuration.
Potential solutions I am interested in (order of preference)
main.js
import { createApp } from 'vue'
import App from './App.vue'
import Amplify from 'aws-amplify';
import Auth from '#aws-amplify/auth';
Amplify.configure({
Auth: {
identityPoolId: 'eu-central-1_REST-OF-ID',
region: 'eu-central-1',
userPoolWebClientId: '4t49u0pu0skkREST-OF-ID',
mandatorySignIn: false,
cookieStorage: {
domain: 'PREFIX.auth.eu-central-1.amazoncognito.com',
path: '/',
expires: 365,
sameSite: "lax",
secure: true
},
authenticationFlowType: 'USER_PASSWORD_AUTH',
oauth: {
domain: 'PREFIX.auth.eu-central-1.amazoncognito.com',
scope: ['phone', 'email', 'profile', 'openid', 'aws.cognito.signin.user.admin'],
redirectSignIn: 'http://localhost:3000/',
redirectSignOut: 'http://localhost:3000/',
responseType: 'code' // or 'token', note that REFRESH token will only be generated when the responseType is code
}
}
});
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
entries: []
};
},
mutations: {
addTime(state, item) {
state.entries.push(item);
}
}
});
createApp(App).use(store, Amplify, Auth).mount("#app");
And inside App.vue
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<AddTime/>
<amplify-authenticator>
<div>
Inside Authenticator
<amplify-sign-in></amplify-sign-in>
<amplify-sign-out></amplify-sign-out>
</div>
</amplify-authenticator>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue';
import AddTime from './components/AddTime.vue';
export default {
name: 'App',
components: {
HelloWorld,
AddTime,
}
}
</script>
Has anyone being able to just use the AWS amplify authentication module with vue3?
How can I access the Cognito OAuth 2.0 authorization server with vue3 and fetch (Authenticating and receiving the token, by posting the credentials a user placed?

Vue3 is now supported by Amplify, but it's still in the early phases. The big change is that you no longer use the ui-vue package. You need to use ui-components:
yarn add aws-amplify #aws-amplify/ui-components
AWS has an auth example for you to use on their website. Make sure you select the Vue3 tabs in the example. I can also confirm that this has worked for me within ionic apps too.

It turns out, vue3 is just not supported even months after the release.
See: https://github.com/aws-amplify/amplify-js/issues/6756

Related

AWS Cognito UI configured using Amplify in quasar/Vue3 doesn't show Facebook login button

I'm using amplify to add auth UIs for AWS Cognito to my quasar/Vue3 website.
I used amplify import auth since I already have Cognito userpool configured sepratly.
Here is my sample App.vue
<template>
<div id="q-app">
<div>
<div v-if="authState !== 'signedin'">You are signed out.</div>
<amplify-authenticator :federated="federatedIds">
<div v-if="authState === 'signedin' && user">
<div>Hello, {{user.username}}</div>
</div>
<amplify-sign-out></amplify-sign-out>
</amplify-authenticator>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from '#vue/composition-api'
import { onAuthUIStateChange } from '#aws-amplify/ui-components'
export default defineComponent({
name: 'App',
created() {
this.unsubscribeAuth = onAuthUIStateChange((authState, authData) => {
this.authState = authState;
this.user = authData;
})
},
data() {
return {
user: undefined,
authState: undefined,
unsubscribeAuth: undefined,
federatedConfig: { provider: "Facebook" },
federatedIds: {
facebookAppId: "*******"
}
}
},
beforeUnmount() {
this.unsubscribeAuth();
}
})
</script>
Here is my boot file:
import Amplify from 'aws-amplify';
import awsconfig from '../aws-exports';
import {
applyPolyfills,
defineCustomElements,
} from '#aws-amplify/ui-components/loader';
applyPolyfills().then(() => {
defineCustomElements(window);
});
Amplify.configure(awsconfig);
I have spent hours looking for a solution, here are a few links
https://www.npmjs.com/package/#aws-amplify/ui-components#vue
https://github.com/aws-amplify/amplify-js/issues/3818
Amplify federated buttons not showing up
In case someone faces similar issue, you need to add .prop for amplify's properties:
<amplify-authenticator :federated="federatedIds"> has to change to <amplify-authenticator :federated.prop="federatedIds">
I had to do the same thing for
<amplify-sign-up slot="sign-up" username-alias="email" :form-fields.prop="formFields">
</amplify-sign-up>
Reference: https://github.com/aws-amplify/amplify-js/issues/5298#issuecomment-621124576

Nuxt / Vue redirect if vuex property is undefind not working

I got a nuxt app running which has an account page. This account page uses mapState computed properties for the user. User data is used in the account page template as well as its child components via props.
Whenever I start the app by going to myurl/account I get "can not read property x of undefined". Its obvious to me, as there is no logged in user when I go right away to /account.
I tried to push the routes back to the /login page within the created() hook of the account page, but its not working. I still get the same error.
How to deal with users trying to access a page before a property used by the template is set? The created hook logs only server side, not in the dev tools of chrome. Shouldnt this.$router.push("login") work?
ACCOUNT PAGE
<template>
<v-container fluid class="px-0 py-0 mt-12">
<v-row>
<accountheader :user="user" :company="company" />
</v-row>
</v-container>
</template>
<script>
import { mapState } from "vuex";
export default {
transitions: "page",
computed: {
...mapState("user", {
company: (state) => state.company,
user: (state) => state.user,
}),
},
head: {
title: "ACCOUNT",
meta: [
{
hid: "description",
name: "description",
content: "account page",
},
],
},
created() {
if (this.user === undefined) {
this.$router.push("/login");
}
},
};
</script>
<style></style>
I managed to get arround this myself by implementing a middleware in the page file itself like so. In case anyone runs into the same issue.
Solution
middleware({ store, redirect }) { // If the user is not authenticated if (store.state.user.user === undefined) { return redirect("/login"); } },

Returning json data from url with Vue.js

I am working on a simple app that returns one value from a json resource at certain url.
Although I've created a vue.config.js file to avoid CORS problem, still getting on execution the message:
Access to fetch at 'https://api.darksky.net/forecast/xxx/37.8267,-122.4233' from origin 'http://localhost:8080' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
What am I missing? Thank you very much!
Location.vue
<template>
<div>
<h1>{{ forecast.timezone }}</h1>
</div>
</template>
<script>
export default {
name: 'Location',
props: {
forecast: Array
}
}
</script>
App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<location v-bind:forecast="forecast" />
</div>
</template>
<script>
import Location from './components/Location.vue'
export default {
name: 'App',
components: {
Location
},
data() {
return {
forecast: []
}
},
mounted() {
this.getTimeZone()
},
methods: {
async getTimeZone() {
try {
const response = await fetch('https://api.darksky.net/forecast/xxx/37.8267,-122.4233')
const data = await response.json()
this.forecast = data
} catch (error) {
console.error(error)
}
}
}
}
</script>
vue.config.js
module.exports = {
devServer: {
proxy: 'https://api.darksky.net/forecast/xxx/'
}
}
You need to setup CORS policy for external API. It's not related to vue.js.
Alternatively, if the external API is a 3rd-party API and you cannot change the CORS policy, then you can consume the external API in your server-side code and create your own API in your server-side code that will return whatever value you get from the external API.
From Vue Cli docs:
WARNING
When devServer.proxy is set to a string, only XHR requests will be proxied. If you want to test an API URL, don't open it in the browser, use an API tool like Postman instead.
So, setting the devServer.proxy property, does not solve the issue, as api requests won't be proxied. For a permanent solution, as Circuit Breaker suggested, you have to allow the CORS request on the api server.

Refresh required to detect authentication state using nuxt auth module

My app is unable to detect the state change that occurs when a user logs in without completely refreshing the page. Upon refreshing everything displays correctly. I am using Nuxt and its included auth module documented here - https://auth.nuxtjs.org/.
Here is the v-if statement that is unable to detect the state change:
<template v-if="$auth.$state.loggedIn">
<nuxt-link
to="/profile"
>
Hello, {{ $auth.$state.user.name }}
</nuxt-link>
</template>
<template v-else>
<nuxt-link
to="/logIn"
>
Sign In
</nuxt-link>
</template>
Here is the login method in my login page.
methods: {
async onLogin() {
try{
this.$auth.loginWith("local", {
data: {
email: this.email,
password: this.password
}
});
this.$router.push("/");
}catch(err){
console.log(err);
}
}
}
I tried fetching the state via a computed property but got the same result. I can see the vuex store data change to indicate I am correctly logged in/out in the 'Application' tab in Chrome Dev Tools but the Vue Dev seems to constantly indicate I'm logged in.. Not sure if its just buggy though..
I also encounter the same problem in reverse when logging out. Here's the method:
async onLogout() {
try{
await this.$auth.logout();
}catch(err){
console.log(err);
}
}
I am happy to provide further details.
In store/index.js add this :
export const getters = {
isAuthenticated(state) {
return state.auth.loggedIn
},
loggedInUser(state) {
return state.auth.user
},
};
In the pages you are suppose to be authenticated
use middle ware auth as : middleware: 'auth'
use import { mapGetters } from 'vuex'
in computed add ...mapGetters(['isAuthenticated', 'loggedInUser']),
you can use loggedInUser to get your user details or check if isAuthenticated
and the logout would work as expected as long as your are importing the map getters in the computed
Sometimes Vue's reactivity system falls short and you just need to manually trigger a re-render and the simplest way to do so is by wrapping your function logic in setTimeout()
setTimeout(async () => {
await this.$auth.logout();
}, 0);

Vuex state on page refresh and multiple tabs

In my app i use firebase API for users authentication
I save the login status as a boolean value in my vuex state
When the user logs in I set the login status to true and using this I hide the login button on the top menu and display the log out button and vice versa when the user logs out.
So i use vuex-persisted state to save the state for page refreshes
The dafault storage in vuex-persisted state is local storage
Instead of saving the state of store on locaal storage i want it to be saved in cookies...so i followed the same apprach as described in the vuex-persisted state documentationn
the problems I am facing are:
when i use the default storage i.e local storage it works but when i use cookies the state is not getting saved in the cookie and persisted state does not work
when i open the app on 2 different tabs and the user logs out in one tab the state is synced in both tabs but log out button is still shown in the other tab
my store
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import * as Cookies from 'js-cookie'
import authStore from './modules/auth'
import statusStore from './modules/allStatus'
Vue.use(Vuex);
export const store = new Vuex.Store({
modules: {
authStore,
statusStore
},
plugins: [
createPersistedState({
getState: (key) => Cookies.getJSON(key),
setState: (key, state) => Cookies.set(key, state, { expires: 3, secure: true })
})
]
});
The author of vuex-persistedstate here.
You've indeed try to set your cookies on a "secure connection". Try to set secure to false should do the trick. Otherwise open an issue on the repository.
I had a similar issue and thought that persisted state cookies were not working. I changed "secure: true" to "secure: false" and it started working as described in the documentation. If you testing the app in a non SSL enabled environment like a localhost nodejs server, try the "secure: false" option.
With bootstrap and vue js that works for me!
<div id="app">
<b-tabs content-class="mt-3" v-model="myIndex" #input="change()">
<b-tab title="Tab 1">
</b-tab>
<b-tab title="Tab 2">
</b-tab>
<b-tab title="Tab 3">
</b-tab>
</b-tabs>
</div>
<script>
let lecture = new Vue({
el: '#app',
data() {
return {
myIndex: 0, // Current tab
}
},
mounted() {
// Get the previous tab from the url
this.myIndex = parseInt(window.location.hash.replace('#',''), 10);
},
methods: {
change () {
// Save the current tab in url
window.location.hash = this.myIndex;
}
}
});
</script>