Next-auth How to redirect when 401? - next

I am working with Next-auth and rtk query. I need that when a request, any, returns a 401 unauthorized error, the page redirects directly to the login. How is it done?
I added 'maxAge: 60' to the [...nextauth].js file and also refetchInterval={30} refetchOnWindowFocus={true} to the component tried to find a similar solution, but it doesn't work

since you're using rtk query, you can update your apiSlice baseQuery function, to check for auth errors and redirect on that, my suggestion is this:
create a base query where you check for the 401 and any other error you want:
// try to execute the req, if it fails logout, and redirect to login.
const baseQueryWithAuth: BaseQueryFn = async (args, api, extraOptions) => {
const result = await baseQuery(args, api, extraOptions);
if (result.error?.status === 403 || result.error?.status === 401) {
// non authorized, then redirect to login page.
// if we have jwt, here u should update the access token
localStorage.removeItem(TOKEN_KEY_IN_LOCAL_STORAGE);
Router.replace('/auth/login');
}
return result;
};
in the snippet above, when I'm referring to token deletion as logout because the token is already invalid in the DB, so I just need to delete it in the front, so no invalidate request is needed.
the mentioned baseQuery can be done like this:
const baseUrl = `${process.env.NEXT_PUBLIC_API_PROTOCOL}://${process.env.NEXT_PUBLIC_API_HOST}/api`;
const TOKEN_KEY_IN_LOCAL_STORAGE = 'SavedToken';
const baseQuery = fetchBaseQuery({
baseUrl,
// credentials: 'include',
prepareHeaders: (headers) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem(TOKEN_KEY_IN_LOCAL_STORAGE);
if (token) {
headers.set('Authorization', token);
} else {
Router.replace('/auth/login');
}
return headers;
},
});
and then now since you have a working base query with auth support, you can use that to create a main rtk query apiSlice for your project:
// create api
export const apiSlice = createApi({
baseQuery: baseQueryWithAuth,
tagTypes: ['tag1', 'tag2', 'tag3'],
endpoints: (_builder) => ({}),
});

Related

NodeJS browser session

I'm quite new to NodeJS so could someone explain this to me:
I've got a simple authorization on my endpoint created in express. As you can see in the code there is no JWT or session cookie set anywhere. I've also checked in browses cookies just to be sure and there is no sessionId set anywhere.
With that in mind I'd expect my app to prompt for credentials each time I refresh the page ( or at least when I manually delete all cookies, refresh cache and then refresh the page) but that's not happening. Browser is still sending request with correct authorization header (after i put credentials for the first time and refresh page) and I have no idea why.
simple endpoint code:
app.get('/cms', function (req, res) {
const reject = () => {
res.setHeader("www-authenticate", "Basic");
res.sendStatus(401);
};
const authorization = req.headers.authorization;
if (!authorization) {
return reject();
}
const [username, password] = Buffer.from(
authorization.replace("Basic ", ""),
"base64"
)
.toString()
.split(":");
if (!(username === "user" && password === "pass")) {
return reject();
}
res.status(200)
res.sendFile(path.join(__dirname+'/cms.html'));
});

API Token returning 401 Unauthorised when its working in URL

I am making a Multi-Purpose API Service and as I got the token in the URL working perfect and authorising as expected with a 200. I've been having issues with the token not authorising with curl command or superagent, as its always return a 401 error.
auth.js
const { DB } = require('../src/Routes/index.js');
module.exports = function auth(req, res, next) {
if(!req.query.apiKey) {
return res.status(401).json({"error": "401 Unauthorized", message: "API Token is missing in the query. You will need to generate a Token and put in the apiKey query."})
} else {
let check = DB.filter(k => k).map(i => i.apiToken);
console.log(check);
let t = check.some(e => e == req.query.apiKey)
if(t !== true)
return res.status(401).json({"error": "401 Unauthorized", message: "The API Key you provided is invalid. Please refer to our Docs () and Generate a Token ()"});
return next();
}
}
This is the middleware for the token, I am using this in my routers so then the token will authenticate. However, if I remove the if statement for checking if an API Token is present. It seem to fix the issue kinda but always authenticates with any key (even ones not saved in the db) and is still not properly fixed.
and an example for requesting endpoint with token on a Discord Command:
const { MessageEmbed } = require("discord.js");
const { get } = require("superagent");
exports.run = async (bot, message, args) => {
const { body } = await get(`https://example.com/generators/3000years`)
.query({ image: message.author.displayAvatarURL({ dynamic: true, size: 2048 }) })
.set("Authorization", `Bearer MY_TOKEN`);
const embed = new MessageEmbed()
.setTitle(`**3000 Years**`)
.attachFiles({ attachment: body, name: "3000years.png" })
.setImage("attachment://3000years.png")
.setColor(`#ed8a5c`);
message.channel.send(embed);
}
You can see that if I authorise with Superagent, it will not work and return a 401 Unauthorised in the console.
I would like to ask why this is doing this and if I did something wrong. Any help is appreciated.

How fix 'Error while running `getDataFromTree` Network error: localStorage is not defined'?

Everythings works but I keep getting: Error while running getDataFromTree Network error: localStorage is not defined. I tried conditionals to only send req from client but it did'nt work. All the requests seem to be coming from server.
I'm using google Oauth and passport for authentication in NextJS
Should I create authLink from a different file? Any clue?
Usefull links:
https://www.apollographql.com/docs/react/recipes/authentication/
https://github.com/zeit/next.js/tree/canary/examples/with-apollo
I've tried to conditional logic to only send req from client but then, logic stops working. Looks like most of nextJS reqest are from serverside.
let apolloClient = null
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? Bearer ${token} : "",
}
}
});
function create(initialState) {
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
const isBrowser = typeof window !== 'undefined'
console.log(isBrowser);
return new ApolloClient({
connectToDevTools: isBrowser,
ssrMode: !isBrowser, // Disables forceFetch on the server (so queries are only run once)
link: authLink.concat(new createHttpLink({
uri: 'http://localhost:3000/graphql', // Server URL (must be absolute)
credentials: 'same-origin', // Additional fetch() options like credentials or headers
// Use fetch() polyfill on the server
fetch: !isBrowser && fetch
})),
cache: new InMemoryCache().restore(initialState || {})
})
}
export default function initApollo(initialState) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return create(initialState)
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = create(initialState)
}
return apolloClient
}
I need apollo to send cookies with every request, so my react component can find if the user is logged in.
It's breaking my head, any help appreciated. Thank you in advance.

do something 'before' login in loopback

I am pretty new to loopback and here is what I am doing:
I am using standard login route provided by the loopback to log in the users - extended base Users to my own model say orgadmin.
With prebuilt route /api/orgadmin/login, I can easily login.
Now, I have a flag in orgadmins say 'status' which can be either 'active' or 'inactive' based on which I have to defer user login.
I was thinking something with remote hooks like beforeRemote as below but it doesn't work:
//this file is in the boot directory
module.exports = function(orgadmin) {
orgadmin.beforeRemote('login', function(context, user, next) {
console.log(user)
// context.args.data.date = Date.now();
// context.args.data.publisherId = context.req.accessToken.userId;
next();
});
};
So what is the best way to accomplish this?
The user attribute will only be available if the request is coming with a valid access token. The attribute is unused for unauthenticated requests, which login is.
Here's a possible alternative:
module.exports = (OrgAdmin) => {
OrgAdmin.on('dataSourceAttached', () => {
const { login } = OrgAdmin;
OrgAdmin.login = async (credentials, include) => {
const accessToken = await login.call(OrgAdmin, credentials, include);
const orgAdmin = await OrgAdmin.findById(accessToken.userId);
if (orgAdmin.status !== 'active') {
OrgAdmin.logout(accessToken);
const err = new Error('Your account has not been activated');
err.code = 'NOT_ACTIVE_USER';
err.statusCode = 403;
throw err
}
return accessToken;
};
});
};
The above code overrides the login method and does the following:
Login the user, using loopback's built-in login
Take the response of login, which is an access token, and use it to get the user.
If the user is active, return the access token, satisfying the expected successful response of login.
If the user is not active, remove the access token that was created (which is what logout does), and throw an error.

Auth0 "service not found" error

I'm attempting to use Auth0 to issue JWT tokens for accessing my API (so that Auth0 handles all the OAuth and security concerns, etc., and my API just needs to check the token). When I try to test the Authorization Code flow for clients to receive an access token (using Node + Express), the following happens:
The authorization code request works fine, and the client is redirected back to my redirect_uri with the code appended to the query. All good.
The token request then always fails. If I include the audience parameter, the request returns an access_denied error with the following details: Service not found: {the audience parameter}, regardless of what value I set for the audience parameter.
If I don't include the audience parameter, I get a server_error with the message Service not found: https://oauth.auth0.com/userinfo.
I've checked every Auth0 setting and read every documentation page thoroughly, and so far nothing has worked. I've also tested the Authorization Code flow in Auth0's API debugger, and it worked fine. My test follows exactly the same parameters, and yet still receives an error requesting the token. I'm testing on localhost. The client credentials and implicit flows are working fine.
Here is a test endpoint I created which retrieves the authorization code from Auth0:
const qs = require('querystring');
const getCode = (req, res) => {
const params = {
audience, // the value of the API Audience setting for the client
client_id, // the client ID
redirect_uri, // the redirect_uri, which is also listed in the Allowed Callback URLs field
response_type: `code`,
scope: `offline_access open` // ask to return ID token and refresh token,
state: `12345`,
};
const authDomain = `mydomain.auth0.com/oauth`;
res.redirect(`${authDomain}/oauth/authorize?${qs.stringify(params)}`);
};
The redirect_uri then redirects to the following endpoint, where I make the request for the access token:
const https = require('https');
const callback = (req, res) => {
const body = {
client_id,
client_secret,
code: req.query.code,
grant_type: `authorization_code`,
redirect_uri, // same value as provided during the code request
};
const opts = {
headers: { 'Content-Type': `application/json` },
hostname: `mydomain.auth0.com`,
method: `POST`,
path: `/oauth/token`,
};
const request = https.request(opts, response => {
let data = ``;
response.on(`data`, chunk => { data += chunk; });
response.on(`error`, res.send(err.message));
response.on(`end`, () => res.json(JSON.parse(data))); // this executes, but displays the error returned from Auth0
});
request.on(`error`, err => res.send(err.message));
request.end(JSON.stringify(body), `utf8`);
};
Any suggestions as to what I might be doing wrong?
The issue was that I was calling the incorrect URL at Auth0. I mistakenly thought that both the authorization and token endpoints began with /oauth, when in fact the authorization endpoint is just /authorize, while the token endpoint is /oauth/authorize. Correcting the URLs in my code fixed the problem.
My solution was the identifier of the api was not found. If it is not exact it won't find it. I had an extra backslash on my 'audience' where the identifier didnt have one. pretty easy mistake but the error is not very clear in Auth0.
In my case, I was using auth0 react hooks. So the example code looked like this:
const getUserMetadata = async () => {
const domain = process.env.REACT_APP_AUTH0_DOMAIN
try {
const accessToken = await getAccessTokenSilently({
audience: `https://${domain}/api/v2/`,
scope: 'read:current_user',
})
console.log('accessToken', accessToken)
localStorage.setItem('access_token', accessToken)
setUserAuthenticated(true)
} catch (e) {
console.log('error in getting access token', e.message)
}
}
My solution to this was using by default Auth0 Audience value in audience field
const getUserMetadata = async () => {
const auth0audience = process.env.REACT_APP_AUTH0_AUDIENCE
try {
const accessToken = await getAccessTokenSilently({
audience: auth0audience,
scope: 'read:current_user',
})
console.log('accessToken', accessToken)
localStorage.setItem('access_token', accessToken)
setUserAuthenticated(true)
} catch (e) {
console.log('error in getting access token', e.message)
}
}
Because its stated in auth0 docs of configuring custom domains that, you need to use by default API audience
Source - https://auth0.com/docs/brand-and-customize/custom-domains/configure-features-to-use-custom-domains

Resources