I have a SPA built with the Quasar Framework (based on Vue.js). The SPA is registered in Auth0 and uses the auth0-spa-js library to handle the login via Auth Code Flow. While the login works and I get a token, when I reload the page the Auth Code Flow is started again and the user is redirected to the /authorize endpoint to get a new code, which is then again exchanged for a new token.
To me this does not seem like the correct behaviour. I would have expected that the Auth0 library caches/stores the token in the browser and on page reload checks if there is a valid token already, instead of restarting the Auth Code Flow every time.
Or is that actually the way it should be considering this is a SPA and token storage in the browser is not good.
The code from the boot file:
import createAuth0Client from '#auth0/auth0-spa-js';
import axios from 'axios'
export default async ({ app, router, Vue }) => {
let auth0 = await createAuth0Client({
domain: '{domain}.auth0.com',
client_id: '{client_id}',
audience: '{audience}'
});
const isAuthenticated = await auth0.isAuthenticated();
if (isAuthenticated) {
// show the gated content
await afterLogin(auth0, Vue)
return;
}
const query = window.location.search;
if (query.includes("code=") && query.includes("state=")) {
// Process the login state
await auth0.handleRedirectCallback()
await afterLogin(auth0, Vue)
// Use replaceState to redirect the user away and remove the querystring parameters
window.history.replaceState({}, document.title, "/");
return
}
await auth0.loginWithRedirect({
redirect_uri: window.location.origin
});
}
async function afterLogin(auth0, Vue) {
let user = await auth0.getUser()
Vue.prototype.$user = user
Vue.prototype.$auth = auth0
// let claims = await auth0.getIdTokenClaims()
// console.log(claims)
// setAuthHeader(claims.__raw)
let token = await auth0.getTokenSilently()
setAuthHeader(token)
}
function setAuthHeader(token) {
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token
}
What am I missing?
When refreshing you should check if user exists..
const user = await auth0.getUser();
You could create an autoCheckUser function at src/boot folder, that can check if user exist every time app created...
The feedback from Auth0 is that this is the expected behaviour.
"If the token is stored in memory, then it will be erased when the page is refreshed. A new token is silently requested via a cookie session."
Here is the link to the original answer
Related
The auth0 documentation on their credentials manager states
The credentials manager is an easy to use source of Keychain-based
authentication for iOS and Android, and should be usable with
auth.credentialsManager
When trying to use this suggested method
const isLoggedIn = await auth0.credentialsManager.hasValidCredentials();
This error is being thrown
undefined is not an object (evaluating '_$$_REQUIRE(_dependencyMap[10],
"../context/actions/authActions").auth0.credentialsManager.getCredentials')
Here's an overview of our auth0 configuration, and how it works currently
in AuthActions.js
export const auth0 = new Auth0({
domain: Config.AUTH0_DOMAIN,
clientId: Config.AUTH0_CLIENT_ID,
});
export const actionLogin = async (callback) => {
try {
const authState = await auth0.webAuth.authorize({
scope: 'openid profile email offline_access',
audience: Config.AUTH0_AUDIENCE,
prompt: 'login',
});
let response = await getState(authState, callback);
return response
} catch (e) {
console.log('Error Authenticating: ', e)
}
The hasValidCredentials() method mentioned above is called after a user has successfully authenticated with the webAuth, and it should be returning something along the lines of an access token, refresh token, id, and email per the docs
Note that we are trying to use this so that we can stop using the react-native-keychain package and use auth0's implementation of the native keystores by
await auth0.credentialsManager.requireLocalAuthentication();
I have two simple authentication callback functions "jwt" and "session" which check if the user object exists and create the session if so.
callbacks: {
jwt: async ({ token, user }) => {
if(user) {
token.id = user.id
}
return token
},
session: ({ session, token }) => {
if(token) {
session.id = token.id
}
return session
},
}
My issue is, and I have been searching a lot to find information concerning this, why isn't this jwt automatically saved to cookies?
I find that my session is created and I am successfully "logged in", however if I look into my local storage there are no jwt cookies saved.
Do I have to manually save the jwt to my cookies in the jwt callback? Or is the jwt cookie not even required in the case of jwt session strategy? I need jwt cookies because from what I've read about middleware most solutions use cookies and decrypt the jwt to see if the user is logged in, instead of checking the getSession() hook.
You might need to to explain your problem in more detail since I canĀ“t really tell what you already implemented and left out for simplicity sake. I hope this helps you anyway:
The steps to add the cookie look roughly like this:
Create / sign a jwt with a npm package like jose or jsonwebtoken
Set the header of your response and add your signed jwt to it; return it to the client
import { SignJWT, jwtVerify, JWTVerifyResult } from "jose";
async function setCookie(response, user: {id: number}) {
const token = await generateJwtToken(user);
response.setHeader("Set-Cookie", [
`user=${token};` +
"expires=" + new Date(new Date().getTime() + 1 * 86409000).toUTCString() + ";"
]);
response.status(200).json({message: "Successfully set cookie"})
}
async function generateJwtToken(user: { id: number }) {
return await new SignJWT({id: user.id})
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("24h")
.sign(new TextEncoder().encode(process.env.YOUR_JWT_TOKEN_ENV_VAR));
}
Verify the jwt on further requests with the same package as in 1.
export async function verifyJwt(request) {
const token = request.cookies["yourCustomUser"];
const verified: JWTVerifyResult = await jwtVerify(
token,
new TextEncoder().encode(process.env.YOUR_JWT_TOKEN_ENV_VAR),
);
verified.payload.status = 200;
return verified.payload;
}
In addition to that you might wanna add sameSite=Strict, secure or path=/. For further information you should have a look at developers.mozilla
Also make sure to add error handling for expired Jwts etc.
I am building a Next JS app that has Github Login through Auth0 and uses the Octokit to fetch user info / repos.
In order to get the IDP I had to setup a management api in auth0. https://community.auth0.com/t/can-i-get-the-github-access-token/47237 which I have setup in my NodeJs server to hide the management api token as : GET /getaccesstoken endpoint
On the client side : /chooserepo page, I have the following code :
const chooserepo = (props) => {
const octokit = new Octokit({
auth: props.accessToken,
});
async function run() {
const res = await octokit.request("GET /user");
console.log("authenticated as ", res.data);
}
run();
And
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps({ req, params }) {
let { user } = getSession(req);
console.log("user from get session ", user);
let url = "http://localhost:4000/getaccesstoken/" + user.sub;
let data = await fetch(url);
let resData = await data.text();
return {
props: { accessToken: resData }, // will be passed to the page component as props
};
},
});
However, I keep getting Bad credentials error. If I directly put the access token in the Octokit it seems to work well, but doesn't work when it's fetching the access token from the server.
It seems like Octokit instance is created before server side props are sent. How do I fix it ?
I figured out the error by comparing the difference between the request headers when hardcoding and fetching access token from server. Turns out quotes and backslashes need to be replaced (and aren't visible when just console logging)
I'm working on login authentication with a react native app. I have login working as someone logs in for the first time through the login form. At this initial login step, I'm sending my JWT web token to my secure routes in my express server as a string and this works fine. NOTE: I have a middleware setup that my token goes through before my routes will fire completely.
The problem is when the app refreshes it needs to go to my "local storage". My express middleware doesn't like the token I'm pulling from my "local storage (I retrieve it through AsyncStorate.getItem('UserToken'). When I look at the item being stored in the header, it seems like it's the exact item I was sending it at the initial login, but I think it's my middleware on the server that doesn't like the value when it's coming from the " local storage" header. Below is my middlware.js code.
I've tried looking at the JWT value being sent in both scenarios, and it seems like it's the exact item being sent to the server in both situations.
module.exports = function(req, res, next) {
//Get Token from header
const token = req.header('x-auth-token');
// Check if no token
if(!token) {
return res.status(401).json({ msg: 'No token, authorization denied'})
}
//Verify token
try {
var decoded = jwt.verify(token, global.gConfig.jwtSecret);
req.user = decoded.user;
next();
}catch(err){
res.status(401).json({ msg: 'Token is not valid'});
}
}
This is the function I'm using to retrieve the token from AsyncStorage
export const getAsyncStorage = async () => {
try {
// pulling from header to get x-auth-token here
Value = await AsyncStorage.getItem('Usertoken');
//setting x-auth-token here
axios.defaults.headers.common['x-auth-token'] = Value;
} catch (error) {
// Error retrieving data
}
};
I have a VueJS App, using Vuex & Vue Router.
I have 3 components (which are also pages): Home, Login and a Protected Page which requires one to be authenticated.
The login page make a POST call to the backend API which returns a token if the credentials are valid.
methods: {
sendCredentials: function() {
const { email, password } = this
this.$store.dispatch(AUTH_REQUEST, {email, password})
.then(() => {
this.$router.push('/')
})
.catch((err) => console.log(err.response));
}
}
Here is the related action:
actions: {
[AUTH_REQUEST]: ({ commit, dispatch }, user) => {
return new Promise((resolve, reject) => {
commit(AUTH_REQUEST);
axios.post('http://localhost:3000/api/login', user)
.then((resp) => {
const token = resp.data.token;
localStorage.setItem('userToken', token);
commit(AUTH_LOGIN, token);
resolve(resp);
})
.catch(err => {
commit(AUTH_ERROR, err);
localStorage.removeItem('userToken');
reject(err);
})
});
}
I have used navigation guard to block access to the protected page if the user is not logged in.
This is actually working: When I go the protected page, I'm asked to log in. When I use the rights credentials, I'm able to access the protected page.
I have yet a huge bug: When I put any random string on the localStorage as the userToken, I can access the protected page...
How to prevent that ?
The initial state is defined as below:
state: {
token: localStorage.getItem('userToken') || '',
},
Is there a way to validate the userToken which I get through the localStorage when I set up the initial state of token ?
I have been wondering the same thing a while ago. What I ended up with is to check the token against your backend on initial loading of your page. If the token is valid you commit it to Vuex, if the token is invalid, you delete everyting from localStorage.
This leads to the outcome where someone hypothetically could replace the token after initial load with their own invalid token, but if the clientside token is already validated, what would be the point? If you want to secure against this scenario as well you could apply the same logic in your navigation guard. So not just check for a token, but validate the token against your backend on each route change and clear localStorage if invalid. I think this will come at a performance disadvantage though due to the extra API call.