Credentials provider returns undefined for account #2479
-
I'm trying to write an auth system using JWT with a Django backend. The authorization works for the most part, but I need to have refresh tokens working and the session to look identical. I've taken the example in the docs and have tried to make it work within my own flow, but I keep running into snags. I have a check to see if a user and account are coming into the JWT callback. It works with the Google provider because it returns an account object and a user object, however, the credentials provider only returns a user object. How can I get the credentials provider to return an account object similar to the google provider? Is this possible? My credentials flow also returns a weird session object like this: expires: "2021-09-01T20:57:40.223Z"
user:
accessToken: "tokencode"
access_token: "tokencode"
email: "[email protected]"
expires: null
id: 15
name: "first last"
refreshToken: "tokencode"
refresh_token: "tokencode"
user:
email: "[email protected]"
first_name: ""
last_name: ""
pk: 15 While the google session is expires: "2021-09-01T21:00:10.962Z"
user:
accessToken: "tokencode"
email: "[email protected]"
expires: 1627941609064
id: "112183534058500471612"
image: "imageurl"
name: "ddd ssss"
refreshToken: "tokencode" The credentials flow has duplicate values and I'm not sure where it's coming from. The Here's the import axios from 'axios';
import { NextApiRequest, NextApiResponse } from 'next';
import NextAuth, {
Account,
CallbacksOptions,
Profile,
Session,
TokenSet,
} from 'next-auth';
import Providers from 'next-auth/providers';
import { AuthenticatedUser } from '../../../interfaces/user';
import credentialsRefreshToken from './credentialsRefreshToken';
import googleRefreshToken from './googleRefreshToken';
const GOOGLE_AUTHORIZATION_URL =
'https://accounts.google.com/o/oauth2/v2/auth?' +
new URLSearchParams({
prompt: 'consent',
access_type: 'offline',
response_type: 'code',
});
/**
* Takes a token, and returns a new token with updated
* `accessToken` and `accessTokenExpires`. If an error occurs,
* returns the old token and an error property
*/
async function refreshAccessToken(token: TokenSet, provider: string) {
if (provider === 'google') {
return googleRefreshToken(token);
}
if (provider === 'credentials') {
return credentialsRefreshToken(token);
}
}
const providers = [
Providers.Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorizationUrl: GOOGLE_AUTHORIZATION_URL,
}),
Providers.Credentials({
id: 'credentials',
name: 'Credentials',
credentials: {
email: { label: 'Email', type: 'text', placeholder: '[email protected]' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials, req) {
const res = await axios.post(
'http://127.0.0.1:8000/api/auth/login/',
{
password: credentials.password,
username: credentials.email,
email: credentials.email,
},
{
headers: {
accept: '*/*',
'Content-Type': 'application/json',
},
}
);
if (res.data.user) {
res.data.id = res.data.user.pk;
res.data.email = res.data.user.email;
res.data.name = `${res.data.user.first_name} ${res.data.user.last_name}`;
// res.data.refreshToken = res.data.refresh_token;
return res.data;
} else {
throw new Error('error message');
}
},
}),
];
const callbacks: CallbacksOptions = {};
callbacks.signIn = async function signIn(user: any, account: Account) {
if (account.provider === 'google') {
const { accessToken, idToken } = account;
// Make the post req to the DRF api backend
try {
const res = await axios.post(
// use sep .ts .json file to store url endpoints
'http://127.0.0.1:8000/api/social/login/google/',
{
access_token: accessToken,
id_token: idToken,
}
);
// extract the returned token from the DRF backend and add it to the `user` object
const { access_token, refresh_token } = await res.data;
user.accessToken = access_token;
user.refreshToken = refresh_token;
return true;
} catch (err) {
throw new Error(err);
}
}
// account, when sent from Credentials will have an id and type of `credentials`
if (account.id === 'credentials') {
user.accessToken = user.access_token;
user.refreshToken = user.refresh_token;
return user;
}
return false;
};
callbacks.jwt = async function jwt(
token: any,
user: AuthenticatedUser,
account: Account
) {
// initial sign in
if (account && user) {
return {
accessToken: account.accessToken,
accessTokenExpires: Date.now() + account?.expires_in * 1000,
refreshToken: account.refresh_token,
user,
};
}
// Return previous token if the access token has nto expired yet
if (Date.now() < token.accessTokenExpires) {
return token;
}
// Access token has expired, try to update it
if (account?.type === 'oauth' && account?.provider === 'google') {
return googleRefreshToken(token);
}
return credentialsRefreshToken(token);
};
callbacks.session = async function session(session: Session, user: AuthenticatedUser) {
// console.log(user);
session.user = user.user;
session.user.expires = user.accessTokenExpires;
// this session is getting an `expires` attribute from somewhere with an incorrect time.
return session;
};
const options = {
providers,
callbacks,
};
const AuthenticationRequest = (req: NextApiRequest, res: NextApiResponse) =>
NextAuth(req, res, options);
export default AuthenticationRequest; The googleRefreshToken function: import axios from 'axios';
import { TokenSet } from 'next-auth';
export default async function googleRefreshToken(token: TokenSet) {
try {
const url =
'https://oauth2.googleapis.com/token?' +
// @ts-ignore
new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
grant_type: 'refresh_token',
refresh_token: token.refreshToken,
});
const refreshedTokens = await axios.get(url, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
method: 'POST',
});
if (refreshedTokens.statusText !== 'OK' && refreshedTokens.status !== 200) {
throw refreshedTokens;
}
return {
...token,
accessToken: refreshedTokens.data.access_token,
accessTokenExpires: Date.now() + refreshedTokens.data.expires_in * 1000,
refreshToken: refreshedTokens.data.refresh_token ?? token.refreshToken, // Fall back to old refresh token
};
} catch (error) {
console.log(error);
return {
...token,
error: 'RefreshAccessTokenError',
};
}
} credentialsRefreshToken function: import { TokenSet } from 'next-auth';
export default async function credentialsRefreshToken(token: TokenSet) {
console.log('cred refresh');
try {
const url = 'http://127.0.0.1:8000/api/auth/token/refresh/';
const res = await fetch(url, {
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
body: JSON.stringify({
refresh: token.user.refreshToken,
}),
});
const refreshedTokens = await res.json();
if (!res.ok) {
throw refreshedTokens;
}
return {
...token,
accessToken: refreshedTokens.access_token,
accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, // Fall back to old refresh token
};
} catch (error) {
console.log(error);
return {
...token,
error: 'RefreshAccessTokenError',
};
}
} I've been piecing this auth workflow together and it sort of seems like I'm doing something wrong with the JWT and Session callbacks, but I can't figure out what I'm doing wrong. Thanks! |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
I'm realizing that I misunderstood some of the documentation and the credentials provider isn't supposed to return an account object similar to google. It seems to return an account object but only with a I don't know how to mark this issue as answered or delete it. |
Beta Was this translation helpful? Give feedback.
I'm realizing that I misunderstood some of the documentation and the credentials provider isn't supposed to return an account object similar to google. It seems to return an account object but only with a
type
andid
, which I've been able to utilize and solve my problems. As for the duplicate values, I just reworked the credentials flow to delete the duplicates.I don't know how to mark this issue as answered or delete it.