page redirection after login - vue.js

below is vue.js2 code when user tries to login with credentials it should redirect to the dashboard view, however it does not get redirect even provided correct credentials:
and says can not rout to same page.
if (username && password) {
const payload = { username, password };
this.$store.dispatch(LOGIN,payload)
.then((resp)=>{
if(resp && resp.data.message==="Welcome" ) {
this.error=false;
console.log(this.$route.query) // here it returns {} object
this.$router.push(this.$route.query.redirect || "/");
}
})
.catch(err => {
this.error=true;
console.log(err);
});
EDIT Action LOGIN
[LOGIN]({commit}, payload) {
// LOGIN user
return loginService.LoginUser(payload).then(res => {
const token = res.data.token;
const user = res.data.userName
localStorage.setItem('user', token)
localStorage.setItem('token',user)
// axios.defaults.headers.common['Authorization'] = token
commit(AUTH_SUCCESS, token,user);
return res;
}).catch(error => {
commit(AUTH_ERROR, error);
localStorage.removeItem('token')
localStorage.setItem('token',user)
});
},
//EDIT..ERROR
host-report-errors.js?44de:6 Unhandled promise rejection NavigationDuplicated {_name: "NavigationDuplicated", name: "NavigationDuplicated", message: "Navigating to current location ("/pages/login") is not allowed", stack: "Error↵ at new NavigationDuplicated (webpack-int…al:///./node_modules/vue/dist/vue.esm.js:1862:57)"}

Add a conditional:
if (
this.$route.path !== '/' &&
this.$route.path !== this.$route.query.redirect
) {
this.$router.push(this.$route.query.redirect || "/")
}
As the error says, you can't route to the same route you're currently on.

Related

SvelteKit + Hooks and MSAL.js using Azure AD B2C results in non_browser_environment

I am new to SvelteKit and i am trying to use MSAL.js with SvelteKit, the issue is i want to implement something similar to an AuthGuard/HttpInterceptor which checks to see if the user is still logged in as they navigate around the SPA or call the external API.
I am using the OAuth 2.0 authorization code flow in Azure Active Directory B2C
within in my auth.ts file i have the following code
let accountId: string = "";
const signIn = () => {
try {
msalInstance.loginRedirect(loginRequestWithApiReadWrite);
} catch (error) {
isAuthenticated.set(false);
console.warn(error);
}
}
// This captures the response from using the redirect flow to login
await msalInstance.handleRedirectPromise()
.then(response => {
if (response) {
if (response.idTokenClaims['tfp'].toUpperCase() === b2cPolicies.names.signUpSignIn.toUpperCase()) {
handleResponse(response);
}
}
})
.catch(error => {
console.log(error);
});
async function handleResponse(response: msal.AuthenticationResult) {
if (response !== null) {
user.set(response);
isAuthenticated.set(true);
setAccount(response.account);
} else {
selectAccount();
}
}
function selectAccount() {
const currentAccounts = msalInstance.getAllAccounts();
if (currentAccounts.length < 1) {
return;
} else if (currentAccounts.length > 1) {
const accounts = currentAccounts.filter(account =>
account.homeAccountId.toUpperCase().includes(b2cPolicies.names.signUpSignIn.toUpperCase())
&&
account.idTokenClaims?.iss?.toUpperCase().includes(b2cPolicies.authorityDomain.toUpperCase())
&&
account.idTokenClaims.aud === msalConfig.auth.clientId
);
if (accounts.length > 1) {
if (accounts.every(account => account.localAccountId === accounts[0].localAccountId)) { console.log("Multiple accounts belonging to the user detected. Selecting the first one.");
setAccount(accounts[0]);
} else {
console.log("Multiple users accounts detected. Logout all to be safe.");
signOut();
};
} else if (accounts.length === 1) {
setAccount(accounts[0]);
}
} else if (currentAccounts.length === 1) {
setAccount(currentAccounts[0]);
}
}
// in case of page refresh
selectAccount();
function setAccount(account: msal.AccountInfo | null) {
if (account) {
accountId = account.homeAccountId;
}
}
const authMethods = {
signIn,
getTokenRedirect
}
In a +page.svelte file i can then import the authMethods no problem, MSAL redirects me to the microsoft sign in page, i get redirected back and can then request an access token and call external API, great all is well.
<script lang='ts'>
import authMethods from '$lib/azure/auth';
<script>
<button on:click={authMethods.signIn}>Sign In</button>
However, the issue i am having is trying to implement this so i can check to see if the user is logged in against Azure B2C using a hook.server.ts file automatically. I would like to check a variable to see if the user is authenticated and if they arnt the hooks.server will redirect them to signUp by calling the authMethod within the hook, and the user will be automatically redirected to the sign in page.
In the hooks.server.ts i have the following code:
export const handle: Handle = (async ({ event, resolve }) => {
if (isAuthenticated === false) {
authRedirect.signIn();
msalInstance.handleRedirectPromise().then((response) => {
if (response) {
console.log('login with redirect succeeded: ', response)
isAuthenticated = true;
}
}).catch((error) => {
console.log('login with redirect failed: ', error)
})
}
const response = await resolve(event);
return response;
}) satisfies Handle;
When i navigate around the SvelteKit SPA, MSAL.js keeps throwing the error below, which i know is because i am running the code from the server flow rather than in the browser, so it was my understanding that if i implement the handleRedirectPromise() in both the auth.ts file and hooks.server.ts this would await the response from the signIn event and so long as i got a response i can then set isAuthenticated to true.
errorCode: 'non_browser_environment',
errorMessage: 'Login and token requests are not supported in non-browser environments.',
subError: ''
Are you required to use the MSAL library? I have got it working with https://authjs.dev/. I was using Active Directory -https://authjs.dev/reference/oauth-providers/azure-ad but there is also a flow for B2C https://authjs.dev/reference/oauth-providers/azure-ad-b2c which I haven't tried.
Then in the hooks.server.js you can do something like the below.
import { sequence } from '#sveltejs/kit/hooks';
import { redirect } from '#sveltejs/kit';
import { SvelteKitAuth } from '#auth/sveltekit';
import AzureADProvider from '#auth/core/providers/azure-ad';
import {
AZURE_AD_CLIENT_ID,
AZURE_AD_CLIENT_SECRET,
AZURE_AD_TENANT_ID
} from '$env/static/private'
const handleAuth = SvelteKitAuth({
providers: [
AzureADProvider({
clientId: AZURE_AD_CLIENT_ID,
clientSecret: AZURE_AD_CLIENT_SECRET,
tenantId: AZURE_AD_TENANT_ID
})
]
});
async function isAuthenticatedUser({ event, resolve }) {
const session = await event.locals.getSession();
if (!session?.user && event.url.pathname !== '/') {
throw redirect(302, '/');
} else if (session?.user && event.url.pathname === '/') {
throw redirect(302, '/dashboard');
}
const response = await resolve(event);
return response;
}
export const handle = sequence(handleAuth, isAuthenticatedUser);

react admin and AuthProvider no redirect after login

I've notice (using some console.log) that the method checkAuth is called some times only before the login methods and not after, so
when the login is accomplished correctly, the token is stored in the browser's local storage and at the end the method login returns the resolved promise,
the checkAuth is not anymore invoked and the page redirect is not performed by the dashboard
If then i change manually the page, it works correctly because the token is in the localstorage and the checkAuth method is able to check it normally
This is my AuthProvider
import axios from "axios";
export default {
// called when the user attempts to log in
login: ({ username, password }) => {
username = encodeURIComponent(username);
password = encodeURIComponent(password);
const tokenUrl = "https://myendpoint/profili/token";
const headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
const config = {
headers
};
const data = `username=${username}&password=${password}&grant_type=password`;
axios.post(tokenUrl,data, config)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response;
})
.then(response => {
localStorage.setItem('token', response.data);
})
.catch((error) => {
throw new Error('Network error');
});
console.log("LOGIN");
return Promise.resolve();
},
// called when the user clicks on the logout button
logout: () => {
localStorage.removeItem('token');
return Promise.resolve();
},
// called when the API returns an error
checkError: ({ status }) => {
if (status === 401 || status === 403) {
console.log("passato in checkError");
localStorage.removeItem('token');
return Promise.reject();
}
return Promise.resolve();
},
// called when the user navigates to a new location, to check for authentication
checkAuth: () => {
console.log("CHECK AUTH");
return localStorage.getItem('token')
? Promise.resolve()
: Promise.reject();
},
// called when the user navigates to a new location, to check for permissions / roles
getPermissions: () => Promise.resolve(),
};
I think you did the wrong returns.
Your login function returns Promise.resolve() right at the moment it emits the axios HTTP POST request, long before it gets the response from the authentication server.
Instead replace the return by of the function by :
return axios.post(tokenUrl,data, config)
.then(response =>
{
if (response.status < 200 || response.status >= 300)
{
throw new Error(response.statusText);
}
return response;
}).then(response =>
{
localStorage.setItem('token', response.data);
})
.catch((error) => {
throw new Error('Network error');
});
This way the Promise resolution will only happens after your authentication server responds.

Migrate ADAL.js to MSAL.js

I have a SPA which uses the solution provided here to authenticate with Azure AD and everything works as expected. Now I want to migrate this to use MSAL.js.
I use below for login:
import * as MSAL from 'msal'
...
const config = {
auth: {
tenantId: '<mytenant>.com',
clientId: '<myclientid>',
redirectUri: <redirecturi>,
},
cache: {
cacheLocation: 'localStorage',
}
};
const tokenRequest = {
scopes: ["User.Read"]
};
export default {
userAgentApplication: null,
/**
* #return {Promise}
*/
initialize() {
let redirectUri = config.auth.redirectUri;
// create UserAgentApplication instance
this.userAgentApplication = new MSAL.UserAgentApplication(
config.auth.clientId,
'',
() => {
// callback for login redirect
},
{
redirectUri
}
);
// return promise
return new Promise((resolve, reject) => {
if (this.userAgentApplication.isCallback(window.location.hash) || window.self !== window.top) {
// redirect to the location specified in the url params.
}
else {
// try pull the user out of local storage
let user = this.userAgentApplication.getUser();
if (user) {
resolve();
}
else {
// no user at all - go sign in.
this.signIn();
}
}
});
},
signIn() {
this.userAgentApplication.loginRedirect(tokenRequest.scopes);
},
And then I use below to get the token:
getCachedToken() {
var token = this.userAgentApplication.acquireTokenSilent(tokenRequest.scopes);
return token;
}
isAuthenticated() {
// getCachedToken will only return a valid, non-expired token.
var user = this.userAgentApplication.getUser();
if (user) {
// get token
this.getCachedToken()
.then(token => {
axios.defaults.headers.common["Authorization"] = "Bearer " + token;
// get current user email
axios
.get('<azureapi-endpoint>' + '/GetCurrentUserEmail')
.then(response => { })
.catch(err => { })
.finally(() => {
});
})
.catch(err => { })
.finally(() => { });
return true;
}
else {
return false;
}
},
}
but after login I get below error:
Access to XMLHttpRequest at 'https://login.windows.net/common/oauth2/authorize?response_type=code+id_token&redirect_uri=<encoded-stuff>' (redirected from '<my-azure-api-endpoint>') from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Also the token that I get seems to be invalid as I get 401 errors trying to call api using the token. Upon checking the token against https://jwt.io/ I get an invalid signature.
I really appreciate anyone's input as I've already spent good few days and haven't got anywhere yet.
I'm not sure if this is your issue. however, for msal.js, in the config, there is no tenantId parameter, it's supposed to be authority. Here is a sample for graph api using msal.js
https://github.com/Azure-Samples/active-directory-javascript-graphapi-v2
specifically: the config is here: https://github.com/Azure-Samples/active-directory-javascript-graphapi-v2/blob/quickstart/JavaScriptSPA/authConfig.js
as per here, https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-js-initializing-client-applications it is supposed to be hitting login.microsoftonline.com not login.windows.net

How to re-post data to the server after a JWT token has been refreshed in Vuejs/Laravel

I'm building a PWA wherein users log in to enter production data to a form and submit to the server for subsequent processing. I'm using a JWT token to manage the user's status. I'm using Axios interceptors to check that the token is fresh/expired. If the latter, I'm refreshing the token.
My current problem is that I don't know how to automatically resubmit a user's data input if, upon form submission, their token was found to be expired and a new one created.
So, in my bootstrap.js file I have:
window.axios.interceptors.response.use((response) => {
return response;
}, error => {
let pathUrl = error.config.url;
if (error.response.status !== 401) {
return new Promise((resolve, reject) => {
reject(error);
});
}
if (pathUrl == '/api/question' || error.response.message == 'Your session has expired; please log in again.') {
getRefreshToken();
return Promise.reject(error)
}
});
function getRefreshToken() {
window.axios.post('/api/auth/refresh')
.then(response => {
const token = response.data.access_token
localStorage.setItem('token', token)
const JWTtoken = 'Bearer ' + token
window.axios.defaults.headers.common['Authorization'] = JWTtoken;
})
}
The method for submitting the form in the component within which the data are inputted is:
submitData () {
let vm = this;
if (vm.$v.formvar.$pending || vm.$v.formvar.$error) return;
axios.post('/api/question',vm.formvar)
.then(res => {
this.$router.push('/' + this.$i18n.locale + res.data.path)
})
},
Any help here would be gratefully received.
Thanks/Tom
You can try using window.axios.request(error.config) to resend the request
if (pathUrl == '/api/question' || error.response.message == 'Your session has expired; please log in again.') {
return getRefreshToken()
.then(JWTtoken => {
error.config.headers['Authorization'] = JWTtoken
return window.axios.request(error.config)
})
}
getRefreshToken should return a Promise
function getRefreshToken() {
return window.axios.post('/api/auth/refresh')
.then(response => {
const token = response.data.access_token
localStorage.setItem('token', token)
const JWTtoken = 'Bearer ' + token
window.axios.defaults.headers.common['Authorization'] = JWTtoken;
return JWTtoken
})
}

How to redirect after http basic auth to same url

I am able to bring a username and password pop up whenever i hit the url but i am able to verify the details with the one's present in database but not redirect it to same handler. It is stucking in else loop. How to do that ? After verifying if the logged in person has the right scope then it will give you response data.
My Server.js -
const simple_validate = function (request, reply, next) {
var credentials = auth(request);
if (!credentials || credentials.name !== 'john' || credentials.pass !== 'secret') {
reply('Not authorized').code(401);
reply().header('WWW-Authenticate', 'Basic realm="example"').hold();
reply('success');
} else {
next();
reply('Access granted');
}
}
server.register(Basic, (err) => {
server.auth.strategy('simple', 'basic', { validateFunc: simple_validate });
});
This is the right way of doing it.
const validate = function (request, email, password, callback) {
// validate your email and password here
User.findUser(email,password,function(err,result){
if(err)
callback(null,false);
else
//do whatever you wanna do
});
}
server.register(require('hapi-auth-basic'), (err) => {
server.auth.strategy('simple', 'basic', {
validateFunc: validateApi
});
});