simple-auth-token JWT authorization not setting Authorization header - authentication

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.

Related

Invalid csrf token with NestJS

I would like to implement Csrf protection with NestJS and Quasar.
But I think I misunderstand something...
btw I'm not doing SSR, so I don't send the form from the back to the view.
Here is the NestJs back-end code:
async function bootstrap() {
const PORT = process.env.PORT;
const app = await NestFactory.create(AppModule, {
cors: true,
bodyParser: false,
});
console.log(`your App is listening on port ${PORT}`);
// Added Cookie-parser to user csurf packages
// Prevent CSRF attack
app.use(cookieParser());
app.use(csurf({ cookie: true }));
await app.listen(PORT);
}
bootstrap();
So I'm just using CookieParser and csurf package.
On my login page I call a "csrf endpoint" just to send a cookie to the view, to send it back with the post call (login).
I still get the "invalid csrf token" AND a CORS error and don't know why....(see screen below), any suggestions to make it works ?
When I try to login, error in the browser:
And error in the back-end:
Same error if I try a request with insomnia.
I thought that the CSRF token is attached to the "web browser" to go back to the back-end with nest request, so why I'm still getting this error ?
Insomnia send the cookie automatically with the right request so the token should go back to the back-end.
Any idea ?
Regards
EDIT:
After many times reading docs, It seems that CSRF protection is for SSR only ? No need to add csrf security with SPA ? Could anyone can confirm ?
EDIT: Here's another work:
The purpose here is to send a request before login to get a csrf token that I can put into a cookie to resend when I login with a POST method.
Here is my endpoint:
import { Controller, Get, Req, Res, HttpCode, Query } from "#nestjs/common";
#Controller("csrf")
export class SecurityController {
#Get("")
#HttpCode(200)
async getNewToken(#Req() req, #Res() res) {
const csrfToken = req.csrfToken();
res.send({ csrfToken });
}
}
Here is what I've done into my main.ts file (I'll explain below):
async function bootstrap() {
const PORT = process.env.PORT;
const app = await NestFactory.create(AppModule, {
cors: {
origin: "*",
methods: ["GET,HEAD,OPTIONS,POST,PUT"],
allowedHeaders: [
"Content-Type",
"X-CSRF-TOKEN",
"access-control-allow-methods",
"Access-Control-Allow-Origin",
"access-control-allow-credentials",
"access-control-allow-headers",
],
credentials: true,
},
bodyParser: false,
});
app.use(cookieParser());
app.use(csurf({ cookie: true }));
console.log(`your App is listening on port ${PORT}`);
await app.listen(PORT);
}
bootstrap();
And here my axiosInstance Interceptors of the request in my VueJS frontend:
axiosInstance.interceptors.request.use(
(req) => {
const token = Cookies.get('my_cookie')
if (token) {
req.headers.common['Authorization'] = 'Bearer ' + token.access_token
}
req.headers['Access-Control-Allow-Origin'] = '*'
req.headers['Access-Control-Allow-Credentials'] = 'true'
req.headers['Access-Control-Allow-Methods'] = 'GET,HEAD,OPTIONS,POST,PUT'
req.headers['Access-Control-Allow-Headers'] =
'access-control-allow-credentials,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,content-type,x-csrf-token'
const csrfToken = Cookies.get('X-CSRF-TOKEN')
if (csrfToken) {
req.headers['X-CSRF-TOKEN'] = csrfToken
console.log(req)
}
return req
},
(err) => {
console.log(err)
},
Here the same for repsonse:
axiosInstance.interceptors.response.use(
(response) => {
if (response?.data?.csrfToken) {
const {
data: { csrfToken },
} = response
Cookies.set('X-CSRF-TOKEN', csrfToken)
}
return response
},
And inside my login I make a call on the mounted function of my login component:
async mounted() {
const result = await securityService.getCsrf()
},
So now to explain:
As I said I'm not building a SSR project, that's why I want to send the token into a classic axios reponse and store it in a Cookie (this part is for test I heard that storing a csrf token into a classic cookie is not the right way.)
And for each next request I get the csrf token and "attach" it to the request into the headers, making my headers "custom".
Here is a problem I don't know how to make custom headers works with nestJS and CORS, that's why I try many thing with CORS options in NestJS and writte some custome header before the request go to the back-end but without success, I've got the same error message:
I'm a bit confuse about this problem and CORS/CSRF is a big deal for spa, my questions still the same, with CORS and SameSite cookie attributes, and my api is in a subdomain of my front-end, is it really necessary to make a anti-csrf pattern ?
Btw how can I make my custom headers working and why CORS say to me there is no "Access-Control-Allow-Origin" header but there is:
try to generate csrf token and pass to front on each petition
// main.ts - from NestJs - Backend
// after app.use(csurf({ cookie: true }))
app.use((req: any, res: any, next: any) => {
const token = req.csrfToken()
res.cookie("XSRF-TOKEN", token)
res.locals.csrfToken = token
next()
})
from: https://github.com/nestjs/nest/issues/6552#issuecomment-1175270849

Problems to get a refresh token using vue, nuxt and keycloak

I'm doing a project with vue, nuxt and keycloak as server for token, axios as http client and #nuxtjs/auth-next module for keycloak access.
I'm using a public client so I don't have a secret key which is the most recommended.
The part of getting the token and talking to the backend is working.
But as it is a public client it has no refresh token.
Searching the internet, a recommendation would be to post from time to time to the keycloak /token endpoint, passing the current token, to fetch a new token.
To perform this post, it doesn't work to pass json, having to pass application/x-www-form-urlencoded.
But it generates an error saying that the parameter was not passed.
On the internet they recommended passing it as url string, but then it generates an error on the keycloak server, as a parameter that is too long, because of the current token that is passed.
Below is the code used to try to fetch a new token.
This code is being called on a test-only button.
If anyone can help, I appreciate it.
const token = this.$auth.strategy.token.get()
const header = {
"Content-Type": "application/x-www-form-urlencoded"
}
const body = {
grant_type: "authorization_code",
client_id: "projeto-ui",
code: token
}
this.$axios ( {
url: process.env.tokenUrl,
method: 'post',
data: body,
headers: header
} )
.then( (res) => {
console.log(res);
})
.catch((error) => {
console.log(error);
} );
Good afternoon people.
Below is the solution to the problem:
On the keycloak server:
it was necessary to put false the part of the implicit flow.
it was necessary to add web-origins: http://localhost:3000, to allow CORS origins.
In nuxt.config.js it was necessary to modify the configuration, as below:
auth: {
strategies: {
keycloak: {
scheme: 'oauth2',
...
responseType: 'code',
grantType: 'authorization_code',
codeChallengeMethod: 'S256'
}
}
}

#nuxtjs/auth Why refresh page always redirect to login

I can't refresh page or open new tab of secure page after refresh or new tab will redirect me to login
again
Version
Nuxt.js v2.9.1
#nuxtjs/module: 4.8.4
secure page
middleware: ['auth'],
middleware of auth-module
login page
middleware: ['guest'],
middleware/guest.js
export default async function({ store, redirect }) {
// console.log(store.state.auth)
if (store.state.auth.loggedIn) {
return redirect('/')
}
}
console.log(store.state.auth) = { user: null, loggedIn: false, strategy: 'local' }
nuxt.config.js
auth: {
strategies: {
local: {
endpoints: {
// register: { url: 'member', method: 'post', propertyName: 'data.accessToken' },
login: { url: 'api/authen-admin', method: 'post', propertyName: 'custom' },
user: { url: 'api/admin', method: 'get', propertyName: 'custom' },
logout: false
},
tokenRequired: 'Authorization',
tokenType: false
}
},
watchLoggedIn: true,
localStorage: {
prefix: 'auth.'
},
cookie: {
prefix: 'auth.', // Default token prefix used in building a key for token storage in the browser's localStorage.
options: {
path: '/', // Path where the cookie is visible. Default is '/'.
expires: 5 // Can be used to specify cookie lifetime in Number of days or specific Date. Default is session only.
// domain: '', // Domain (and by extension subdomain/s) where the cookie is visible. Default is domain and all subdomains.
// secure - false, // Sets whether the cookie requires a secure protocol (https). Default is false, should be set to true if possible.
}
},
redirect: {
login: '/login',
logout: '/login',
home: '/'
},
resetOnError: true
}
I try to use vuex-persist to persist local storage but doesn't work and when login not redirect to home path still stay login path
maybe you can use nuxtServerInit to check the login user. place in the store/index.js folder as root folder. every time you open the web for the first time, this code will run. example i use the cookie to check user loggedIn or not:
export const actions = {
async nuxtServerInit ({ commit }, { req }) {
let auth = null
if (req.headers.cookie) {
// cookie found
try {
// check data user login with cookie
const { data } = await this.$axios.post('/api/auths/me')
// server return the data is cookie valid loggedIn is true
auth = data // set the data auth
} catch (err) {
// No valid cookie found
auth = null
}
}
commit('SET_AUTH', auth) // set state auth
},
}
here the documentation
Extending Fauzan Edris answer.
I was using Auth Nuxt, following fixed my issue.
export const actions = {
async nuxtServerInit({
commit
}, {
req
}) {
let auth = null
if (req.headers.cookie) {
// cookie found
try {
// check data user login with cookie
const {
data
} = await this.$axios.post('/user/profile')
// server return the data is cookie valid loggedIn is true
auth = data.data // set the data auth
} catch (err) {
// No valid cookie found
auth = null
}
}
// How we can set the user for AuthNuxt
// Source: https://auth.nuxtjs.org/api/auth
this.$auth.setUser(auth)
},
}
You set propertyName of user endpoint to 'custom', do you receive the response with this property name? when page reload, auth plugin will try to fetchUser method to sure client still authenticated, if you didnt config user endpoint correctly, regardless of whether receive, user will set null, so you will redirect to login page, you can check what user property set by run this code:
let user = await this.$auth.requestWith(
'local', null, { url: 'api/admin', method: 'get', propertyName: 'custom' } );
console.log(user);
I'm using Nuxt with Laravel Sanctum and the thing that solved the problem for me was an issue with the SESSION_DOMAIN. I'm running the project on a subdomain and the SESSIOn_DOMAIN was set to ".domain.com", but it has to be set to "sub.domain.com".
I've got same and find out on server message, that looked impossible
[404] /api/admin
So I've tried to add BASE_URL to this request url into nuxt.config.js
auth: {
strategies: {
local: {
endpoints: {
user: { url: `${BASE_URL}/api/admin`, ... },
...
}
and issue gone

How to properly use passport-github for REST API authentication?

I am building a vue.js client which needs to be authenticated through github oauth using an express server. It's easy to do this using server side rendering but REST API has been troublesome for me.
I have set the homepage url as "http://localhost:3000" where the server runs and I want the authorization callback url to be "http://localhost:8080" (which hosts the client). I am redirecting to "http://localhost:3000/auth/github/redirect" instead, and in its callback redirecting to "http://localhost:8080". The problem I am facing is that I am unable to send user data to the vuejs client through res.redirect. I am not sure if I am doing it the right way.
router.get("/github", passport.authenticate("github"));
router.get(
"/github/redirect",
passport.authenticate("github", { failureRedirect: "/login" }),
(req, res) => {
// res.send(req.user);
res.redirect("http://localhost:8080/"); // req.user should be sent with this
}
);
I have implemented the following approach as a work around :-
A route that returns the user details in a get request :
router.get("/check", (req, res) => {
if (req.user === undefined) {
res.json({});
} else {
res.json({
user: req.user
});
}
});
The client app hits this api right after redirection along with some necessary headers :
checkIfLoggedIn() {
const url = `${API_ROOT}auth/check/`;
return axios(url, {
headers: { "Content-Type": "application/json" },
withCredentials: true
});
}
To enable credentials, we have to pass the following options while configuring cors :
var corsOption = {
origin: true,
credentials: true
};
app.use(cors(corsOption));

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,
}
}
}
},