I am working on Angular5 app user authentication, api return token on successful login. For some reasons LoginPageComponent has no idea what is the token, even if I store it in localstorage I will still get null.
What to do?
App Structure:
LoginPageComponent
import { Component, ViewChild } from '#angular/core';
import { NgForm } from '#angular/forms';
import { Router, ActivatedRoute } from "#angular/router";
import { AuthService } from '../../../shared/auth/auth.service';
#Component({
selector: 'app-login-page',
templateUrl: './login-page.component.html',
styleUrls: ['./login-page.component.scss']
})
export class LoginPageComponent {
#ViewChild('f') loginForm: NgForm;
private user: any = {
email: '',
password: ''
};
constructor(
private auth: AuthService,
private router: Router,
private route: ActivatedRoute) { }
// On submit button click
onSubmit(f: NgForm) {
this.auth.signinUser(f.value.email, f.value.password);
// Returns even signinUser has token
console.log(this.auth.getToken());
}
}
AuthService
signinUser(email: string, password: string) {
this.userService.login(email, password).then(res => {
console.log(res);
this.token = res.token;
// here app has token and this.token contains value
});
}
getToken() {
return this.token;
}
UserService:
login(email, password) {
this.endpoint = this.endpointLocal + '/api/auth/login';
return new Promise(resolve => {
this.http.post(this.endpoint, {"email": email, "password": password})
.subscribe(res => resolve(res.json()));
});
}
Your call to signinUser invokes asynchronous code, so the then portion of the Promise in signinUser won't have been executed before you make your console.log call in onSubmit.
If you modify your signinUser method in the AuthService to return the Promise, then you can just chain a .then call in your LoginPageComponent.onSubmit method:
signinUser(email: string, password: string): Promise {
return this.userService.login(email, password).then(res => {
console.log(res);
this.token = res.token;
// here app has token and this.token contains value
});
}
// On submit button click
onSubmit(f: NgForm) {
this.auth.signinUser(f.value.email, f.value.password).then(() => {
console.log(this.auth.getToken());
});
}
Related
I am trying to create a Sveltekit app where users can log in. The login process is handled by a self-created API, so I would like to use the Auth.js Credentials Provider.
When I call the SignIn method as the FormAction in the +page.server.ts file, I get the error message 'window is not defined', which makes sense. Does this mean that Auth.js is not compatible with server-side rendering, or is there something else that I can adjust?
My Code:
//hooks.server.ts
SvelteKitAuth({
providers: [
// #ts-ignore
Credentials({
name: "credentials",
async authorize(credentials, req) {
// TODO: Call Api
const user = { id: "1", name: "J Smith", email: "jsmith#example.com" }
if (user) {
return user
} else {
return null
}
}
})
],
});
//+page.server.ts
export const actions = {
default: async ({ cookies, request }: { cookies: any, request: any }) => {
const data = await request.formData();
const credentials = {
username: data.get('username'),
password: data.get('password')
};
signIn('credentials', credentials)
.then(response => {
console.log('Success');
})
.catch(error => {
console.error('error', error);
});
}
};
I am working on login of a vue 3 app, both the login and registration work fine, but i still need to throw send back a meaningful response to user if login in credentials are rejected by the back-end, i have tried every possible means to log the rejection response from server to console but to no avail, the login is fine when credential is correct, but the console just stay mute when incorrect credential is entered
this is my login.vue
import store from "../store"
import { useRouter } from "vue-router";
import { ref } from "vue";
const router = useRouter()
const user = { email: '', password: '', remember : false }
let errorMsg = ref('');
async function login(ev) {
ev.preventDefault();
await store.dispatch('login', user)
.then(()=> {
router.push({
name: 'Dashboard'
})
})
.catch((err) => {
errorMsg = err.response.data.error
console.log(err)
})
}
and this is my vuex store
import {createStore} from 'vuex'
import axiosClient from "../axios";
const store = createStore({
state: {
user: {
data: {},
token: sessionStorage.getItem('TOKEN')
}
},
getters: {},
setters: {},
actions: {
register({commit}, user) {
return axiosClient.post('/register', user)
.then(({data}) => {
commit('setUser', data);
return data;
})
},
login({commit}, user) {
return axiosClient.post('/login', user)
.then(({data}) => {
commit('setUser', data);
return data;
})
},
},
mutations: {
logout: state => {
state.user.data = {};
state.user.token = null;
},
setUser: (state, userData)=> {
state.user.token = userData.token;
state.user.data = userData.user;
sessionStorage.setItem('TOKEN', userData.token)
}
},
modules: {}
})
export default store;
And here is my axios js file
import axios from "axios";
import store from "./store";
const axiosClient = axios.create({
baseURL: 'http://localhost:8000/api'
})
axiosClient.interceptors.request.use(config=> {
config.headers.Authorization = `Bearer ${store.state.user.token}`
return config;
})
export default axiosClient;
Response from backend as seen from Network Tab
{"error":"The provided credentials are incorrect","0":422}
After checking through my controller in my Laravel project, I discovered that I did not set the status code for the response properly.
Incorrect code:
if (!Auth::attempt($credentials, $remember)) {
return response([
'error'=> 'The provided credentials are incorrect',
422
]);
}
Corrected code:
if (!Auth::attempt($credentials, $remember)) {
return response([
'error'=> 'The provided credentials are incorrect',
], 422);
}
Axios does not treat the response received as a rejection; which needs to get its catch triggered.
Therefore my console.log that I had in my try/catch does not run at all.
I'm very happy we got this solved, big thanks to every one.
I have written a React Native "Auth Portal" component, that links with an existing OAuth portal and handles getting the auth-code from the redirect URI and the subsequent token exchange request. It seems to be working well, but clearly I need to test this assumption, so I am trying to write unit/functional tests. How can I properly do this?
I originally considered extracting the functions used in the two useEffects out into separate, isolated functions and taking, for example, the authCode as an argument instead of from state and mocking this input.
However, I believe a better strategy is to test the component as a whole and just mock the response to the axios post request, comparing that mock to what get's stored in the AsyncStorage, as well as mocking a bad request/response to test the error handling.
Is this a good approach?
import axios from 'axios'
import AsyncStorage from '#react-native-community/async-storage'
import React, { useEffect, useState } from 'react'
import { Linking } from 'react-native'
import InAppBrowser from 'react-native-inappbrowser-reborn'
import { LoadingIndicator } from '../LoadingIndicator'
interface AuthPortalProps {
client_id: string
scopes: string[]
client_secret: string
redirect_uri: string
onAuthComplete: () => void
onError: () => void
}
interface ApiDataResponse {
token_type: string
expires_in: number
access_token: string
refresh_token: string
}
export const AuthPortal = ({
client_id,
scopes,
client_secret,
redirect_uri,
onAuthComplete,
onError,
}: AuthPortalProps) => {
const [authCode, setAuthCode] = useState()
const getAuthCodeFromRedirectUri = async (url: string) => {
if (url.includes('code=')) {
const regex = /[^=]+$/g
const code = url.match(regex)!.toString()
await setAuthCode(code)
}
}
useEffect(() => {
const getAuthCode = async () => {
const url = `https://example.com/auth/?response_type=code&client_id=${client_id}&redirect_uri=${redirect_uri}&scope=${scopes}`
if (!authCode) {
try {
InAppBrowser.openAuth(url, redirect_uri).then(response => {
if (response.type === 'success' && response.url && response.url.includes('code=')) {
getAuthCodeFromRedirectUri(response.url)
Linking.openURL(redirect_uri)
}
})
} catch (error) {
console.log('Error: ', error.message)
onError()
}
}
}
getAuthCode()
return () => {
InAppBrowser.closeAuth()
}
}, [authCode, client_id, onError, redirect_uri, scopes])
useEffect(() => {
const getAuthRefreshToken = async () => {
if (authCode) {
try {
const { data }: { data: ApiDataResponse } = await axios.post(
'https://example.com/auth',
{
grant_type: 'authorization_code',
client_id: `${client_id}`,
code: `${authCode}`,
client_secret: `${client_secret}`,
redirect_uri: `${redirect_uri}`,
}
)
await Promise.all([
AsyncStorage.setItem('access_token', data.access_token),
AsyncStorage.setItem('refresh_token', data.refresh_token),
])
setTimeout(() => {
onAuthComplete()
}, 1000)
} catch (error) {
if (error.response) {
console.log('Error: ', error.response)
} else if (error.request) {
console.log('Error: ', error.request)
} else {
console.log('Error: ', error.message)
}
onError()
}
}
}
getAuthRefreshToken()
}, [authCode, client_id, client_secret, onAuthComplete, onError, redirect_uri])
return <LoadingIndicator />
}
I am trying to pass the name of the user after authentication into a Vue component, but I get a name: undefined value after load.
Here is my AuthService.js:
//config details taken from OAUTH JS doc: https://github.com/andreassolberg/jso
import { JSO, Fetcher } from 'jso';
const client = new JSO({
providerID: '<my-provider>',
default_lifetime: 1800,
client_id: '<my-client-id>',
redirect_uri: 'http://localhost:8080/',
authorization:'<my-auth-server>/oauth/authorize'
//scopes: { request: ['https://www.googleapis.com/auth/userinfo.profile'] }
});
export default {
getProfile() {
// JSO plugin provides a simple wrapper around the fetch API to handle headers
let f = new Fetcher(client);
let url = 'https://www.googleapis.com/auth/userinfo.profile';
f.fetch(url, {})
.then(data => {
return data.json();
})
.then(data => {
return data.user_name;
})
.catch(err => {
console.error('Error from fetcher', err);
});
}
};
Then, in my single file component named MainNav, I have:
import AuthService from "#/AuthService";
export default {
name: "MainNav",
data() {
return {
name: ""
};
},
created() {
this.name = AuthService.getProfile();
}
};
</script>
Anyone have any tips on how I can get the user_name value from the AuthService to my component? I will then need to then display the name in my nav template. Doing a console.log test works fine, just can't return it to my SFC. Also, the JSO library is here: https://github.com/andreassolberg/jso#fetching-data-from-a-oauth-protected-endpoint
Because getProfile returns nothing (undefined). I see you use es6 then you can use async functions
//config details taken from OAUTH JS doc: https://github.com/andreassolberg/jso
import { JSO, Fetcher } from 'jso';
const client = new JSO({
providerID: '<my-provider>',
default_lifetime: 1800,
client_id: '<my-client-id>',
redirect_uri: 'http://localhost:8080/',
authorization:'<my-auth-server>/oauth/authorize'
//scopes: { request: ['https://www.googleapis.com/auth/userinfo.profile'] }
});
export default {
getProfile() {
// JSO plugin provides a simple wrapper around the fetch API to handle headers
let f = new Fetcher(client);
let url = 'https://www.googleapis.com/auth/userinfo.profile';
return f.fetch(url, {}) // return promise here
.then(data => {
return data.json();
})
.then(data => {
return data.user_name;
})
.catch(err => {
console.error('Error from fetcher', err);
});
}
};
And
import AuthService from "#/AuthService";
export default {
name: "MainNav",
data() {
return {
name: ""
};
},
async created() {
try {
this.name = await AuthService.getProfile();
} catch(error) {
// handle
}
}
};
</script>
Or without async (add one more then)
import AuthService from "#/AuthService";
export default {
name: "MainNav",
data() {
return {
name: ""
};
},
created() {
AuthService.getProfile().then((userName) => this.name = userName))
.catch((error) => { /* handle */ })
}
};
</script>
So I'm trying to set up a login page for a Vue application. The login component references the imported loginService.login() function, however, when I test the code I get an error "Cannot read property 'login' of undefined." So I logged the entire loginService to the console and it came back as undefined. Why is it that when I access the imported loginService in webstorm it can access the service just fine, but when I try to use it at runtime it's undefined? Here is the (condensed) login component:
<div class="text-center py-4 mt-3 mb-1">
<button class="btn btn-primary " type="submit" style="width:300px" #click="login">Login</button>
</div>
<script>
import {router} from '../../../main'
import { LoginService } from '../login/loginService';
import { StateStorageService } from '../auth/stateStorageService';
import toastr from 'toastr'
export default {
name:'login',
loginService: new LoginService(),
stateStorageService: StateStorageService,
data: function() {
return {
authenticationError: false,
password: '',
rememberMe: false,
username: '',
credentials: {}
}
},
methods:{
login() {
this.loginService.login({
username: this.username,
password: this.password,
rememberMe: this.rememberMe
}).then(() => {
this.authenticationError = false;
if (router.url === '/register' || (/^\/activate\//.test(router.url)) ||
(/^\/reset\//.test(this.router.url))) {
router.navigate(['']);
}
const redirect = StateStorageService.getUrl();
if (redirect) {
this.stateStorageService.storeUrl(null);
this.router.push('/search');
}
}).catch(() => {
this.authenticationError = true;
});
},
And here is the loginService.js
import { Principal } from '../auth/principalService';
import { AuthServerProvider } from '../auth/authJwtService';
export class LoginService {
constructor() {
this.principal = new Principal();
this.authServerProvider = new AuthServerProvider();
}
login(credentials, callback) {
const cb = callback || function() {};
return new Promise((resolve, reject) => {
this.authServerProvider.login(credentials).subscribe((data) => {
this.principal.identity(true).then((account) => {
resolve(data);
});
return cb();
}, (err) => {
this.logout();
reject(err);
return cb(err);
});
});
}
The this in the context of your login method is the current Vue instance, which isn't the same as the base object being exported in this file. That base object contains all of the info for the constructor for the Vue instance, but the properties being passed don't get directly mapped to the generated Vue instance.
In order to make a property available on this like you want, you should set it on the object returned in the data function, not the base object being exported.
So for your loginService and stateStorageService properties:
data: function() {
return {
loginService: new LoginService(),
stateStorageService: StateStorageService,
authenticationError: false,
password: '',
rememberMe: false,
username: '',
credentials: {}
}
},
But, you also don't need to set the loginService property on the Vue instance to have access to it in your login method. You can simply instantiate a new LoginSevice outside of the scope of your exported object and then reference that in your Vue method:
import { LoginService } from '../login/loginService';
const loginService = new LoginService();
export default {
...
methods: {
login() {
loginService.login({
...
})
},
},
}