I am using next js. I am using next-auth for authentication. it is a MERN stack project.
Question: How can I get jwt token and send it to my backend using next-auth and axios.
(session.jwt) is giving me undefined.
here is my nextauth.js file:
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { MongoDBAdapter } from '#next-auth/mongodb-adapter';
import clientPromise from '../../../lib/mongodb';
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
callbacks: {
session: async ({ session, user }) => {
if (session?.user) {
session.user.id = user.id;
}
return session;
},
},
adapter: MongoDBAdapter(clientPromise),
secret: process.env.JWT_SECRET,
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60, // the session will last 30 days
},
});
The example in their docs persists the provider (account) access token in the jwt callback and then includes it to the session object:
callbacks: {
async jwt({ token, account }) {
if (account) {
token.accessToken = account.access_token;
}
return token;
},
async session({ session, token, user }) {
if (session?.user) {
session.user.id = user.id;
}
return {
...session,
accessToken: token.accessToken
};
},
}
However, if you want the raw JWT (not the provider access token) and you're server side you can use the getToken function:
export async function getServerSideProps(context) {
const token = await getToken({ req: context.req, raw: true });
// ...
}
Then to use it in your requests, here in the authorization header for example):
const config = {
headers : {
'Authorization' : `Bearer ${token}`
}
}
axios.get('http://web.com/api', config);
Related
I'm using zendesk OAuth for authorization. I'm using the MERN stack and the current implementation works like this,
User clicks login and redirected to zendesk
once the user signs I get redirected back to /callback path
Where I sent another request to get an auth token
After I get the token I redirect the user to frontend as ?token=XXXX attached to the URL
Is this the correct way? How should I proceed with the token should I keep it in session storage? It's not a good idea to expose the token?
export const authCallback = (req: Request, res: Response): void => {
const body = {
grant_type: 'authorization_code',
code: req.query.code,
client_id: process.env.ZENDESK_CLIENT_ID,
client_secret: process.env.ZENDESK_SECRET,
}
axios
.post(`https://${process.env.SUBDOMAIN}.zendesk.com/oauth/tokens`, body, {
headers: {
'Content-Type': 'application/json',
}
})
.then((response) => {
const token = response.data.access_token
return res.redirect(`${process.env.ORIGIN}?token=${token}`)
})
.catch((err) => {
return res.status(400).send({ message: err.message })
})
}
Either use express-session and store the token on the server in req.session.token:
(response) => {
req.session.token = response.data.access_token;
req.session.save(function() {
res.redirect(`${process.env.ORIGIN}`)
});
}
Or send the token in a session cookie directly:
(response) => {
res.cookie("token", response.data.access_token, {
httpOnly: true,
secure: true,
sameSite: "None"
});
res.redirect(`${process.env.ORIGIN}`)
}
I am trying to pass res from my context into a resolver so that I can call context.res.cookie in my signin function and then send an http only cookie. I have the following code which I am not seeing the cookie added on the client but the sign in function is working besides that:
const resolvers = {
Mutation: {
signin: async (_, { email, password }, context) => {
const user = await User.findOne({ email: email });
if (!user) {
throw new Error("No such user found");
}
const valid = bcrypt.compare(password, user.password);
if (!valid) {
throw new Error("Invalid password");
}
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET,
{
expiresIn: "30m",
});
context.res.cookie("token", token, {
httpOnly: true,
secure: true,
maxAge: 8600,
});
return {
token,
user,
};
},
},
};
I have shortened the above code but originally I am returning the JWT token and mongodb user, I am trying to also add the http cookie of the same token (it will be a different token later when I sepearte access and refresh token).
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req, res }) => {
/* Authentication boiler plate */
return { isAuthenticated, res };
},
});
The above code is just how I am passing the res, not sure if its needed but just in case.
The following is how the function will be called from the front end:
export const Login = () => {
const SIGN_IN = gql`
mutation Signin($email: String!, $password: String!) {
signin(email: $email, password: $password) {
token
user {
id
name
email
}
}
}
`;
const [signIn, { error, loading, data }] = useMutation(SIGN_IN);
const signInFunction = async () => {
signIn({
variables: {
email: email,
password: password,
},
});
};
if (data) {
return <Navigate to="/" />
}
};
So I needed to slightly change both my client and my server to solve my issue. On the client in apollo-client I needed to change my apolloClient from this:
const apolloClient = new ApolloClient({
uri: "http://localhost:3001/graphql",
cache: new InMemoryCache(),
});
to this:
const apolloClient = new ApolloClient({
uri: "http://localhost:3001/graphql",
cache: new InMemoryCache(),
credentials: "include",
});
Now on the server I needed to add cors like this:
const server = new ApolloServer({
typeDefs,
resolvers,
cors: {
origin: "http://localhost:3000",
credentials: true,
},
context: async ({ req, res }) => {
/* insert any boilerplate context code here */
return { isAuthenticated, res };
},
});
Thus passing res to the resolver this way works perfectly fine. However when I was getting the cookie from server now it would get deleted if I refreshed the page thus I needed an explicit expiration date, thus I changed my cookie from:
context.res.cookie("token", token, {
httpOnly: true,
secure: true,
maxAge: 8600,
});
to (24 hour expiration):
context.res.cookie("token", token, {
httpOnly: true,
secure: true,
expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
});
Some notes on this solution: On the client when you add the credentials: "include", you NEED to also add the cors on the backend otherwise nothing will work, however if you remove both they will communicate fine just without cookies. Also if you add the cors and not the include nothing will break but you will not receive the cookies.
Finally this post helped me find the solution, however I did not need to setup express middleware or use apollo-link-http library as you can see above in my solution, however the post may still be helpful.
I'm a bit confused on the implementation of the credentials provider and the redirects. The documentation says that the credentials provider doesn't support a callback and its for the OAuth providers. This is fine. However, instead of staying on the page and flashing an error message or even logging in like in this video it redirects to https://localhost/api/auth/callback/[credentials-provider-name]. Which doesn't even include the port I'm working with. If I explicitly set an id it uses that at the end of the url in instead.
This is what I have for the provider
import NextAuth from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials"
export default NextAuth({
// Configure one or more authentication providers
providers: [
CredentialsProvider({
credentials: {
username: { label: "Username", type: "text", placeholder: "someuser69" },
password: { label: "Password", type: "password" },
},
name: "User Pass",
type: "credentials",
async authorize(credentials, req) {
// Add logic here to look up the user from the credentials supplied
return {
id: 2,
name: "user",
email: "user#gmail.com",
}
return null;
}
})
// ...add more providers here
],
callbacks: {
async jwt({ token, account }) {
// Persist the OAuth access_token to the token right after signin
if (account) {
token.accessToken = account.access_token
}
return token
},
async session({ session, token, user }) {
// Send properties to the client, like an access_token from a provider.
session.accessToken = token.accessToken
return session
},
async redirect({ url, baseUrl, }) {
console.log("");
return baseUrl;
},
async signIn({ user, account, profile, email, credentials }) {
return '/home';
}
},
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60,
},
secret: "CHANGE!!!",
jwt: {
maxAge: 60 * 60 * 24 * 30,
secret: "afdsfi",
},
})
I've looked through the docs and I'm not sure if I'm making some massive oversight, here. But some of my major confusions are:
Where is this callback set and how do I turn in off in the default provider (if possible).
I don't think the authorize function works. If I put a console log in it. It doesn't print to the terminal. So I don't even know if it's being called.
The issue is that I hadn't set the NEXTAUTH_URL variable correctly. The module apparently appends https if the protocol isn't set in the provided address. This is the case whether you are using 127.0.0.1 or localhost. The solution to fixing the callback issues is to pass in the unsecured http protocol if you're using a local address for testing or development purposes like so:
NEXTAUTH_URL='http://127.0.0.1:3001'
I am trying to add routes authentication for that I am using express-jwt I added this middleware to protect post creation routes. But while testing I get the error in postman.
error
TypeError: Cannot read property 'authorization' of undefined<br> at Object.getTokenFromHeaders [as getToken]
These are my code for express jwt
auth.js
import jwt from 'express-jwt';
const getTokenFromHeaders = (req) => {
const { headers: { authorization } } = req;
console.log(authorization); <----- in this log i am getting token
if(authorization && authorization.split(' ')[0] === 'Token') {
return authorization.split(' ')[1];
}
return null;
};
const auth = {
required: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
}),
optional: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
credentialsRequired: false,
}),
};
module.exports = auth;
routes.js
routes.post('/post', auth.required, postController.post);
You have an error on this line:
const { headers: { authorization } } = req.body;
Because headers prop is on req object, not on req.body, so it should be like this instead:
const { headers: { authorization } } = req;
use const authorization = req.headers.authorization it should solve the problem
This solution helped me out:
const { headers: { authorization } } = req;
const token = authorization && authorization.split(" ")[1];
I've an issue on using an access token in hapi.js. I'm unable to understand how I can use that token to authenticate. I'm following this article dwyl/hapi-auth-jwt2. I'm using mongodb as my database. But until I send my request like this http://localhost:8000/restricted?token=mycreatedtoken, I can't log into {auth: 'jwt'} pages. But sending the request like this does not seem right. So how can I use that token? Don't I have to save that in local storage or a database to access? This is my code:
app.js
const jwt = require('jsonwebtoken');
await server.register(require('hapi-auth-jwt2'));
server.auth.strategy('jwt', 'jwt', {
key: 'NeverShareYourSecret',
validate: validate,
verifyOptions: { algorithms: ['HS256'] }
});
server.auth.default('jwt');
validate function:
const validate = async (decoded, req) => {
let user = await User.findOne({ _id: decoded.id });
if (user) {
req.user = user;
return { isValid: true };
} else {
return { isValid: false };
}
};
for login:
method: 'POST',
path: '/login',
config: { auth: false },
handler: async function(req, h) {
try {
let { username, password } = req.payload;
let student = await student.findOne({
username
});
let validUser = student && (await bcrypt.compareSync(password,student.password));
if (validUser) {
let token = jwt.sign({ id: user.id }, 'mysecretkey');
console.log('tpken'+token);
// return h.view('welcome');
return { token };
} else {
return boom.unauthorized('incorrect pass');
}
}
}
signup
method: 'POST',
path: '/student',
config: { auth: false },
handler: async function(req, h) {
try {
let salt = bcrypt.genSaltSync(10);
req.payload.password = bcrypt.hashSync(req.payload.password, salt);
let student = new User(req.payload);
let result = await student.save();
const expiresIn = 24 * 60 * 60;
let token = jwt.sign({ id: result.id }, 'mysecretkey',{ expiresIn: expiresIn
});
return {token} ;
}
}
this path is using jwt token.
{
method: 'GET',
path: '/register',
config: { auth: 'jwt' },
handler: async (request, h) => {
try {
return h.view('student');
} catch(err){
return h.response(err).code(500);
}
}
}
Could you please share your validate function? I understand that you can generate the JWT token. In order to user that token to authenticate your request, you need to send that token with "Authorization" header on your requests to your server.
I am using react for frontend and this is my setup to send JWT token to server.
import axios, {AxiosInstance} from 'axios';
const createClient: () => AxiosInstance = () => {
const options = {
baseURL: process.env.REACT_APP_API_URL,
responseType: 'json',
withCredentials: true,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Authorization': ''
},
};
const instance = axios.create(options);
// Set the AUTH token for any request
// ref: https://stackoverflow.com/questions/43051291/attach-authorization-header-for-all-axios-requests
instance.interceptors.request.use(function (config) {
const token = localStorage.getItem("USER_JWT_TOKEN");
config.headers.Authorization = token ? `Bearer ${token}` : '';
return config;
});
return instance;
};
export default createClient();
Then when I make requests with this setup, automatically axios sends authentication headers in my all requests.
import apiClient from "./apiClient";
const results = await apiClient.get(`/users`);
There is the curl preview of the request that I copied from chrome's network panel.
curl 'https://myserver.com/users' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36' -H 'Accept: application/json, text/plain, */*' -H 'Referer: https://myserver.com/' -H 'Origin: https://myserver.com' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...... long JWT token string here' -H 'X-Requested-With: XMLHttpRequest' --compressed