Skip to content

RFC: Improve OAuth provider configuration #1846

@balazsorban44

Description

@balazsorban44

Summary of proposed feature
Introducing a new way of configuring OAuth providers. With this proposal, we can provide granular control from basic (with the best defaults we can think of) to completely controlled hooks into the login flow. (The user would be able to do the actual fetch calls themselves. Hopefully, this will almost never would be necessary)

Here is an example:

export default function Auth0(options) {
  return {
    id: "auth0",
    name: "Auth0",
    authorization: `https://${options.domain}/authorize?grant_type=authorization_code&response_type=code&scope=openid%20email%20profile`,
    // Alternatively, you could pass an url and params
    // which will be combined for you for convinience.
    // authorization: {
    //   url: `https://${options.domain}/authorize`,
    //   params: {
    //     grant_type: "authorization_code",
    //     response_type: "code",
    //     scope: "openid email profile",
    //   },
    // },
    // Skips fetching user data. In some rare situations, you may not need this. See #1065
    userinfo: null,
    token: {
      url: `https://${options.domain}/oauth/token`,
      method: "POST",
      via: "header",
      // Complete control over how to get the userinfo.
      async request({ tokens }) {
        const response = await fetch("custom/endpoint/token", {
          headers: {
            Authorization: `Bearer ${tokens.access_token}`,
          },
        })
        return await response.json()
      },
    },
    profile(profile) {
      return {
        id: profile.sub,
        name: profile.nickname,
        email: profile.email,
        image: profile.picture,
      }
    },
    ...options,
  }
}

Purpose of proposed feature
To simplify the configuration by reducing the number of required fields, but give full control at the same time, for advanced use cases.

Detail about proposed feature

So to sum it up, 3 new methods could be introduced, using a similar shape:

/** Any contextual information that makes the request useful, eg.: tokens in case of the token endpoint */
type RequestContext = any

type OAuthEndpoint = string | null | {
  url?: string
  method?: "POST" | "GET"
  /** How to pass the access token */
  via?: "header" | "body" | "query"
  /** Will be used as URL params if method is GET, or the body as x-url-form-encoded if POST*/
  params?: URLSearchParams | Record<string, unknown>
  request(context: RequestContext)?: Promise<any>
}

Potential problems
As there are no conflicting configuration options, this could be implemented in a non-breaking way, meaning in theory we don't have to wait until the next major version.

Describe any alternatives you've considered
The current configuration is fragile since the OAuth spec can be interpreted differently, which often ends up with slightly too specific implementations, which has been hard to handle until now. The best solution was one-off configuration flags for certain providers, but in the long term, it introduced bloat in the code.

Additional context

Please indicate if you are willing and able to help implement the proposed feature.
Could fit neatly into #1698, or a follow-up PR.

Related: #1065, #1642, #1605, #1607, #1756, #950

Please share your opinions!

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions