nextauth v4 credentials provider, adding the raw token to the session - next-auth

I am new to nextauth credentials provider, and I have been following different tutorials on youtube and searching for answers here.
I have a web application using next.js and in it I have a bunch of rest apis to get data from mongodb. I have secured the api by accessing the token. I have used Postman to test the apis, and they work when I pass the raw token to in the Authorization header.
I need to get the raw token into the session object for the session call back in next-auth, so I then can call the apis from client side pages.
Any help would be appreciated.
In [...nextauth].js:
export default NextAuth({
providers: [
// Google Provider
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET
}),
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET
}),
CredentialsProvider({
id: "credentials",
name: "Credentials",
async authorize(credentials, req) {
console.log("In Authorization");
connectMongo().catch((error) => {
error: "Connection Failed...!";
});
// check user existance
const user = await Users.findOne({ email: credentials.email });
if (!user) {
throw new Error("No user found with this email");
}
// compare()
const checkPassword = await compare(
credentials.password,
user.password
);
// incorrect password
if (!checkPassword || user.email !== credentials.email) {
throw new Error("Email or Password don't match");
}
// check if user is enabled
if (user.active === AccountStatus.DISABLED) {
throw new Error(
"Account has been disabled. Please contact support to re-enable your account"
);
}
// Value returned will go into token property
//console.log("Returnng User Object", user);
return user;
}
})
],
session: {
strategy: "jwt",
maxAge: 60 * 60 * 24
},
callbacks: {
async jwt({ token, user, account, profile, isNewUser }) {
if (user) token.user = user;
if (account) token.accessToken = account.access_token;
return token;
},
async session({ session, token, user, account }) {
// Send properties to the client, like an access_token from a provider.
const { password, ...tokenPwdRemoved } = token.user;
session.user = tokenPwdRemoved;
return session;
}
},
pages: {
signIn: "/login"
}
});

Take a look at the Session callback:
callbacks: {
async session({ session, token, user }) {
// Send properties to the client, like an access_token from a provider.
session.accessToken = token.accessToken
return session
}
}
Keep in mind security concerns relating to the token and session.
Session callback
The session callback is called whenever a session is checked. By
default, only a subset of the token is returned for increased
security. If you want to make something available you added to the
token through the jwt() callback, you have to explicitly forward it
here to make it available to the client.

Related

finding the user while assigning new access token

I have a website where when user logsIn, they are assigned an access and a refresh token. When the access token is expried, a request to the server is made and checks if the refresh token is present in the global array in the database. If it is, a new access token is assigned to the user.
But I wanted to ask if should also check for the user by the information given by the refresh token when it is decoded. Or it is not necessary.
Please suggest me good practice and also tell me if something is wrong with my process.
routes.post("/newAccessToken", async (req, res) => {
const token = req.headers.cookie?.split("=")[1];
try {
const existingToken = await refreshTokens.findOne({
tokens: { $in: [token] },
});
if (existingToken) {
const email = await jwt.verify(token, process.env.RefreshTokenSecret);
if (email) {
const user = await userSchema.findOne({ email });
if (user) {
const newAccessToken = await jwt.sign(
{ email },
process.env.AccessTokenSecret
);
res.json({ newAccessToken });
}
} else res.json({ message: "token is invalid" });
} else res.json({ message: "No token found" });
} catch (error) {
console.log(error);
}
});

How to get the user from request in a dynamically created custom provider in NestJS?

Purpose
Create a custom provider that returns an axios instance that has interceptors that uses the attached authenticated user (from authguarded jwt-strategy) from request.
Context
I need to consume a third party oauth2 api. So I need intercepts for before request - to add the accessToken in the header - and for after request - to validated a possible ExpiredTokenException, where I'll need to perform a refresh token and redo the original request. This third party oauth2 api has a User level authentication. So my app is as well authenticated in user level - In my nestjs app I have my routes authguarded with jwt-strategy working propertly.
Problem
Considering that the HttpModule and HttpService exported from the #nestjs/common package have been deprecated, I trying to accomplish my goal creating a dynamically custom provider that returns an axios instance. The problem is that since this third party ouath2 api is user level authenticated, in this provider I need to retrieve from my database the accessToken/RefreshToken about the user authenticated in my app, so I'm trying to get them from the request, since after authenticated by passport, it's added to the request, but it seems that the factory is ran before that the authguard, so my req.user is undefined at the time.
Do not misunderstood the auths here: I have my app authenticating using jwt-strategy and I have in my database the accessToken/RefreshToken for the user in the third party api.
So here's my code:
Jwt-strategy
#Injectable()
export class JwtAccessTokenStrategy extends PassportStrategy(
Strategy,
'jwt-access-token',
) {
constructor(
readonly configService: ConfigService,
private moduleRef: ModuleRef,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('jwt.accessTokenSecret'),
passReqToCallback: true,
});
}
async validate(request: Request, payload: any) {
const contextID = ContextIdFactory.getByRequest(request);
const usersService = await this.moduleRef.resolve(UsersService, contextID);
const user = await usersService.findOne(payload.email);
console.log(user);
if (!user) throw new UnauthorizedException();
return user;
}
}
Controller
#Controller('gtc-me')
export class GtcMeController {
constructor(private readonly gtcMeService: GtcMeService) {}
#UseGuards(JwtAccessTokenAuthGuard)
#Get()
async me() {
return await this.gtcMeService.me();
}
}
Service
#Injectable()
export class GtcMeService {
constructor(#Inject('GTC_API') private readonly gtc: AxiosInstance) {}
async me() {
const { data } = await this.gtc.get('https://thirdpartyapi.com/api...');
...
}
}
Axios dynamically created custom provider
const gtcFactory = {
provide: 'GTC_API',
useFactory: async (
gtcUsersAuthService: GtcUsersAuthService,
req: Request,
) => {
const instance = axios.create();
console.log(req['user']);
instance.interceptors.request.use(
async (config) => {
const at = (await gtcUsersAuthService.findByUserId(req['user'].id))
.accessToken;
config.headers = {
Authorization: `Bearer ${at}`,
};
return config;
},
(error) => {
Promise.reject(error);
},
);
return instance;
},
inject: [GtcUsersAuthService, REQUEST],
};
#Module({
imports: [GtcUsersAuthModule],
providers: [gtcFactory],
exports: ['GTC_API'],
})
export class GtcApiModule {}
So requesting:
http://localhost/gtc-me
Console output:
undefined --printed from gtcFactory
{user object} --printed from JwtAccessTokenStrategy
So is there another way to accomplish my purpose here? I mean, how can I get the user from request?

Next-Auth with Provider.Credentials: How to implement when API is already returning a JWT Token?

I have a NextJS page where I try to implement Next-Auth.
I use credentials to login to my Rails API.
My API is returning (already) a JWT-Token. (so NextAuth must not create it)
How to implement the Provider.Credentials for [...nextauth].js in that case?
Flow "Diagram"
Next request ---> Next API (with Next-Auth) ---> Rails API (returning Token)
At the momemt I have these options:
providers: [
CredentialsProvider({
name: 'Email',
credentials: {
email: { label: "Email", type: "email", placeholder: "meine.email#domain.com" },
password: { label: "Passwort", type: "password" }
},
async authorize(credentials) {
// The 'url' is pointing to a Rails API endpoint which returns a JWT Token
const url = `${process.env.NEXT_PUBLIC_API_URL}/auth/login`;
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify(credentials),
headers: {
"Content-Type": "application/json" }
})
const user = await res.json()
// If no error and we have user data, return it
if (res.ok && user) {
// I SAW EXAMPLES RETURNING {"email": "blah#tst.com"}
return user // MY CONTENT {token: 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0LCJyb2xl…0.OAGiwjj9O_NsH02lIjA2D4HYZkmTQ3_SqtKcVgaIul0'}
}
// Return null if user data could not be retrieved
return null
}
})
]
}
A session_token is set in the browser, but that content is something (random?) what I dont have set. Where does this content come from if not from my token?
My Rails API Token Content:
{
"user_id": 4,
"roles": [
"user"
],
"exp": 1631096219
}
Next-Auth API Token Content:
{
"iat": 1631009819,
"exp": 1633601819
}
Do I have to decode my API token and reassamble that within the Provider.Credentials function?
I implement Next-Auth to provide more Authentications like Twitter and Co, but as well to make use of "useSession" instead of building everything of my own (Wont reinventing the wheel).
Add Callbacks.
export default NextAuth({
providers: [
CredentialsProvider({
name: "Email",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "meine.email#domain.com",
},
password: { label: "Passwort", type: "password" },
},
async authorize(credentials) {
// The 'url' is pointing to a Rails API endpoint which returns a JWT Token
const url = `${process.env.NEXT_PUBLIC_API_URL}/auth/login`;
const res = await fetch(url, {
method: "POST",
body: JSON.stringify(credentials),
headers: {
"Content-Type": "application/json",
},
});
const user = await res.json();
// If no error and we have user data, return it
if (res.ok && user) {
// I SAW EXAMPLES RETURNING {"email": "blah#tst.com"}
return user; // MY CONTENT {token: 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0LCJyb2xl…0.OAGiwjj9O_NsH02lIjA2D4HYZkmTQ3_SqtKcVgaIul0'}
}
// Return null if user data could not be retrieved
return null;
},
}),
],
callbacks: {
async jwt({ token, user, account, isNewUser }) {// This user return by provider {} as you mentioned above MY CONTENT {token:}
if (user) {
if (user.token) {
token = { accessToken: user.token };
}
}
return token;
},
// That token store in session
async session({ session, token }) { // this token return above jwt()
session.accessToken = token.accessToken;
//if you want to add user details info
session.user = { name: "name", email: "email" };//this user info get via API call or decode token. Anything you want you can add
return session;
},
},
});
So the user that you return from the credentials authorize function will get stored in the JWT (JSON Web Token - which is passed around with all authenticated requests) which is a hashed/encoded (not encrypted by default!) version of the object.
You can use https://jwt.io to debug and see whats in your JWT. You paste into that site's debugger field your eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0LCJyb2xl... value and it'll show you the { iat: 49835898: sub: 47897: email: 'abc#xyz.com' }, etc. value.
If your rails API is returning a JWT to you in that fetch call in the authorize function, you can use a library like jwt-decode to decode it right there, and append any values from it which you would like to "persist" in your application to the user object you're returning from the authorize function in next-auth.

Passport.js local strategy gives 401 error when trying to authenticate

The authentication process gives 401 bad request. Though the new user is saved to db with hashed password I noticed is saved to MongoDB Atlas. I'm using the local strategy. If I try "local-signup" for the register t I reach the secret route, but for the login route I still get the 401 bad request.
Or can there be an authenticaiton issue with mongodb atlas trying to access the credentials??
app.post("/register", function (req, res, next) {
var newUser = new User({
username: req.body.username
});
User.register(newUser, req.body.password, function (err, user, info) {
console.log(user);
if (err) {
return res.render("register");
} else {
// go to the next middleware
next();
}
res.status(401).send(info);
});
}, passport.authenticate('local', {
successRedirect: '/secret',
failureRedirect: '/login'
}));
app.post(
"/login",
passport.authenticate("local", {
successRedirect: "/secret",
failureRedirect: "/login"
}),
function (request, response) {}
);
Found the error. my misstake
passport.use(new localStrategy(User.authenticate));
shold be
passport.use(new localStrategy(User.authenticate()));

firebase signInWithCredential from FacebookAuthProvider error

Hi, I'm building a react-native app with Expo. Authentication is handled via Firebase. The user has the ability to sign up with Facebook. So the FB token needs to passed down to firebase. I'm testing in iOS. Firebase auth without FB is working. Facebook login works. Passing the FB token to firebase returns "auth/internal-error".
Firebase FB auth is enabled and has the correct details (app id, secret)
Facebook "Valid OAuth redirect URIs" is set to the one from Firebase
Client OAuth Login, Web OAuth Login are set to yes - and I toggled them for all cases with the same error.
Expo app has the correct facebookScheme, facebookAppId, facebookDisplayName in app.json
I'm using Expos "logInWithReadPermissionsAsync" function to get the users FB details.
Response:
{type: "success", token: "EAAaX8y1lhFkBAO1MJZCTDzSjVCOjAjZCVJrWnNhyUVsgqdLlZ…
yVnAWDPap1ZBHzJUWwK4VpZAZCtEH9grVcFebjK2ekxZCxmx", expires: 1517634962}
I parse the response with:
const provider = new firebase.auth.FacebookAuthProvider();
...
const credential = provider.credential(response.token);
const credential becomes:
{idToken: "EAAaX8y1lhFkBAO1MJZCTDzSjVCOjAjZCVJrWnNhyUVsgqdLlZ…
yVnAWDPap1ZBHzJUWwK4VpZAZCtEH9grVcFebjK2ekxZCxmx", providerId:
"facebook.com"}
I call firebase signInWithCredential with credential:
const auth = firebase.auth();
...
auth.signInWithCredential(credential)
.then((user) => {
console.log('signInWithCredential success', user.providerData);
})
.catch((err) => {
console.log('signInWithCredential error', err);
});
The error response is:
code: "auth/internal-error", message: "{"error":{"errors":
[{"domain":"global","reason":"invalid":400,"message":"A system error has
occurred"}}"}
I triple checked if all Firebase/Facebook settings and values are correct. It looks like a firebase issue as FB login works. Firebase auth with "createUserWithEmailAndPassword" also works. It's only "signInWithCredential" making trouble.
Full function:
import { Facebook } from 'expo';
import * as firebase from 'firebase';
....
const firebaseConfig = {
apiKey: 'xxxx',
authDomain: 'xxxx',
databaseURL: 'xxxx',
projectId: 'xxxx',
storageBucket: 'xxxx',
messagingSenderId: 'xxxx',
};
...
firebase.initializeApp(firebaseConfig);
...
const auth = firebase.auth();
const provider = new firebase.auth.FacebookAuthProvider();
....
// on botton press
handleFacebookButton() {
Facebook.logInWithReadPermissionsAsync(FACEBOOK_APP_ID, {
permissions: ['public_profile', 'email'],
})
.then((response) => {
console.log('logInWithReadPermissionsAsync success', response);
if (response.type === 'success') {
// Firebase credential is created with the Facebook access token.
const credential = provider.credential(response.token);
console.log('FB SUCCESS', credential);
auth.signInWithCredential(credential)
.then((user) => {
console.log('signInWithCredential success', user.providerData);
})
.catch((err) => {
console.log('signInWithCredential error', err);
});
} else {
console.log('something went wrong on facebook auth', response);
}
}).catch((err) => {
console.log('error', err);
this.setState({
error: err.message,
loading: false,
});
});
}