emberjs component form action - authentication

I'm trying to authenticate against DRF token.
I've successfully been able to login using an auth app I have created.
I thought I'd be slick and make the login form a component.
Since making it a component however, I'm not able to login and I get an Assertion failure.
My templates/components/auth-login.hbs template looks like so ...
<form class='navbar-form navbar-right' {{action 'authenticate' on='submit'}}>
<div class="form-group">
{{input id='identification' placeholder='Username' type='text' class='form-control' value=identification}}
{{input id='password' placeholder='Password' type='password' class='form-control' value=password}}
</div>
<button type="submit">Login</button>
</form>
I also have app/controllers/auth-login.js
import Ember from 'ember';
export default Ember.Controller.extend({
session: Ember.inject.service(),
actions: {
authenticate: function() {
var credentials = this.getProperties('identification', 'password'),
authenticator = 'authenticator:jwt';
this.get('session').authenticate(authenticator, credentials).catch((reason) => {
this.set('errorMessage', reason.error || reason);
});
}
}
});
It works as an app but not as a component.
If I blank the template, and use the auth route/app instead, it works peachy.

Option 1. You need to define action authenticate in actions hash of auth-login component.
Option 2. You can keep identification, password properties and authenticate action in controller. and include the auth-component like below,
app/templates/application.hbs
{{auth-component identification=identification password=password authenticate="authenticate" }}
app/components/auth-component.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
authenticate() {
this.sendAction('authenticate'); //this will call corresonding controller authenticate method through bubbling.
}
}
});
app/controllers/application.js
import Ember from 'ember';
export default Ember.Controller.extend({
session: Ember.inject.service(),
actions: {
authenticate: function() {
var credentials = this.getProperties('identification', 'password'),
authenticator = 'authenticator:jwt';
this.get('session').authenticate(authenticator, credentials).catch((reason) => {
this.set('errorMessage', reason.error || reason);
});
}
}
});

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

Vuex: How to grab latest user data after user profile updated?

I am using Vuex and having trouble getting a user's data to be "reactive" after his profile has been updated. Here's my scenario:
My App.vue checks a user's properties during the created() hook
like so:
async created() {
await this.$store.dispatch('getSSOUser') // gets user from auth server
await this.$store.dispatch('fetchUserProfiles') // queries known user table to see if user has a profile
// set for global user state throughout app
await this.setUser()
// then loads the UI
this.isBusy = false
methods: {
setUser() {
const user = this.getUserProfileBySSOID(this.ssoUser.data.user_id)
this.$store.commit('SET_USER', user)
}
So now I have the user's profile (user object) to use throughout the app. Works good....but...when a user edits his profile in the app (for example, updates his phone number, etc) and clicks submit, I can't seem to get the state to refresh/see that there has been a change unless the user manually refreshes the page.
What is the recommended way to handle this issue? Do I need to run a dispatch to the user state on every route change? The user's profile is located at path: '/userEdit/:uid'
This is my app structure:
<div id="app">
<Banner />
<section class="container-fluid">
<loading-spinner v-if="isBusy"></loading-spinner>
<div v-else>
<AuthName class="text-right" />
<MainNav />
<main id="routerView">
<transition name="component-fade" mode="out-in">
<RouterView :key="$route.fullPath" />
</transition>
</main>
</div>
</section>
</div>
User profile update function:
ApiService.putUserProfile(this.user)
.then(() => {
this.loading = false
this.$router.push('/admin/users')
}
})
.catch(err => {
if (err.response) {
this.errors = err.response.data
} else {
if (err.request) {
this.errors = err.request
} else {
this.errors = err.message
}
}
this.loading = false
console.error('Error from update', err)
})
after you update the detail of the user you can fire an event which may will fetch the data from the server behind the scene.
methods:{
loadData(){
await this.$store.dispatch('getSSOUser') ;
await this.$store.dispatch('fetchUserProfiles');
await this.setUser()
this.isBusy = false
},
updateInfo() {
this.form.put('api/profile').then((response) => {
let userData = response.data.userdata;
Fire.$emit('ProfileEvent');
}
},
},
created(){
this.loadData();
Fire.$on('ProfileEvent', () => {
this.loadData();
});
}
may be firing an event after the there is any changes saved in the profile page and executing the function that is called when the component is created after the fired event may resolve your problem. Hope you will get the idea from above code example.

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);

Cannot read property 'push' of undefined - vue and axios

I have;
An API built from express running on port 2012
An Vue app running on port 8080
The Vue application communicates with the API using Axios.
I have been able to register users and log them in when the user clicks 'register' or 'login' it will submit their data to the API, if the API responses with an OK message, I use this.$router.push('/login') if a user successfully registered and this.$router.push('/dashboard') if a user is successfully logged in from the login page. However I continue to get "cannot read property 'push' of undefined" when I try to call this.$router.push on the dashboard vue.
login.vue (this.$router.push works)
<template>
<form id="login_form" method="post" v-on:submit.prevent="onSubmit">
<input type="text" name="username" class="form-control" v-model="auth.username" placeholder="username" />
<input type="password" name="password" class="form-control" v-model="auth.password" placeholder="password" />
<input type="submit" value="Submit" />
</form>
</template>
<script>
import Vue from 'vue'
import login_axios from '../axios/login_axios.js'
export default{
name: 'login_form',
data:function(){
return{
auth:{
username:'',
password:''
}
}
},
methods:{
onSubmit: login_axios.methods.onSubmit
},
components:{
login_axios
}
}
</script>
This login_vue component imports a javascript file called login_axios.js
login_axios contains a method called onSubmit which is called when the user clicks login/submit. onSubmit checks if res.data.auth.authenticated is true or false, if it is true, it executes this.$router.push to /dashboard, this works. However from the dashboard it does not work.
login_axios.js (this.$router.push works)
import Vue from 'vue'
import axios from 'axios'
import AxiosStorage from 'axios-storage'
let sessionCache = AxiosStorage.getCache('localStorage');
export default {
methods:{
async onSubmit(e){
e.preventDefault();
const res = await axios.post('http://myapi/login', this.auth);
try{
if(res.data.auth.authenticated){
sessionCache.put('authenticated', true);
this.$router.push('/dashboard');
}
} catch (error){
console.log(error);
}
}
}
}
Below is dashboard.vue which imports dashboard_axios.js
dashboard.vue (cannot read property 'push' of undefined)
<template>
<div>
<h1>Dashboard</h1>
Login
Register
Posts
About
</div>
</template>
<script>
import Vue from 'vue'
import dashboard_axios from '../axios/dashboard_axios.js'
export default {
name: 'dashboard',
methods:{
},
components:{
dashboard_axios
}
}
</script>
I have tried a few different things, but I have ended up setting self as a const of this. I defined the function verify_auth in dashboard_axios.js then called it directly after. I would expect this to work as it is just a function which should need called. I may be completely out of the loop as I am no expert at vue, but have been trying to research as much as I can.
dashboard_axios.js (cannot read property 'push' of undefined)
import Vue from 'vue'
import router from 'vue-router'
import axios from 'axios'
import AxiosStorage from 'axios-storage'
const self = this;
let sessionCache = AxiosStorage.getCache('localStorage');
sessionCache.put('authenticated', false);
console.log(sessionCache.get('authenticated'));
function verify_auth(){
if(sessionCache.get('authenticated')){
console.log('successfully verified authentication')
self.$router.push('/')
}else{
console.log('issue verifying authentication')
self.$router.push('/login')
}
}
verify_auth();
export default {
name: 'dashboard_axios',
methods:{
},
data: function() {
},
created: function(){
}
}
I am not 100% sure if this is the answer, but I have found a workaround.
I was importing javascript files such as 'dashboard_axios.js' which did not get loaded in as I wished it would. So instead, I renamed the file to 'dashboard_axios.vue' and added <template></template>, and left it empty, and then wrapped my js code in <script></script> then on the dashboard.vue I called the <dashboard_axios /> tag and it worked as I expected.

VueJS - can't get user token from Django Rest Framework via login POST

I have token authentication setup with DRF and VueJS. However, I can't seem to login using the combo of VueJS, axios, and the DRF token system and I don't understand why. I have CORS installed and if I manually set the token into the VueJS code, all my requests from VueJS to the DRF backend API work fine. It's just the login and getting the initial token that isn't working.
Here's my code:
Django URLs:
from rest_framework.authtoken import views
url(r'^api/token-auth/', views.obtain_auth_token)
Django middleware
class SiteLogin:
def process_request(self, request):
url = request.path.split('/')
# Don't require password for the URL /api/token-auth/
if url[1] == 'api' and url[2] == 'token-auth':
return None
auth Service -- VueJS:
// ... other code ...
login (context, creds, redirect) {
console.log(creds)
axios.post(LOGIN_URL, creds).then((response) => {
localStorage.setItem('token', response.data.token)
this.user.authenticated = true
// If a redirect link is provided
if (redirect) {
router.push(redirect)
}
}).catch((err) => {
console.log(err)
})
},
Login Component - VueJS:
<template>
<div>
<div class='container-fluid container-fullw bg-white'>
<div class='row'>
<div class='col-md-4'>
Login<br>
<br>
<input type='text' class='form-control' placeholder="Username" v-model='credentials.username'><br>
<input type='password' class='form-control' placeholder="Password" v-model='credentials.password'><br>
<button class='btn btn-primary btn-sm' #click='submit()'>Login</button>
</div>
</div>
</div>
</div>
</template>
<script>
import auth from '../../services/auth'
export default {
name: 'login',
data () {
return {
credentials: {
username: '',
password: ''
},
error: ''
}
},
methods: {
submit () {
auth.login(this, this.credentials, '/')
}
}
}
</script>
Upon clicking "Submit" the DRF backend always returns a 401.
Edit:
Also, if I add print(request.POST) into the SiteLogin section of the Django middleware, I can see that a) the if statement returns true (it's acknowledging that it's a login URL) and b) the QueryDict is empty -- I see: <QueryDict: {}>
I solved it over here: https://stackoverflow.com/a/45850775/717682
Problem was that the TokenAuthentication was preventing my axios POST request from being called at all -- immediately returning a 401.