I have the required fields for user login how to bypass the nextauth login page and perform the authorize method - authentication

I create a custom user registration because nextauth does not support registration. Everything works correctly but I do not know how after a successful registration of the user immediately log him in the credentials that he gave during registration.
As far as I can see, the signIn method from nextauth does not allow any credentials to be passed on to it. Redirects only to the login subpage.
I also did not find any endpoint that provides nextauth to which I can pass parameters so as to log in the user.
In fact, it is enough to call the authorize method that is in nextauth, unfortunately, there is no possibility to export it or there is no way to call it from the call to api level and it is a pity because with it I could log in the user.
User flow
User registers if the registration is successful, he is immediately logged in credentials that he provided during registration
My registration
async function handleRegister(
username: string,
email: string,
password: string
) {
const registerUser = await fetch(
`${process.env.API_URL}`,
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
username,
email,
password,
}),
}
);
const registerResponse = await registerUser.json();
if (registerResponse.user) {
// TODO: Login with NextAuth
}
}
[...nextauth].ts
export default NextAuth({
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
identifier: {
label: "Email or Username",
type: "text",
placeholder: "jsmith",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/auth/local`,
{
method: "POST",
body: JSON.stringify(credentials),
headers: { "Content-Type": "application/json" },
}
);
const user = await res.json();
if (res.ok && user) {
return user;
}
return null;
},
}),
],
session: {
strategy: "jwt",
},
jwt: {
maxAge: 60,
encode: async ({ secret, token }) => {
const encodedToken = jsonwebtoken.sign(token!, secret, {
algorithm: "HS256",
});
return encodedToken;
},
decode: async ({ secret, token }) => {
const decodedToken = jsonwebtoken.verify(token!, secret, {
algorithms: ["HS256"],
});
return decodedToken as JWT;
},
},
callbacks: {
jwt: async (token, user, account) => {
const isSignIn = user ? true : false;
if (isSignIn) {
token.jwt = user.jwt;
token.id = user.user.id;
token.name = user.user.username;
token.role = user.user.user_role;
token.email = user.user.email;
}
return Promise.resolve(token);
},
session: async ({ session, token }) => {
if (session.user) {
session.user.id = token.sub!;
}
return session;
},
},
secret: process.env.JWT_SECRET,
});

You need to login with credentials e.g
const response: SignInResponse | undefined = await signIn(
"credentials",
{
redirect: false,
email: "example#user.com",
password: "12345678",
}
);

Related

Next-auth how to decrypt session-token

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()
}

How can I set Next-Auth callback url? and next-auth session return null

I want to set login, logout callback url.
So, I set the callback url like this.
//signIn
const signInResult = await signIn("credentials", {
message,
signature,
redirect: false,
callbackUrl: `${env.nextauth_url}`,
});
//signOut
signOut({ callbackUrl: `${env.nextauth_url}`, redirect: false });
But, When I log in, I look at the network tab.
api/auth/providers, api/auth/callback/credentials? reply with
callbackUrl(url) localhost:3000
It's api/auth/callback/credentials? reply.
It's api/auth/providers reply
and api/auth/session reply empty object.
When I run on http://localhost:3000, everything was perfect.
But, After deploy, the login is not working properly.
How can I fix the error?
I added [...next-auth] code.
import CredentialsProvider from "next-auth/providers/credentials";
import NextAuth from "next-auth";
import Moralis from "moralis";
import env from "env.json";
export default NextAuth({
providers: [
CredentialsProvider({
name: "MoralisAuth",
credentials: {
message: {
label: "Message",
type: "text",
placeholder: "0x0",
},
signature: {
label: "Signature",
type: "text",
placeholder: "0x0",
},
},
async authorize(credentials: any): Promise<any> {
try {
const { message, signature } = credentials;
await Moralis.start({
apiKey: env.moralis_api_key,
});
const { address, profileId } = (
await Moralis.Auth.verify({ message, signature, network: "evm" })
).raw;
if (address && profileId) {
const user = { address, profileId, signature };
if (user) {
return user;
}
}
} catch (error) {
console.error(error);
return null;
}
},
}),
],
pages: {
signIn: "/",
signOut: "/",
},
session: {
maxAge: 3 * 24 * 60 * 60,
},
callbacks: {
async jwt({ token, user }) {
user && (token.user = user);
return token;
},
async session({ session, token }: any) {
session.user = token.user;
return session;
},
async redirect({ url, baseUrl }) {
// Allows relative callback URLs
if (url.startsWith("/")) return `${baseUrl}${url}`;
// Allows callback URLs on the same origin
else if (new URL(url).origin === baseUrl) return url;
return baseUrl;
},
},
secret: env.nextauth_secret,
});

Vue-Authenticate for Github Login

I have used the Vue-Authenticate plugin for the social login and this is my configuration for the Github Login
providers: {
github: {
clientId: 'my Id',
redirectUri: 'https://' + process.env.PROJECT_DOMAIN,
responseType: 'token',
authorizationEndpoint: 'https://github.com/login/oauth/authorize',
},
},
And in the method through which is calling the method after authentication is
authenticate(provider) {
const this_ = this
this.$auth.authenticate(provider).then(function () {
const token = this_.$auth.getToken()
if (provider === 'github') {
const options = {
headers: {
Authorization: 'token ' + token,
},
}
this_.$http
.post('https://api.github.com/user', options)
.then(function (response) {
if (response.status === 200) {
const { email, name, picture } = response.data
const data = {
email,
name,
image: picture.data.url,
}
this_.createOAuth2User(data, provider)
}
})
.catch((error) => {
console.log(error)
})
}
})
},
I am able to receive an access token after successful authentication but when I try to use that token to access the user details and hit the https://api.github.com/user API I am getting 401 error. So is there something I am missing while authentication with github?

User authentication error using express not working

So I'm having trouble authenticating a user login using express for the backend. If I do a simple res.send I could get a response in postman. but if I do a check if the user and password check and generate a token if says error 401 invalid usernames and password. mind the tokens work for register and update profiles. I also attached the user schema.
Simple Approach
const authUser = asyncHandler(async (req, res) => {
const { email, password } = req.body
res.send({ email, password })
})
When I try to check and generate a token
const authUser = asyncHandler(async (req, res) => {
const { email, password } = req.body
const user = await User.findOne({ email })
if (user && (await user.matchPassword(password))) {
res.json({
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin,
token: generateToken(user._id),
})
} else {
res.status(401)
throw new Error('Invalid Email or password')
}
})
Also here's my user schema using mongoose and added a function to check password since in the database it's encrypted using bcrypt.js
import mongoose from 'mongoose'
import bcrypt from 'bcryptjs'
const userSchema = mongoose.Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
isAdmin: {
type: Boolean,
required: true,
default: false,
},
},
{
timestamps: true,
}
)
userSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password)
}
const User = mongoose.model('User', userSchema)
export default User
You see, your code is absolutely correct, but only if you have saved the user correctly in your model at the first. In addition, the password used during storage is hashed in the same way and from the same module.
Now you have to change your checking terms because there may not really be a user with this email in the database to match your password and return true value.
import mongoose from 'mongoose'
import bcrypt from 'bcryptjs'
const userSchema = mongoose.Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
isAdmin: {
type: Boolean,
required: true,
default: false,
},
},
{
timestamps: true,
}
)
userSchema.pre('save', function (next) {
if (this.password) {
bcrypt.hash(this.password, bcrypt.genSaltSync(15), (err, hash) => {
if (err) console.log(err);
this.password = hash;
next();
});
}
}
userSchema.methods.Save = async (data) => {
let model=new User(data)
await model.save();
}
userSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password)
}
const User = mongoose.model('User', userSchema)
export default User
and update this code:
const authUser = asyncHandler(async (req, res) => {
const { email, password } = req.body
const user = await User.findOne({ email })
if (user) {
if(await user.matchPassword(password)) {
res.json({
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin,
token: generateToken(user._id),
})
} else {
res.status(401)
throw new Error('Invalid password')
}
} else {
res.status(401)
throw new Error('Invalid Email')
}
})
Now you know which part of the job is the problem. No user or no wrist password

JWT Token doesn't expire after a certain time

Why my jwt token doesn't expire after 1 hour?
I've noticed that it doesn't expire when I forgot to logout my account in my admin panel that I created in vuejs with vuex.
here is my API that I created in ExpressJS using bcrypt and express-jwt for token.
router.post('/login', (req, res) => {
let sql = "SELECT * FROM AUTHENTICATION WHERE email = ?";
myDB.query(sql, [req.body.email, req.body.password], function (err, results) {
if (err) {
console.log(err);
} else {
if (!results) {
res.status(404).send('No user found.')
} else {
try {
let passwordMatched = bcrypt.compareSync(req.body.password, results[0].password);
if (passwordMatched) {
// Passwords match
let token = jwt.sign({ id: results.id }, config.secret, {
expiresIn: '1h'
});
res.status(200).send({ auth: true, token: token, user: results });
} else {
//Password doesn't match
return res.status(401).send({ auth: false, token: null });
}
} catch (error) {
res.send({ Success: false })
}
}
}
})
});
here's my login in vuex where I received the token from my backend.
import axios from 'axios';
const state = {
status: '',
token: localStorage.getItem('token') || '',
user: {}
};
const getters = {
isLoggedIn: state => !!state.token,
authStatus: state => state.status,
};
const mutations = {
auth_request(state) {
state.status = 'loading'
},
auth_success(state, token, user) {
state.status = 'success'
state.token = token
state.user = user
},
auth_error(state) {
state.status = 'error'
},
logout(state) {
state.status = ''
state.token = ''
},
};
const actions = {
login({ commit }, user) {
return new Promise((resolve, reject) => {
commit('auth_request')
axios({ url: 'http://localhost:9001/login/login', data: user, method: 'POST' })
.then(resp => {
const token = resp.data.token
const user = resp.data.user
localStorage.setItem('token', token)
// Add the following line:
axios.defaults.headers.common['Authorization'] = token
commit('auth_success', token, user)
resolve(resp)
})
.catch(err => {
commit('auth_error')
localStorage.removeItem('token')
reject(err)
})
})
}
};
EDIT: Added vuejs code for login
thanks for the help guys!
Your JWT token is just an encoded + signed JSON with relevant fields such as expiresIn, iat.
While it may contain the expiresIn field, it does not mean that the backend server will honour it.
Logic needs to be written in the backend server to parse the timestamp, do comparison with the current time to determine whether it has expired. If it is expired, the backend should return a response code of 401 Unauthorized to tell the frontend (your Vue client) that the token is no longer valid.
What you can do is to put the expiry-checking logic in a middleware to look into the request headers' Authorization field.