Loopback 4 issues generating/refreshing tokens - authentication

I'm using loopback at work and just started new project with this framework.
When i'm refreshing token i noticed that lb4 is just generating a basic jwt token,
i digged in node modules and sees that the refreshService.refreshToken method is actually not returning the new refresh token anywhere.
so my questions are:
Does i have to generate a new refresh token manually when refreshing token ?
Does exists a better way to get the userprofile when refreshing token as i dont have a token to decode (except the new generated token)
here is the code used for refreshing token
#post('/runners/token/refresh')
#response(200, {
content: {
'application/json': {
schema: {
properties: {
access_token: {type: 'string'},
refresh_token: {type: 'string'},
expires_in: {type: 'string'}
}
}
}
}
})
async refreshToken(
#requestBody({content: {'application/json': {schema: {properties: {refresh_token: {type: 'string'}}}}}})
body: {refresh_token: string}
) {
const {
refreshToken: refresh_token,
accessToken: access_token,
expiresIn: expires_in
} = await this.refreshService.refreshToken(body.refresh_token);
return {refresh_token, access_token, expires_in};
}
also it's declared in loopback modules that the refreshToken method return a access+refresh token + expires in but that not the case...
thanks a lot

Related

Nuxt Auth Module c5 doesn't refresh token automatically when token expires

Version
module: 5.0.0-1624817847.21691f1
nuxt: 2.15.8
Nuxt configuration
Universal
Nuxt configuration
// Auth: https://auth.nuxtjs.org/ (v5)
auth: {
redirect: {
login: '/account/login/',
logout: '/account/login/',
callback: '/account/login/',
home: '/account/beams/'
},
strategies: {
local: {
scheme: 'refresh',
token: {
property: 'access_token',
maxAge: 120, // seconds, 2 minutes
global: true
},
refreshToken: {
property: 'refresh_token',
data: 'refresh_token',
maxAge: 1209600 // seconds, 2 weeks
},
user: {
property: 'user',
autoFetch: true
},
endpoints: {
login: { url: '/api/account/login', method: 'post', propertyName: 'token' },
refresh: { url: '/api/account/refresh', method: 'post', },
logout: { url: '/api/account/logout', method: 'post' },
user: { url: '/api/account', method: 'get' }
},
autoLogout: false
}
}
},
Additional information
Checklist
[x] I have tested with the latest Nuxt version and the issue still occurs
[x] I have tested with the latest module version and the issue still occurs
[x] I have searched the issue tracker and this issue hasn't been reported yet
Steps to reproduce
What is expected?
When a user's token expires and refresh scheme is implemented, a user shouldn't be logged out and redirected back to the login screen, the refresh token should be used to obtain a new token and the transition should be seamless allowing any authenticated route to continue to work.
What is actually happening?
In my Nuxt project with the Auth module I've implemented the refresh scheme, however, when my token expires I don't see any request in my network being made to the refresh route after my token expires and I navigate to a protected page via the auth middleware.
I expect I'm missing some simple configuration?
My current token has an expiry of 1 minute for testing, and my refresh token has an expiry of 14 days for testing.
However, when adding:
scheme: 'refresh'
refresh: { url: '/api/account/refresh', method: 'post', }
the functionality appears to not be fetching my user and automatically logging me in.
My /api/account/refresh endpoint in my API returns the following:
{
refresh_token: 'my refresh token',
token_type: 'bearer',
expired_in: 5000
}
My /api/account/login endpoint in my API returns the following:
{
access_token: 'my token',
token_type: 'bearer',
expired_in: 1000
}
What am I missing?
You need to return refresh token from /api/account/login. And then set in conf property name of it.
I have same issue with very similar comfiguration. This is my result from API (I added refresh token to the result):
{
"access_token": "XXX",
"refresh_token": "XXX",
"expired_in": 3600,
"token_type": "bearer"
}
If I inspect cookies, I can see acces token, but refresh token does not set:
I try to manually set refresh token after login, but with same result:
const result = await this.$auth.loginWith('local', {
data: this.login,
})
this.$auth.setUserToken(result.data.accessToken, result.data.refreshToken)

How to test single page application with Cypress and Auth0

I am having a single page application hidden behind Auth0 lock, using #auth0/auth0-spa-js. I would like to test it using Cypress, so I have decided to follow the official Auth0 blog post, as well as Johnny Reilly blog post.
I am able to successfully retrieve valid JWT token from auth0 using suggested request. I have no idea what to do with it :(
The trouble I am facing is that both of the above approaches are relying on the app to store the JWT token locally (either in cookie or localstorage). The #auth0/auth0-spa-js is, however, using a different approach, and I assume all the relevant cookies/localstorage is stored on auth0 domains.
Do you have any idea, if there is a way to get around it?
There is a similar issue reported here raised in July 2018, not really providing any solution
I found a resolved issue on #auth0/auth0-spa-js github. The approach suggested by cwmrowe seems to be working
The solution is to mock the response of oauth/token endpoint with token generated on e2e test side.
The approach seems to be working for us
I am copying over the sample code cwmrowe has provided
Cypress.Commands.add(
'login',
(username, password, appState = { target: '/' }) => {
cy.log(`Logging in as ${username}`);
const options = {
method: 'POST',
url: Cypress.env('Auth0TokenUrl'),
body: {
grant_type: 'password',
username,
password,
audience: Cypress.env('Auth0Audience'),
scope: 'openid profile email',
client_id: Cypress.env('Auth0ClientId'),
client_secret: Cypress.env('Auth0ClientSecret')
}
};
cy.request(options).then(({ body }) => {
const { access_token, expires_in, id_token } = body;
cy.server();
// intercept Auth0 request for token and return what we have
cy.route({
url: 'oauth/token',
method: 'POST',
response: {
access_token,
expires_in,
id_token,
token_type: 'Bearer'
}
});
// Auth0 SPA SDK will check for value in cookie to get appState
// and validate nonce (which has been removed for simplicity)
const stateId = 'test';
const encodedAppState = encodeURI(JSON.stringify(appState));
cy.setCookie(
`a0.spajs.txs.${stateId}`,
`{%22appState%22:${encodedAppState}%2C%22scope%22:%22openid%20profile%20email%22%2C%22audience%22:%22default%22}`
);
const callbackUrl = `/auth/callback?code=test-code&state=${stateId}`;
return cy.visit(callbackUrl);
});
}
);
declare namespace Cypress {
interface Chainable<Subject> {
login(
username: string,
password: string,
appState?: any
): Chainable<Subject>;
}
}
Whilst it's not recommended to use the UI to login I do this myself once prior to all tests and then use the silent auth for the tests:- cy.visit("/") silent auths and allows access to the app.
integration/app.js
describe("App", () => {
before(() => {
Cypress.config("baseUrl", "http://localhost:3000");
cy.login();
});
/** Uses silent auth for successive tests */
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
/** tests */
support/commands.js
/**
* Auth0 login
* https://github.com/cypress-io/cypress/issues/461#issuecomment-392070888
*
* Allows silent auth login between tests
*/
let LOCAL_STORAGE_MEMORY = {};
Cypress.Commands.add("saveLocalStorage", () => {
Object.keys(localStorage).forEach(key => {
LOCAL_STORAGE_MEMORY[key] = localStorage[key];
});
});
Cypress.Commands.add("restoreLocalStorage", () => {
Object.keys(LOCAL_STORAGE_MEMORY).forEach(key => {
localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]);
});
});
Cypress.Commands.add("clearLocalStorage", () => {
LOCAL_STORAGE_MEMORY = {};
});
For those who has issue with Google Sign in for Cypress look at the plugin: https://github.com/lirantal/cypress-social-logins/
it('Login through Google', () => {
const username = Cypress.env('googleSocialLoginUsername')
const password = Cypress.env('googleSocialLoginPassword')
const loginUrl = Cypress.env('loginUrl')
const cookieName = Cypress.env('cookieName')
const socialLoginOptions = {
username,
password,
loginUrl,
headless: false,
isPopup: true,
logs: false,
loginSelector: 'a[href="/auth/auth0/google-oauth2"]',
postLoginSelector: '.account-panel'
}
return cy.task('GoogleSocialLogin', socialLoginOptions).then(({cookies}) => {
cy.clearCookies()
const cookie = cookies.filter(cookie => cookie.name === cookieName).pop()
if (cookie) {
cy.setCookie(cookie.name, cookie.value, {
domain: cookie.domain,
expiry: cookie.expires,
httpOnly: cookie.httpOnly,
path: cookie.path,
secure: cookie.secure
})
Cypress.Cookies.defaults({
whitelist: cookieName
})
}
})
});

How to save JWT Token in Vuex with Nuxt Auth Module?

I am currently trying to convert a VueJS page to NuxtJS with VueJS. Unfortunately I have some problems with authenticating the user and I can't find a solution in Google. I only use Nuxt for the client. The API is completely separate in express and works with the existing VueJS site.
In Nuxt I send now with the Auth module a request with username and password to my express Server/Api. The Api receives the data, checks it, and finds the account in MongoDB. This works exactly as it should. Or as I think it should. Now I take the user object and generate the jwt from it. I can debug everything up to here and it works.
Now I probably just don't know how to keep debugging it. I send an answer with res.json(user, token) back to the Nuxt client (code follows below). As I said, in my current VueJS page I can handle this as well. Also in the Nuxt page I see the answer in the dev console and to my knowledge the answer fits.
Now some code.
The login part on the express Api:
const User = require('../models/User')
const jwt = require('jsonwebtoken')
const config = require('../config/config')
function jwtSignUser(user){
const ONE_YEAR = 60 * 60 * 24 * 365
return jwt.sign(user,config.authentication.jwtSecret, {
expiresIn: ONE_YEAR
})
}
module.exports = {
async login (req, res){
console.log(req.body)
try{
const {username, password} = req.body
const user = await User.findOne({
username: username
})
if(!user){
return res.status(403).send({
error: `The login information was incorrect.`
})
}
const isPasswordValid = await user.comparePassword(password)
if(!isPasswordValid) {
return res.status(403).send({
error: `The login information was incorrect.`
})
}
const userJson = user.toJSON()
res.json({
user: userJson,
token: jwtSignUser(userJson)
})
} catch (err) {
console.log(err)
res.status(500).send({
error: `An error has occured trying to log in.`
})
}
}
}
nuxt.config.js:
auth: {
strategies: {
local: {
endpoints: {
login: {url: '/login', method: 'post' },
user: {url: '/user', method: 'get' },
logout: false,
}
}
},
redirect: {
login: '/profile',
logout: '/',
user: '/profile',
callback:'/'
}
}
even tried it with nearly any possible "propertyName".
and, last but not least, the method on my login.vue:
async login() {
try {
console.log('Logging in...')
await this.$auth.loginWith('local', {
data: {
"username": this.username,
"password": this.password
}
}).catch(e => {
console.log('Failed Logging In');
})
if (this.$auth.loggedIn) {
console.log('Successfully Logged In');
}
}catch (e) {
console.log('Username or Password wrong');
console.log('Error: ', e);
}
}
What I really don't understand here... I always get "Loggin in..." displayed in the console. None of the error messages.
I get 4 new entries in the "Network" Tag in Chrome Dev Tools every time I make a request (press the Login Button). Two times "login" and directly afterwards two times "user".
The first "login" entry is as follow (in the General Headers):
Request URL: http://localhost:3001/login
Request Method: OPTIONS
Status Code: 204 No Content
Remote Address: [::1]:3001
Referrer Policy: no-referrer-when-downgrade
The first "user" entry:
Request URL: http://localhost:3001/user
Request Method: OPTIONS
Status Code: 204 No Content
Remote Address: [::1]:3001
Referrer Policy: no-referrer-when-downgrade
Both without any Response.
The second login entry:
Request URL: http://localhost:3001/login
Request Method: POST
Status Code: 200 OK
Remote Address: [::1]:3001
Referrer Policy: no-referrer-when-downgrade
and the Response is the object with the token and the user object.
The second user entry:
Request URL: http://localhost:3001/user
Request Method: GET
Status Code: 200 OK
Remote Address: [::1]:3001
Referrer Policy: no-referrer-when-downgrade
and the Response is the user object.
I think for the login should only the login request be relevant, or I'm wrong? And the user request works because the client has asked for the user route and the user route, always send the answer with the actual user object in my Express API.
Because I think, the problem is in the login response? Here some screenshots from the Network Tab in Chrome Dev Tools with the Request/Response for login.
First login request without response
Second login request
Response to second login request
Do I have to do something with my Vuex Store? I never found any configured Vuex Stores in examples for using the Auth Module while using google so I thougt I do not have to change here anything.
Thats my Vuex Store (Vue Dev Tools in Chrome) after trying to login without success:
{"navbar":false,"token":null,"user":null,"isUserLoggedIn":false,"access":false,"auth":{"user":"__vue_devtool_undefined__","loggedIn":false,"strategy":"local","busy":false},"feedType":"popular"}
There is also some logic I use for my actual VueJS site. I will remove that when the Auth Module is working.
Asked by #imreBoersma :
My /user endpoint on Express looks like:
app.get('/user',
isAuthenticated,
UsersController.getUser)
I first check if the User is authenticated:
const passport = require('passport')
module.exports = function (req, res, next) {
passport.authenticate('jwt', function (err, user) {
if(err || !user) {
res.status(403).send({
error: 'You are not authorized to do this.'
})
} else {
req.user = user
next()
}
})(req, res, next)
}
After that I search the User document in MongoDB and send the document to the client:
const User = require('../models/User')
module.exports = {
[...]
getUser (req, res) {
User.findById(req.user._id, function (error, user){
if (error) { console.error(error); }
res.send(user)
})
}
[...]
}
Feel free to ask for more information.
I think I can answer my own question.
I searched the whole time for an error regarding to my api response.
The problem was the "propertyName" on user endpoint in the nuxt.config.js.
It is set to "user" as default. When I set it to "propertyName: false", than everything works as it should.
auth: {
strategies: {
local: {
endpoints: {
login: {url: '/login', method: 'post', propertyName: 'token' },
user: {url: '/user', method: 'get', propertyName: false },
logout: false,
}
}
}
},

How to use VueJS axios-oauth-client and google photos api?

I'm using vue cli and I try to get album list from my google photos account.
I'm using axios-oauth-client and I try to implement this code:
const axios = require('axios');
const oauth = require('axios-oauth-client');
const getAuthorizationCode = oauth.client(axios.create(), {
url: 'https://oauth.com/2.0/token',
grant_type: 'authorization_code',
client_id: 'foo',
client_secret: 'bar',
redirect_uri: '...',
code: '...',
scope: 'baz',
});
const auth = await getAuthorizationCode(); // => { "access_token": "...", "expires_in": 900, ... }
As described in here and I can't understand how do I get the authorization code aka code in this implementation.
I managed to do this call in postman but I'm unable to do it using axios.
Postman
My Code
async function getToken() {
const getAuthorizationCode = oauth.client(axios.create(), {
url: "https://oauth2.googleapis.com/token",
grant_type: "authorization_code",
client_id:
"**********.apps.googleusercontent.com",
client_secret: "***********",
redirect_uri: "http://localhost:8080/oauth2/callback",
code: "...",
scope: "https://www.googleapis.com/auth/photoslibrary.readonly"
});
const auth = await getAuthorizationCode(); // => { "access_token": "...", "expires_in": 900, ... }
console.log(auth);
}
getToken();
What am I doing wrong?
Thanks!
UPDATE
I still didn't manage to make it works but I found this answer which I will try to check.
UPDATE#2
Eventually I ended up using google documentation for Oauth2 using php and I took their git example as a base project.
In order to do it I also needed to use database to save the tokens and especially the refresh token which I use to refresh the access token every time I receive 401.

simple-auth-token JWT authorization not setting Authorization header

I'm trying to setup a simple Ember.js app to talk with a custom API server, with JWT authentication.
I can login at the API server and obtain a JWT token, but then no Authorization header is set in subsequent calls to the API server.
My login controller is:
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
authenticate: function() {
var credentials = this.getProperties('identification', 'password'),
authenticator = 'simple-auth-authenticator:jwt';
this.get('session').authenticate(authenticator, credentials).then(function() {
// authentication was successful
console.log('OK');
}, function(err) {
// authentication failed
console.log('FAIL ' + JSON.stringify(err));
});
},
logOut: function() {
this.get('session').invalidate();
}
}
});
I can successfully login and obtain a token. My login route:
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
sessionAuthenticationFailed: function(error) {
console.log('Login error: ' + error.ErrorDesc);
this.controllerFor('login').set('loginErrorMessage', error.ErrorDesc);
this.controllerFor('login').set('ErrorMoreInfo', error.MoreInfo);
},
sessionAuthenticationSucceeded: function() {
console.log('Session authenticated: ' + this.get('session').content.secure.token);
// redirect to last route requested, or to default route
var attemptedTransition = this.get('session').get('attemptedTransition');
if (attemptedTransition) {
attemptedTransition.retry();
this.get('session').set('attemptedTransition', null);
} else {
this.transitionTo('index');
}
}
}
});
...shows me the token is properly acquired, and correctly redirects me to my protected routes (e.g. index). Since then, if I try to get any data from the API server, it does not receive any "Authorization: Bearer [token]" header at all.
My environment configuration:
ENV['simple-auth'] = {
authorizer: 'simple-auth-authorizer:token'
};
ENV['simple-auth-token'] = {
refreshAccessTokens: true,
timeFactor: 1000,
refreshLeeway: 300, // Refresh the token 5 minutes (300s) before it expires.
serverTokenEndpoint: 'https://localhost:8000/login',
crossOriginWhitelist:[
'http://localhost:4200',
'https://localhost:8000'
],
identificationField: 'user',
passwordField: 'password',
tokenPropertyName: 'token',
authorizationPrefix: 'Bearer ',
authorizationHeaderName: 'Authorization',
// headers: {},
};
I also tried manually setting the header by calling jqXHR.setRequestHeader overriding the authorize function in my login route, but with no success:
authorize: function(jqXHR, requestOptions) {
var auth= "Bearer " + this.get('session').content.secure.Token;
console.log('Add authorization header ' + auth);
console.log( JSON.stringify(requestOptions));
jqXHR.setRequestHeader("Authorization", auth);
}
Can anybody tell what I'm missing? Shouldn't simple-auth-token take care of adding the header automatically?
Thanks for any help,
al.
I had the same issue, with a REST adapter making calls on a different port.
Solved adding
ENV['simple-auth'] = {
crossOriginWhitelist: ['*']
}
Xabi's answer is working for me. But I didn't find it intuitive.
"Authorized requests" comply to a restrictive CORS policy : the authorization is not added in case of CORS issue.
In the docs :
Ember Simple Auth will never authorize requests going to a different origin than the one the Ember.js application was loaded from.
But requests that don't need an authorizer (with no 'Authorization' header in case of JWT) are allowed and working fine.