Describe the bug
I'm unable to get the credentials provider endpoint to work - requests to /api/auth/callback/credentials as per https://next-auth.js.org/providers/credentials are stuck in a pending state. The request comes in but I don't know why there isn't a response.
Nothing comes up on the default signin page though a Sign In button does show up if I add Github.
I use a custom basePath because my Next app is set up to receive requests at /p/*. Also use TypeScript but that shouldn't be an issue.
I am on version 2.0.0-beta.78
To Reproduce
Set up files as below:
[...nextAuth].ts
const options = {
site: 'http://localhost:4000',
basePath: '/p/api/auth',
session: {
jwt: true
},
providers: [
Providers.Credentials({
authorize: async (credentials) => {
// Logging code here does not print
if (true) {
return Promise.resolve({ })
} else {
return Promise.resolve(false)
}
}
})
]
}
Login code:
const onSubmit = (e) => {
e.preventDefault()
axios.request({
method: 'post',
url: `/p/api/auth/callback/credentials`,
data: {
email: formData.email,
password: formData.password
}
})
}
next.config.js:
module.exports = {
assetPrefix: '/p'
experimental: {
basePath: '/p'
},
env: {
NEXTAUTH_BASE_PATH: '/p/api/auth',
}
}
To be compatible with clients that don't use JavaScript, the endpoint expects a form submission rather than a JSON data submission.
If you use an HTML form or change the JavaScript so it submits a form (rather than JSON) then it should work - everything else there looks good!
To address this point, which I inadvertently skipped over:
Nothing comes up on the default sign in page though a Sign In button does show up if I add Github.
I appreciate this behaviour is confusing.
~The UI doesn't currently generate a sign in option for Credentials provider.~
~To support doing that in a flexible way that will work for everyone, we'd need to have a way for people to define what they should be.~
^ Update: This was correct at time of writing, but was subsequently added in v3!
I'm thinking it could be something like this:
e.g.
credentials: {
email: { label: "Email", type: "text ", placeholder: "[email protected]" },
password: { label: "Password", type: "password" }
}
PS: Thanks for all the information supplied with this issue - it's much easier to help folks with detailed information about the options being used and relevant config files, etc!
There has been no response on this so am closing this issue.
To confirm, this is not a bug in NextAuth.js but a bug in the JavaScript that was being used to sign in, which was submitting JSON rather than submitting a form.
As an enhancement, as of 2.0.0-beta.82 sign in forms for credentials providers are now automatically generated if details about the expected credentials are configured.
See #290 and the updated documentation for examples.
Thanks Ian, major oversight on my part.
@faizan-ali I appreciate you raising the issue! I'm really happy that credentials provider now has better documentation (although I still need to work on that, the current examples are a bit misleading as they omit the CSRF token) and that it now has built-in sign in form support! I think that will be less confusing for folks.
I came across this issue because I was using react-hook-form which has its own onSubmit handler that validates the form data and then executes a callback (e.g. the submit function). So I wasn't able to use the native form submit event. For anyone in a similar position, here's the workaround I came up with to post the form data:
const postForm = (path, params, method = 'post') => {
const form = document.createElement('form');
form.method = method;
form.action = path;
Object.keys(params).forEach((k) => {
const hiddenField = document.createElement('input');
hiddenField.type = 'hidden';
hiddenField.name = k;
hiddenField.value = params[k];
form.appendChild(hiddenField);
});
document.body.appendChild(form);
form.submit();
};
const handleLogin = async (data: LoginInputs) => {
// example for facebook login, replace 'facebook' with the relevant provider id
postForm('/api/auth/signin/facebook', data);
};
I tried a bunch of different ways of doing this with both the fetch and XMLHttpRequest APIs but apparently the headers associated with an actual form submission are important and can't be changed. Without this type of submission, the redirect back to the callbackurl doesn't work (seems to be an issue with the Sec-Fetch-Dest headers? not entirely sure). So that's why we've landed on a workaround that literally creates a new form. Not sure if this is the best way to do this - open to other suggestions! - but it works.
@iaincollins I'm running into this same issue when signin('credentials', values) where values is an object containing form data. On inspecting the request I see the body.
body: [Object: null prototype] {
csrfToken: 'ae700cec9b27de8249e99179cf9d896f38bffa2da683d9dce21c2dab6750d278',
callbackUrl: 'http://localhost:3001/sessions/signin',
email: '[email protected]',
password: 'topsecretpassword'
},
However, this does not make it to the authorize.
authorize: async (credentials) => {
console.log(credentials)
}
@bdesouza The actual signin() method for credentials in 2.0 is buggy :-(
The good news is, it's fixed in v3 beta!
There is even an example in the documentation.
@bscaspar Related: In v3 you can add the form parameter json: "true" to get the response as JSON object with a url property to work around this issue. This is what the the v3 client does for the same reason, as it seems redirect URLs are not readable in the response from fetch.
For compatibility with non-JavaScript clients the default behaviour is still to behave like a standard form and return an HTTP redirect header unless the the json: "true" is passed in the body of the form. This isn't a very elegant solution (and I'd like to find a better one in future) but is cross browser compatible.
The bugs in the client are fixed now, so probably still best to use that if you can, as it will take care of obtaining and sending the CSRF token as well.
Thanks @iaincollins! I switched to using v3 beta and everything started to work. Can't wait for the release!
Most helpful comment
@bdesouza The actual
signin()method for credentials in 2.0 is buggy :-(The good news is, it's fixed in v3 beta!
There is even an example in the documentation.
@bscaspar Related: In v3 you can add the form parameter
json: "true"to get the response as JSON object with aurlproperty to work around this issue. This is what the the v3 client does for the same reason, as it seems redirect URLs are not readable in the response from fetch.For compatibility with non-JavaScript clients the default behaviour is still to behave like a standard form and return an HTTP redirect header unless the the
json: "true"is passed in the body of the form. This isn't a very elegant solution (and I'd like to find a better one in future) but is cross browser compatible.The bugs in the client are fixed now, so probably still best to use that if you can, as it will take care of obtaining and sending the CSRF token as well.