Describe the bug

When using a custom provider, the docs show that profile and profileUrl are optional, however the flow breaks without them.
To Reproduce
Steps to reproduce the behavior.
Any Custom Provider configured without profile and/or profileUrl.
Expected behavior
Optional Parameters should not be required.
My ideal would be for them to actually be optional. I have a stripe auth flow but it's only issue is there's no profile url to call.
Similarly, I am having an issue adding a custom provider for Todoist. (Presumably like Stripe), Todoist does not offer a profile URL endpoint at all, which makes it impossible to use it as a custom provider as far as I can tell.
Example config, which does not work:
{
id: 'todoist',
name: 'Todoist',
type: 'oauth',
version: '2.0',
scope: 'data:read_write',
accessTokenUrl: 'https://todoist.com/oauth/access_token',
authorizationUrl: 'https://todoist.com/oauth/authorize',
clientId: process.env.TODOIST_CLIENT_ID,
clientSecret: process.env.TODOIST_CLIENT_SECRET,
}
It looks like Stripe does not follow OAuth 2.0 convention to return user profile information with an access token and instead overloads the the OAuth Token response to include stripe_user_id along with the access_token.
If that is true (as it seems to be) this would require bespoke handling. This is something that is not currently supported but has come up for providers with similar issues.
While Stripe is a big enough use case that supporting unconventional behaviour is desirable (and something we should aim for) it's worth asking them why it's like this and if they plan to address this, as it would certainly be much easier for them to address by simply providing an endpoint that returns customer info based on the access token provided.
In the case of Todoist it looks like https://todoist.com/API/v7/sync can be used to get user profile information, with some custom headers. As it is also non-standard I think the same thing applies - it's not currently supported it but could be in future.
I'm very much an advocate for putting pressure on these commercial companies to support a standard OAuth 2.0 / OpenID flow for something as basic as getting user info and not to do design unconventional or proprietary APIs, which effectively pushes free labour on to open source maintainers, so if folks using these APIs could also raise the issue with them that would be great.
The most grating thing is that it's incredibly easy for them to support returning user info from an endpoint.
To be clear, I'm 100% up for supporting them in NextAuth.js, I just think we should also call companies on it.
I do agree that it would be great for these companies to support profile endpoints and can make the request for Todoist to include one in their new REST API or Sync API (they're on v8 now, v7 is an older endpoint), but it would be nice to see generic support for services without these profile endpoints available, or for more flexibility on our side where we do not have to provide an actual HTTP endpoint to be called.
I'm not very familiar with the OAuth spec, but are profiles required for a truly compliant implementation?
As a temporary solution to this, I'm evaluating either creating my own, separate OAuth flow for Todoist (which is a pain) or create some sort of dummy HTTP endpoint on my own web app that can return some stub profile information to link the Todoist account to the current user. Does that sound like a feasible workaround?
I'm not very familiar with the OAuth spec, but are profiles required for a truly compliant implementation?
By convention (on 9 out of 10 providers) user info is returned from a dedicated endpoint, using the user-specific Access Token returned from the Token endpoint, so most of the time it's straightforward because they are mostly very similar.
Sadly, returning user profile data is not actually in the OAuth 2 spec so some teams have rolled their own approach rather than going with convention (because the spec doesn't actually say not to).
Thankfully the OpenID Connect spec attempts to rectify this by defining behaviour for a userinfo endpoint - although even just the Core spec does a lot else besides so smaller orgs tend not to aim for that as it requires more work (though is well supported by large providers, like Google, Apple, Microsoft, Amazon, etc).
Getting all providers to support either the convention that the majority of providers support - if not OpenID Connect itself - is definitely helpful; a dedicated endpoint that returns JSON or a JWT is much easier to work with.
I'm sure we can come up with a design that will work for other providers in the longer term, we just might want to take our time to do it, to see if we can cover all the use cases we know about (and see if there is any scope to improve things currently).
… create some sort of dummy HTTP endpoint on my own web app that can return some stub profile information to link the Todoist account to the current user. Does that sound like a feasible workaround?
I think right now that's a very sensible approach generally, as a way of getting provider info.
For anyone curious and searching for how I ended up fixing the issue for Todoist, my provider config now looks like this:
{
id: 'todoist',
name: 'Todoist',
type: 'oauth',
version: '2.0',
scope: 'data:read_write',
accessTokenUrl: 'https://todoist.com/oauth/access_token',
authorizationUrl: 'https://todoist.com/oauth/authorize',
clientId: process.env.TODOIST_CLIENT_ID,
clientSecret: process.env.TODOIST_CLIENT_SECRET,
idToken: false,
profileUrl: 'http://localhost:3000/api/oauth/todoist/profile',
profile: profile => ({
id: profile.user.id,
name: profile.user.full_name,
email: profile.user.email
})
}
Then, my /api/oauth/todoist/profile endpoint:
const endpoint = 'https://api.todoist.com/sync/v8/sync';
/**
* Special proxy endpoint that takes in the access token from Todoist and passes it to their Sync v8 endpoint to get
* the user info, and then returns it in JSON for NextAuth to consume.
*
* @param req
* @param res
*/
export default async (req, res) => {
const [ _, token ] = req.headers['authorization'].split(' ');
const body = [
'token=' + token,
'sync_token=*',
'resource_types=["user"]',
].join('&');
const syncResponse = await fetch(endpoint,
{ method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body });
const json = await syncResponse.json();
res.json(json);
}
Works perfectly and I am able to link Todoist to my app with no issues and with the profile information from their sync endpoint.
While I do agree that there should be a standard that everyone should follow and pressure on the big actors is nice, documentation that does not match the actual implementation is quite confusing (as you get an error if you remove profileUrl and profile in a custom provider). Could perhaps they be sat to required in the docs with a note linking to this issue so that people who wonder why they are required can gain insight?
Hi there! It looks like this issue hasn't had any activity for a while. It will be closed if no further activity occurs. If you think your issue is still relevant, feel free to comment on it to keep ot open. Thanks!
I agree with @FluidSense that at the very least, the docs should be updated to reflect that profileUrl is NOT optional.
Hi, thanks! @FluidSense @chrskrchr would any of you be interested in opening a PR and link to this issue? :slightly_smiling_face:
Hi there! It looks like this issue hasn't had any activity for a while. To keep things tidy, I am going to close this issue for now. If you think your issue is still relevant, just leave a comment and I will reopen it. (Read more at #912) Thanks!
Most helpful comment
For anyone curious and searching for how I ended up fixing the issue for Todoist, my provider config now looks like this:
Then, my
/api/oauth/todoist/profileendpoint:Works perfectly and I am able to link Todoist to my app with no issues and with the profile information from their sync endpoint.