Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth Generator lacks support for custom provider #1585

Open
dthyresson opened this issue Dec 17, 2020 · 25 comments
Open

Auth Generator lacks support for custom provider #1585

dthyresson opened this issue Dec 17, 2020 · 25 comments

Comments

@dthyresson
Copy link
Contributor

See the following Community topic post from "edjiang":

"Looks like we’re not able to generate custom providers anymore?"

image

While the docs do seem to indicate that one can generate a custom auth provider:

image

It is not supported because:

const SUPPORTED_PROVIDERS = fs

and there is no "custom" template.

While it may be relatively easy to setup the web side, the api side is more complicated with the changes needed for graphql and the additional auth lib.

The custom generator could (and should) still add these to the api side, and stub on the web side.

@peterp peterp removed the kind/bug label Mar 7, 2021
@jtoar jtoar added this to To do in Auth via automation May 27, 2021
@jtoar jtoar added this to To do in Generators & Scaffolds via automation May 27, 2021
@jtoar jtoar removed this from To do in Auth Jun 5, 2021
@jtoar jtoar removed this from To do in Generators & Scaffolds Jun 5, 2021
@jtoar jtoar added this to v1 priority in Current-Release-Sprint Jun 5, 2021
@jtoar jtoar added this to New issues in Current-Release-Sprint via automation Jun 10, 2021
@jtoar jtoar moved this from New issues to On deck (future-release; help-wanted) in Current-Release-Sprint Jun 10, 2021
@jtoar jtoar added this to the future-release milestone Jun 10, 2021
@thedavidprice thedavidprice moved this from On deck (v1-rc priorities; help-wanted) to Icebox (post v1-RC priorities) in Current-Release-Sprint Jun 10, 2021
@aggmoulik
Copy link
Contributor

aggmoulik commented Jul 6, 2021

@jtoar I was thinking to work on this as there is another Database Authentication coming after this PR, Should I continue or not ?

@dthyresson @thedavidprice

@thedavidprice
Copy link
Contributor

Hi @aggmoulik It's great having you as a contributor. Thanks for keeping it up!

For all intents and purposes, the dbAuth #2701 will be the Redwood "custom provider". So there's nothing more to be done here.

If you're looking for other high-priority issues that get us to v1 and need help now, talk a look at the column On deck (help-wanted) here in the Current Release Sprint project board.

@thedavidprice
Copy link
Contributor

@dthyresson curious about your reaction to my thoughts above — in light of dbAuth provider, do you still think there's value in a boilerplate setup for "custom"?

Regardless, we should clear up the docs.

@Tobbe
Copy link
Member

Tobbe commented Jul 7, 2021

Not that you asked for my opinion, but until we have a more "plugin" approach to our auth providers I still think there would be value in having a "custom" generator. I think people are always going to want to add other third party auth providers, like maybe a direct Facebook auth solution. Or Twitch.

@dthyresson
Copy link
Contributor Author

@dac09 Did you use a custom provider for a cli side?

@dac09
Copy link
Collaborator

dac09 commented Jul 7, 2021

I do have multiple custom auth ones, but because I use them outside redwood, I don't have a need for a provider, just some additional logic in getCurrentUser in src/lib/auth.ts

@dthyresson
Copy link
Contributor Author

@thedavidprice and @Tobbe I cannot think of a practical reason for a custom provider generator -- and if anything it complicates the authentication docs.

The biggest issue is even if the api-side has some custom logic, what is the web side to do? You need a client that requests the AuthProvider and AuthContextInterface (login, signUp, hasRole, currentUser, etc). Does that get "stubbed" out?

@dthyresson
Copy link
Contributor Author

dthyresson commented Jul 7, 2021

Some background, a year ago I tried to implement NetlifyOAuth as a new provider, rather than custom.

See here: https://community.redwoodjs.com/t/i-implemented-a-netlify-oauth-not-identity-auth-provider-but-im-not-sure-i-should-have-and-why/903

I abandoned it for reasons in that forum post, but can see in https://github.com/dthyresson/redwood/blob/dt-auth-client-netlify-oauth/packages/auth/src/authClients/netlifyOAuth.ts that you still need to implement that AuthContextInterface specific to the auth method you intend to support.

The problem is that custom the client and decoder is in the Redwood framework and maps when authenticating the request. But you cannot modify that code. That is you cannot map the "custom" auth type to some custom authentication code.

So, where does one actually implement the "custom" auth logic?

Hence I think why @dac09 has the logic inside:

I don't have a need for a provider, just some additional logic in getCurrentUser in src/lib/auth.ts

Because in getCurrentUser one has access to the type (supabase, netlify ... custom) and if the type is custom, then you can "do something" with that request and determine if the request's user is authenticated.

Therefore, there the idea of a custom generator setup isn't needed at all.

What should be documented is it you send a GraphQL request or any request really with the auth provider type of custom, then how do you handle this in getCurrentUser?

I think this is much better solved via documentation and a cookbook example than a generator (because I don't think one can i fact be made).

@Tobbe
Copy link
Member

Tobbe commented Jul 7, 2021

I think this is much better solved via documentation and a cookbook example

This sounds good to me! Especially an example.

@dthyresson
Copy link
Contributor Author

dthyresson commented Jul 7, 2021

@thedavidprice Please see the above but TLDR; this issue is best served with an example or cookbook -- and also clarifying the documentation.

The custom provider is misleading because it really isn't declaring a new provider -- but rather that one case send the custom auth type in a request and then have some custom logic in the getCurrentUser auth method.

Perhaps @aggmoulik could create a new issue in the redwoodjs.com report to create a cookbook and update the docs -- after coming up with an example case.

Perhaps something that:

  • takes a JWT and decodes?
  • looks at the query params and authenticates if a certain value?
  • only allows requests to be authenticated based on the user-agent in the event's headers?
  • maybe a header signature stores the encoded/encrypted id if the user?

I think the header idea is best. Then getCurrentUser fetches the headers, looks for a value and decides if the authenticated or not.

But, I think we should close this in favor of docs.

It would great for @aggmoulik to have an understanding of auth for the community.

@thedavidprice thedavidprice removed this from Icebox (post v1-RC priorities) in Current-Release-Sprint Jul 7, 2021
@thedavidprice thedavidprice removed this from the future-release milestone Jul 7, 2021
@aggmoulik
Copy link
Contributor

Hey @thedavidprice @dthyresson It will be great, I will work on cookbook for authentication here and how can we setup the authentication rather than provided one on the frontend and backend in redwood application.

@dthyresson
Copy link
Contributor Author

I will work on cookbook for authentication here

Thanks @aggmoulik even if it turns out that a generator is practical and helpful (I was rethinking things yesterday and maybe the real key to the generator isn't the api side, but rather the web-side to generate a client that implements the AuthProvider interface) and then a small lib in api that the getCurrentUser can use in lieu of a JWT decoder mapper class (that takes the event from the service request) actually doing a proof of concept and writing it up would help inform nay generator made.

TLDR; I think the exercise in writing the docs/how to cookbook will be helpful.

Please reach out to get with any questions and thanks again for taking on this task!

@thedavidprice
Copy link
Contributor

Hi all, just checking in here — is this still of interest and/or any help needed?

@aggmoulik
Copy link
Contributor

Yeah, I have been looking into it. I will work this weekend. So, I will let you know.

@aggmoulik
Copy link
Contributor

I am confused here a little bit as I should add a cookbook on setup custom JWT Provider or in a more general way, how to setup file structure?

@dthyresson
Copy link
Contributor Author

I am confused here a little bit as I should add a cookbook on setup custom JWT Provider or in a more general way, how to setup file structure?

Cookbooks should be practical, real-world examples nit just sample docs.

The best thing to do would be ask 1) why do I need a custom auth provider? 2) how would that be used in a real-world app situation 3) describe why and for what purpose and then 4) show how the custom auth provider solves that problem.

It's as much why and it is how. And actually more specific than general. Ideally people could related to the problem and solution and adapt the solution in a new way that best suits them.

The only real-world case I could come up with was a JWT based token auth for a CLI -- but perhaps there are others.

For example, an "api token" auth where a user is given an api token/key and that is used to identify the user account or organization and grant permissions to access the api.

@thedavidprice
Copy link
Contributor

Just a reminder so we don't lose track of this:

The Auth doc definitely needs clarification. I've created redwoodjs/redwoodjs.com#740

@Philzen
Copy link
Contributor

Philzen commented Jun 23, 2022

We were just about to start on an LDAP auth integration (yeah we know it's almost way too sexy for 2022, but it's simply a requirement that we were given 🤷 😆) and currently trying to piece the information together.

With #5810 and currently no means to generate a custom template using the cli here, it is really a severe slowdown, if not showstopper to develop and contribute new auth providers to RedwoodJS.

Also it looks like the current auth providers are all web focused (except dbAuth and only judging from the few we tested so far), so we'd be grateful for hints regarding the formal process of adding auth to the api-side. Cheers.

@dthyresson
Copy link
Contributor Author

Hi @Philzen just to let you know, the some of the Core Team (@Tobbe @dac09 @cannikin @callingmedic911 and me) met yesterday to discuss next steps to make it easier to implemented your own auth provider implementation for RedwoodJS.

As we get closer to those specs, we'll be in touch and perhaps you could be the first person to try out the new feature.

I made a note to check in with you, but please if you don't hear back from me in a few weeks or so, let me know.

@Tobbe
Copy link
Member

Tobbe commented Aug 7, 2022

I just pushed initial (untested) code for a custom auth setup command to my big auth refactor PR here: #5985. But that PR only focuses on the web side. We'd also need to add some way to customize the api side, like adding a custom jwt decoder.

And as most other auth providers we have, it's focused on integrating with some auth SaaS.

@Philzen
Copy link
Contributor

Philzen commented Aug 7, 2022

@dthyresson @Tobbe @cannikin @callingmedic911

As we get closer to those specs, we'll be in touch and perhaps you could be the first person to try out the new feature.

I made a note to check in with you, but please if you don't hear back from me in a few weeks or so, let me know.

That sounds really awesome, thank you. We spoke to our project sponsor and were able to agree postponing the LDAP login until after our desperately needed summer hiatus. In the meantime they are happy with the dbAuth, which – thanks to Redwood's scaffolding awesomeness – we were able to rollout in a jiffy 😄

So we'll be taking up again end of September / beginning of October.

Kindly advise what will be the best way to get in contact then.


Concerning the implementation itself, we did some sandbox experiments, and it turns out building the LDAP auth is no rocket science at all, and also it's one of the simplest use cases imaginable, as basically only login-handler is required (as always, the biggest extra-work again comes due to Microsoft, which chose to roll their own standard concerning UserIDs in their AD implementation, but we think we already have a good solution to also cater for that).
I could see us implementing this in three steps:

  1. generate custom auth provider in a separate Redwood sandbox and implement the LDAP login flow (which should sit in the API side)
  2. when that works, we'd love to contribute this to the auth-package
  3. integrate the LDAP-Auth into the dbAuth-flow, having them coexist, where dbAuth is the main driver, delegating the authentication to LDAP when a user record either
    (a) has the ldap-flag set in the db
    (b) cannot be found in the db. After successful LDAP response (having verified their appropriate group membership and returned their user details), they would then be created in the DB with the ldap-flag set.

Step 3. is a really crucial must-have for us, not only to enable peaceful coexistence with a local dev-setup, where we may not always have (or want) access to an AD behind a VPN, but we may not even get an account on such an AD but still need to be able to login to the deployed release to be able to support.
However i'm completely in the dark so far, how this could be done with the current handlers available. For 3.(a) i guess we could hook into the login-handler, but for 3.(b) we'd need be able to programmatically handle the login error, starting the ldapAuth-flow and on success create the record, then changing the error to a success (maybe sort-of abuse signup-handler for both use cases?).

Just wanted to share this upfront so you could meditate about this.

I hope this makes sense to you and that i'm not asking to much here. On the other hand, i don't believe that we're the first people out there with such a requirement – however i couldn't find any examples on the Redwood Authentication docs that show how to use dbAuth in conjunction with other auth providers. The closest i found was the RBAC docs, which show how to manage the roles against an external user UUID … but then again i'm missing which handlers to hook into in order to seed them in the first place. I'm sure you all got it worked out already, but the docs could use some directions for Redwood noobs / ongoing-discoverers like me how this is intended – maybe even as a separate tutorial page, or another tutorial chapter?

If you like, i could provide a BPMN or such to illustrate the login process that we're after. Also, kindly advise where we should move this discussion for a more focussed effort. Cheers!

@Tobbe
Copy link
Member

Tobbe commented Aug 8, 2022

Kindly advise what will be the best way to get in contact then.

Both @dthyresson and I are very responsive if you ping us on Discord.

integrate the LDAP-Auth into the dbAuth-flow, having them coexist, where dbAuth is the main driver, delegating the authentication to LDAP when a user record either

I think we'd need to come up with a generic hook into the dbAuth flow for this to have any chance of being accepted 🙂 We definitely need to talk to @cannikin about this, and he's away on vacation for a week+, so can't do that right now.

On the other hand, i don't believe that we're the first people out there with such a requirement – however i couldn't find any examples on the Redwood Authentication docs that shows how to use dbAuth in conjunction with other auth providers.

Yeah, there are no docs, and no examples. It simply hasn't been done yet. But you're right, you're not the first to want this. There was some discussion here #3337 Especially this comment

Yeah you bring up a good point. Now that I've learned how to use dbAuth, I'm thinking that I should completely re-haul the ETH Auth to piggyback on dbAuth. This is a lot, so I may need to move this to draft and come back to it

That was a while ago though, and I don't think anything else has come out of it.

If you like, i could provide a BPMN or such to illustrate the login process that we're after. Also, kindly advise where we should move this discussion for a more focussed effort. Cheers!

@Philzen thanks for offering to help work on this! Discord, as already mentioned, could be a way for us to collaborate. If that doesn't work for you, just ping me here when you're back from vacation and ready to start working on this and I'm sure @thedavidprice can help us figure out a way to efficiently collaborate.

@dthyresson
Copy link
Contributor Author

During Redwood Office Hours yesterday, a "fake" auth provider was shared -- which they use in dev only to have offline auth and should never be used in production code -- but it is a nice short example of what it would take to implement a customAuth provider:

const FakeAuthProvider = ({ children }) => {
  return (
    <AuthContext.Provider
      value={{
        loading: false,
        isAuthenticated: true,
        userMetadata: { name: 'Fake User' },
        currentUser: { sub: 'fake|1234', roles: ['admin'] },
        hasError: false,
        logIn: (options) =>
          new Promise((resolve, _reason) =>
            resolve(console.log('logIn', options))
          ),
        logOut: (options) =>
          new Promise((resolve, _reason) =>
            resolve(console.log('logOut', options))
          ),
        signUp: (options) =>
          new Promise((resolve, _reason) =>
            resolve(console.log('signUp', options))
          ),
        getToken: () =>
          new Promise((resolve, _reason) => {
            console.log('getToken')
            resolve('getToken')
          }),
        getCurrentUser: () =>
          new Promise((resolve, _reason) => {
            console.log('getCurrentUser')
            resolve({ sub: 'fake|1234', roles: ['admin'] })
          }),
        hasRole: (rolesToCheck) => {
          console.log('hasRole', rolesToCheck)
          return true
        },
        reauthenticate: () =>
          new Promise((resolve, _reason) => {
            resolve(console.log('reauthenticate'))
          }),
        forgotPassword: (username) =>
          new Promise((resolve, _reason) =>
            resolve(console.log('forgotPassword', username))
          ),
        resetPassword: (options) =>
          new Promise((resolve, _reason) =>
            resolve(console.log('resetPassword', options))
          ),
        validateResetToken: (resetToken) =>
          new Promise((resolve, _reason) =>
            resolve(console.log('validateResetToken', resetToken))
          ),
        client: {},
        type: 'custom',
      }}
    >
      {children}
    </AuthContext.Provider>
  )
}

const CustomAuthProvider = ({ children }) => {
  if (process.env.FAKE_AUTH === 'true') {
    return <FakeAuthProvider>{children}</FakeAuthProvider>
  }
  return <Auth0AuthProvider>{children}</Auth0AuthProvider>
}

// in api/src/lib/auth.ts
export const getCurrentUser = async (decoded): Promise<RedwoodUser> => {
  if (process.env.FAKE_AUTH === 'true') {
    decoded = { sub: 'fake|1234', roles: ['admin'] }
  }
  ...
}
  
export const isAuthenticated = (): boolean => {
  if (process.env.FAKE_AUTH === 'true') return true
  return !!context.currentUser
}
  
export const hasRole = (roles: AllowedRoles): boolean => {
  if (process.env.FAKE_AUTH === 'true') return true
}  

@Tobbe Tobbe mentioned this issue Sep 30, 2022
28 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Backlog
Development

No branches or pull requests

8 participants