Hi everyone!
I have my own custom strategy to get token, and all is good, but when a refresh page I lose user data and fetchUser does not works. It doesn´t send the params to API to get again the user data.
the workflow is next:
1- send params to token api and get token
2- send params to login API to get the user
//nuxt.config.js
customStrategy: {
_scheme: '~/schemes/customScheme',
endpoints: {
login: {
url: '/api/v1/token',
method: 'post',
propertyName: 'token',
headers: {'x-channel-id': 1}
},
user: {
url: '/api/v1/login',
method: 'post',
propertyName: false,
headers: {'x-channel-id': 1}
},
logout: null
}
}
customScheme.js
import LocalScheme from '#nuxtjs/auth/lib/schemes/local'
export default class CustomScheme extends LocalScheme {
_setToken (token) {
if (this.options.globalToken) {
// Set Authorization token for all axios requests
this.$auth.ctx.app.$axios.setHeader(this.options.tokenName, token)
}
}
_clearToken () {
if (this.options.globalToken) {
// Clear Authorization token for all axios requests
this.$auth.ctx.app.$axios.setHeader(this.options.tokenName, false)
}
}
mounted () {
if (this.options.tokenRequired) {
const token = this.$auth.syncToken(this.name)
this._setToken(token)
}
return this.$auth.fetchUserOnce()
}
async login (endpoint) {
if (!this.options.endpoints.login) {
return
}
// Get token
const result = await this.$auth.request({
...endpoint
},
this.options.endpoints.login
)
// Set token
if (this.options.tokenRequired) {
const token = this.options.tokenType
? this.options.tokenType + ' ' + result
: result
this.$auth.setToken(this.name, token)
this._setToken(token)
}
// If result I get and set user
if (result) {
const user = await this.$auth.request({
...endpoint
},
this.options.endpoints.user
)
this.$auth.setUser(user);
}
}
async fetchUser (endpoint) {
// User endpoint is disabled.
if (!this.options.endpoints.user) {
this.$auth.setUser({})
return
}
// Token is required but not available
if (this.options.tokenRequired && !this.$auth.getToken(this.name)) {
return
}
// Try to fetch user and then set
try{
const user = await this.$auth.requestWith(
this.name,
endpoint,
this.options.endpoints.login
)
this.$auth.setUser(user)
} catch (error){
console.log(error)
}
}
}
When I set this.$auth.setUser(user) in login() method all is fine and app redirect me to /dashboard page and the user information (like role and email) is displayed on navBar but when I refresh page I lose user data. The app try to fetchUser but it give me a 400 error because user and password not sent.
Another thing I don´t understand is Why endpoint parameter is undefined in async fetchUser (endpoint) ??? . I think there is an issue in this part.
I hope u can help me
Regards
I just remove all this library and did my own custom Nuxt authentication
https://nemanjadragun92.medium.com/nuxt-js-custom-authentication-245d2816c2f3
Related
I am controlling user authentification in my next app with next-auth library
I am using the credentials provider. First I call the login endpoint which returns the user informations then I take the access token and put it inside the token given by next-auth callback.
this is my code in [...nextauth].js
const authOptions = {
session: {
strategy: "jwt",
},
providers: [
CredentialsProvider({
type: "credentials",
credentials: {},
async authorize(credentials, req) {
const { email, password } = credentials;
const result = await axios.post(
`http://127.0.0.1:5000/user/login`,
{
email,
password,
},
{
headers: { "Content-Type": "application/json" },
withCredentials: true,
}
);
return {
accessToken: result.data.accessToken,
};
},
}),
],
callbacks: {
async jwt({ user, token }) {
if (user?.accessToken) {
token.value = user.accessToken;
}
console.log(token); //<-- output below
return token;
},
},
};
output :
{
value: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYzOTZiMTlhYTczMmUzMzYwMjU2ZjBlMiIsImlhdCI6MTY3NTAyMzEwNSwiZXhwIjoxNjc1MTA5NTA1fQ.5kdPmeLCpwbJBjtzKMhe5QMNEx75ThiDKm75PN0vjoc',
iat: 1675023106,
exp: 1675109506,
jti: 'd9108700-1b5f-4bd3-8d31-0c36f38d9fcb'
}
Now in getServerSideProps I can get it from the request because it is sent in Cookie
export async function getServerSideProps(context) {
console.log(context.req.cookies["next-auth.session-token"]); // <-- output in Blockquote
return {
// does not matter
};
}
I get this :
eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..6ryJ60GPcDLq9aWG.4oWlJbecyWUnbZYJiv6z0eAuFmRFSfEn4fQSlh1FTjlPiiDGZASA4UwqXNEHRpRMG6HRPRDcsUUCHBBzaV8JwCEetgSYJcSrZ5CK_AhyvFKUlKY-TpHSNDnmCI8ZS4y2nV_Xl0NqvMU3vA-D8gXtT5UcOrJLlN5dMe7S9xZo8vhr-gpohcEhKOefUgDjTmMYmBf190OLl0TY599FkJwpoeSFozAwavwbOZGQOxYVbsj3KTibsfE37juyqnDaiV_t59bWroGjz2d5kHLxfkpQB0IKYRnAH8sXbG7dDZUVLT1UQUN_FrjYpkFrQgxC7MmWZtCccQs-FsBXY7EbiYmJKIddpOeN1Q.1kas8bGE_O7IkEDiilxiZw
Now I want to decrypt this token to get its property 'value' (which is the accessToken) and use it.
is it possible to decrypt it with javascript ? Thank you for your attention !
this funciton is inside next-auth/jwt module:
async function decode(params) {
const {
token,
secret
} = params;
if (!token) return null;
const encryptionSecret = await getDerivedEncryptionKey(secret);
const {
payload
} = await (0, _jose.jwtDecrypt)(token, encryptionSecret, {
clockTolerance: 15
});
return payload;
}
since this is not exported you have to export all module
import jwt from "next-auth/jwt"
// since you reached the token inside getServerSidePrps passed here
jwt.decode(token,passYourSecret)
May jwt.decode(token,secret) works as montioned in #Yilmaz answer but there is a built-in helper method getToken() for doing that
For convenience, this helper function is also able to read and decode tokens passed from the Authorization: 'Bearer token' HTTP header.
import { getToken } from "next-auth/jwt";
const secret = 'MY_SECRET';
export default async function handler(req, res) {
const token = await getToken({ req, secret })
console.log("JSON Web Token", token)
res.end()
}
If using NEXTAUTH_SECRET env variable, we detect it, and you won't actually need to secret
export default async function handler(req, res) {
const token = await getToken({ req })
console.log("JSON Web Token", token)
res.end()
}
I want to protect all routes from my NextJs website to authenticated users with NextAuth except the login route that can be accessed with being authenticated, I tried making a middleware that checks for user token, and redirect them to the login route if it was unavailable. but a general middleware for that would stop the website from getting public files so i came up with this:
// middleware.js
import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt'
export default async function middlware(req) {
// ignoring file system and public folder
if (req.nextUrl.pathname.startsWith('/_next/') ||
req.nextUrl.pathname.startsWith('/icons/') ||
req.nextUrl.pathname.startsWith('/fonts/')) {
return;
}
// igonring api/auth
if (req.nextUrl.pathname.startsWith('/api/auth')) {
return;
}
// user session
const session = await getToken({ req })
// redirecting to homepage if a logged in user tries to access the login page
if (req.nextUrl.pathname.startsWith('/auth/login')) {
if (session) {
const url = req.nextUrl.clone()
url.pathname = '/'
return NextResponse.redirect(url)
}
}
// // redirecting to login page if not logged in
if (!req.nextUrl.pathname.startsWith('/auth/login')) {
if (!session && session == null) {
const url = req.nextUrl.clone()
url.pathname = '/auth/login'
return NextResponse.redirect(url)
}
}
}
this works for general routes, but when I try to fetch some data in other pages from other API routes I get this error:
FetchError: invalid json response body at http://localhost:3000/auth/login reason: Unexpected token < in JSON at position 0
at E:\0 - WEB\xwendkaran-admin\node_modules\next\dist\compiled\node-fetch\index.js:1:51227
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async getServerSideProps (webpack-internal:///./src/pages/departments-marking.js:70:22)
at async Object.renderToHTML (E:\0 - WEB\xwendkaran-admin\node_modules\next\dist\server\render.js:490:20)
at async doRender (E:\0 - WEB\xwendkaran-admin\node_modules\next\dist\server\base-server.js:908:38)
at async cacheEntry.responseCache.get.isManualRevalidate.isManualRevalidate (E:\0 - WEB\xwendkaran-admin\node_modules\next\dist\server\base-server.js:1013:28)
at async E:\0 - WEB\xwendkaran-admin\node_modules\next\dist\server\response-cache.js:69:36 {
type: 'invalid-json'
}
this error only occurs because of the middleware and if i remove the middle everything works but that means anyone can access the website and also post data to the server
The problem occurred because when I was trying to fetch data from an API route there was no cookie with the request headers, and !session && session == null would stop it because it showed as not authenticated, so I fixed it with adding user cookies with request headers while fetching the data, like this:
export async function getServerSideProps({ req }) {
try {
// fetching centers
const data = await fetch(`${server}/api/API-ROUTE`, {
method: 'GET',
headers: {
'cookie': req.headers.cookie, // this is where I parsed the cookies
'Content-Type': 'application/json',
'Accept': 'application/json'
},
}).then(res => res.json())
return {
props: {
data
}
}
}
catch (error) {
console.log(error.message)
return {
props: {}
}
}
}
I have a problem with an authToken in my Vue 3 application (very beginner in JS and Vue).
When the user is authenticated (after a post /login), the app receives a token which is stored in the local storage (I know it is perhaps not a good idea, it will be another question later).
With this token, the app can make some GETs to the endpoint. All is working fine.
Of course, the user can logout. In this case I remove the token from the local storage and the user is redirected to the login page.
The problem is now : when the user reconnects again, a new token is received and stored. But all the GET requests are made with the previous token. So of course all the requests are refused (the previous token is no more on the server).
When the app receives the token :
export function loginUser(email, password) {
return new Promise(async (resolve, reject) => {
try {
let res = await axios({
url: `${REST_ENDPOINT}/login`,
method: 'POST',
data: {
email: email,
password: password
}
})
setAuthToken(res.data)
resolve()
}
catch (err) {
console.error('Caught an error during login:', err.request.response)
reject(JSON.parse(err.request.response))
}
})
}
export function logoutUser() {
clearAuthToken()
}
export function setAuthToken(token) {
axios.defaults.headers.common['Authorization'] = `Bearer ${token.token}`
console.log('je suis dans le setAUthToken')
localStorage.setItem(AUTH_TOKEN_KEY, token.token)
}
And for the requests, I created a repository system. So in my component I just have :
import { RepositoryFactory } from "../../repositories/RepositoryFactory";
const OrganizationsRepository = RepositoryFactory.get("organizations");
export default {
name: "About",
data() {
return {
isLoading: false,
organizations: [],
page: 1,
filterByName: '',
filterByStateId: 'VALIDATED',
};
},
created() {
this.page = 1
this.getOrganizations();
},
methods: {
async getOrganizations() {
this.isLoading = true;
console.log('avant le get dans la view')
const { data } = await OrganizationsRepository.getOrganizations(this.buildParams());
this.isLoading = false;
this.organizations = data;
},
I am sure (but to confirm) that it is normal because when the app is loading, the token is stored and no more changed (thanks to the import just below).
So in this case, how to force the component to reload the token for each request ?
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
Using Vue webpack template, trying to make JWT authentication. What I've done so far:
"src/auth/index.js":
// Send a request to the login URL and save the returned JWT
login (creds, redirect) {
axios.post(LOGIN_URL, creds, (data) => {
localStorage.setItem('access_token', data.access_token)
this.user.authenticated = true
// Redirect to a specified route
if (redirect) {
router.push(redirect)
}
}).error((err) => {
context.error = err
})
},
I'm calling this function from LoginPage.vue:
methods: {
login () {
var credentials = {
username: this.credentials.username,
password: this.credentials.password
}
// We need to pass the component's this context
// to properly make use of http in the auth service
auth.login(this, credentials, 'requests')
}
}
When I'm submitting the form, data is submitted, but I get the following error in a console:
TypeError: __WEBPACK_IMPORTED_MODULE_1_axios___default.a.post(...).error is not a function
Also JWT token is not saving in my local storage, what am I doing wrong?
Rewrote login function:
login (context, creds, redirect) {
axios.post(LOGIN_URL, creds)
.then((response) => {
localStorage.setItem('access_token', response.data.access_token)
this.user.authenticated = true
if (redirect) {
router.push(redirect)
}
}).catch((err) => {
context.error = err.response.data
})
},
Everything is working now.