Unable to authenticate a user using #hapi/cookie 19.x.x - authentication

I've recently upgraded my project to use hapi 19.x.x along with that I have updated the project to use #hapi/cookie as opposed to the deprecated hap-auth-cookie however after successful authentication my application constantly tries to reauthenticate even after setting a session cookie with request.cookieAuth.set({ id : id})
When the application is redirected to the 'restricted page' using the redirectTo: property on the .auth.strategy('admin', 'cookie', {}) object.
I noticed that the state on the incoming request is {} empty when it shouldn't be
node -v // 12.16.2
Google Chrome
Version 80.0.3987.163 (Official Build) (64-bit)
package.json {
"dependencies": {
"#hapi/catbox-redis": "5.0.5",
"#hapi/cookie": "11.0.1",
"#hapi/h2o2": "9.0.1",
"#hapi/hapi": "19.1.1",
"#hapi/inert": "6.0.1",
"#hapi/joi": "17.1.1",
"#hapi/scooter": "6.0.0",
"#hapi/wreck": "17.0.0",
}
server.auth.strategy('admin', 'cookie', {
cookie: {
name: Server.cookieName,
password: auth_cookie_password,
isSecure: false,
ttl: Server.cacheCookieTtlMs
},
appendNext: true,
redirectTo: outboundUrl,
validateFunc: async (request: any, session: any) => {
// blah blah
}
{
method: ['GET', 'POST'],
path: '/login',
options: {
auth: false,
security: true
},
handler: async (request: any, h) => {
try {
const tokenSet = await authCallback();
const session = {
id: tokenSet.id,
}
request.cookieAuth.set(session);
const returnScript = `<script type="application/javascript" >(function() { setTimeout(function() {window.location = "http://localhost:3000"})})()</script>`;
return h.response(returnScript)
} catch (e) {
return h.response('Internal server error').code(500)
}
}
}
any help would be appreciated.

you have to set the cookie path to /
Cookies are only sent to the server when the URL of the request starts with the value of the cookie’s path. When you omit path, the default is the URL of the request that received the response with the Set-Cookie header. So, let’s say you omit path and your cookie is set on a URL like https://example.com/login (which is very common), then the cookie will only be sent on requests for subpaths like https://example.com/login/foo, which is almost never what you want.

Related

CSRF token and Nuxt-auth

I'm now trying to code a login functionality using nuxt-auth.
I've got a FastAPI server that is set to work with HTTPOnly cookies, thus it needs a csrf token for throwing a user to my client. I can't handle the token because it's HTTPOnly so no LocalStorage
Login works fine but I can't manage to get the stored user. I made that after request to my /login endpoint, Nuxt also requests a user on /me endpoint. But I'm getting the 401 response and
Missing cookie access_token_cookie
error on /me. I don't know how to handle it.
my login request method
async userLogin() {
await this.$auth.loginWith('cookie', {
data: `grant_type=&username=${this.emailInput}&password=${this.passwordInput}&scope=&client_id=&client_secret=&`,
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
})
await this.$router.push('/account')
}
I read that nuxt-auth is bad at cookie patterns but the post was from 2018 and we have a 'cookie' strategy now. So is there a workaround of it's better to handle authentication manually?
my auth key in nuxt.config.js
auth: {
strategies: {
cookie: {
endpoints: {
login: {
url: "/api/v1/login/login",
method: "post",
withCredentials: true
},
logout: { url: "/api/v1/login/logout", method: "post" },
user: {
url: "/api/v1/users/me",
method: "get"
}
},
tokenType: "bearer"
}
}
}
I have a working http-only cookie based setup on Nuxt + Django.
My Nuxt application reverse proxies API requests to backend. So, it can read cookies on server side.
So, I create auth-ssr.ts middleware to check is user loggedIn
import { Context, Middleware } from '#nuxt/types'
import { parse as parseCookie } from 'cookie' // this is lib https://github.com/jshttp/cookie
/**
* This middleware is needed when running with SSR
* it checks if the token in cookie is set and injects it into the nuxtjs/auth module
* otherwise it will redirect to login
* #param context
*/
const authMiddleware: Middleware = async (context: Context) => {
if (process.server && context.req.headers.cookie != null) {
const cookies = parseCookie(context.req.headers.cookie)
const token = cookies['session'] || '' // here your cookie name
if (token) {
context.$auth.$state.loggedIn = true
}
}
}
export default authMiddleware
And here my nuxt.config.js
auth: {
strategies: {
cookie: {
user: {
property: 'user',
},
endpoints: {
login: {
url: '/api/v2/auth/login/',
method: 'post',
},
user: {
url: '/api/v2/auth/user/',
method: 'get',
},
logout: {
url: '/api/v2/auth/logout/',
method: 'post',
},
},
},
},
redirect: {
login: '/login',
},
plugins: ['#plugins/axios.ts'],
},
router: {
middleware: ['auth-ssr', 'auth'],
},
// Axios module configuration: https://go.nuxtjs.dev/config-axios
axios: {
proxy: true,
},
proxy: {
'/api': {
target: 'https://backend.com/',
},
},
...

Add Keycloak to express server to get kauth from request

I am trying to add Keycloak authentication to my ApolloServer using keycloak-connect.
I have setup my realm and login from localhost:8080/auth. However, I am having an issue getting kauth from my requests in the context function:
Currently I have the following setup:
const kcConfig = {
clientId: process.env.KEYCLOAK_CLIENT_ID,
serverUrl: `localhost:808/auth`,
realm: process.env.KEYCLOAK_REALM,
realmPublicKey: process.env.KEYCLOAK_REALM_PUBLIC_KEY,
}
const memoryStore = new session.MemoryStore()
app.use(session({
secret: process.env.SESSION_SECRET_STRING || 'this should be a long secret',
resave: false,
saveUninitialized: true,
store: memoryStore
}))
const keycloak = new Keycloak({
store: memoryStore
}, kcConfig as any)
// Install general keycloak middleware
app.use(keycloak.middleware({
admin: graphqlPath
}))
// Protect the main route for all graphql services
// Disable unauthenticated access
app.use(graphqlPath, keycloak.middleware())
And then I try to access req.kauth in context like:
export interface GrantedRequest extends Request {
kauth : {grant?: Grant};
}
const server = new ApolloServer({
engine: {
graphVariant: "current"
},
context: ({req, res} : {
req: GrantedRequest,
res: any
}) => {
console.log(req.kauth) // this line prints an empty object
return {
req,
res,
kauth: req.auth
}
},
schema,
playground: {
settings: {
"request.credentials": "same-origin"
}
}
});
However, I am not able to retrieve the kauth property from my request. How can I solve this issue?
Keycloak authentication is not used until you define a resource which is protected. Compare to comment in https://github.com/keycloak/keycloak-nodejs-connect/blob/master/keycloak.js#L92
Because you only define the keycloak.middleware() function, does not mean, that your resoures are protected.
To achieve login and receive access token from keycloak, you need to use keycloak.protect() or keycloak.checkSso() or keycloak.enforcer(). Therefore you have to assign these as request-handler to either URL
app.get('/proteced-resource', keycloak.protect())
or application
app.use(keycloak.protect()).

#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

XCRF Token Axios / Nuxt plugin - 403 Forbidden

I've been searching for solution since few hours and not able to find any.
Doing post request via axios nuxt plugin is not working as expected:
nuxt.config.js file:
axios: {
debug: true,
baseURL: `${process.env.API_PROTOCOL}://${process.env.API_HOST}${process.env.API_PORT ? `:${process.env.API_PORT}` : ''}${process.env.API_PREFIX}`,
},
axios plugin:
export default function ({
$axios, redirect, store,
}) {
$axios.setHeader('Content-Type', 'application/json');
$axios.setHeader('Accept', 'application/json');
$axios.onRequest((config) => {
const configLocal = config;
const { jwt } = store.state.authentication;
if (jwt) {
configLocal.headers.JWTAuthorization = `Bearer ${jwt}`;
}
if (config.method === 'post') {
configLocal.headers['X-Requested-With'] = 'XMLHttpRequest';
configLocal.headers['X-XSRF-TOKEN'] = store.state.authentication.crfToken;
}
});
}
And call methods:
authenticateUser({ commit }, { data }) {
return this.app.$axios.$post('auth/login', data).then(({ token }) => {
this.$cookies.set('jwt', token);
commit('setAction', { key: 'jwt', value: token });
}).catch(e => console.log(e));
},
getCRFToken({ commit }) {
return this.app.$axios.$get('auth/token').then(({ token }) => {
this.$cookies.set('crf', token);
commit('setAction', { key: 'crfToken', value: token });
});
},
The getCRFTToken works like a charm by returning CSRF token:
Request URL: http://127.0.0.1:8080/auth/token
Request Method: GET
Status Code: 200
Remote Address: 127.0.0.1:8080
Referrer Policy: no-referrer-when-downgrade
{"token":"92618f1e-0ed3-472b-b6a9-db2201a02d86"}
But whenever I do login...
It fails. Was digging in github - trying to set X-XSRF-TOKEN header in many places, but nope - still doesn't work. Anyone know the solution for this case ?
Edit
In the config folder there is shield.js file which config is blocking your route.
Set enable in csrf to false in the file.
It will start woking then.

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