Skip to content

feat: add ability for users to convert their password login type to oauth/github login #8105

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

Merged
merged 64 commits into from
Jun 30, 2023

Conversation

Emyrk
Copy link
Member

@Emyrk Emyrk commented Jun 20, 2023

This feature allows users to convert from password login to oauth/oidc assuming the emails match.

Fixes #4505
UI: #8182

This feature is intentionally hidden. Functionality is in this PR, with UI coming in another PR (thanks @BrunoQuaresma !). There are still some open ended questions at the bottom to determine before GA.

Audit log

Screenshot from 2023-06-20 16-01-49

How it works

On the Account settings page there will be a form to "Link/Merge/Convert (idk wording) with OIDC/Github". There will also be a password field.

  1. User submits their password and the OIDC app to connect with ("oidc" or "github"). Similar to /login page right now.
  2. A state string is generated and stored in a signed JWT cookie that stores oauth state strings with short expire times.
  3. The user is then redirected to the normal OIDC auth flow.
  4. In the OIDC flow, when we get to the part where the code aborts from the wrong login type, we check if the state string given is a special state string. If it is, we look for the additional signed state cookie. If it is, then we assume the user requested this OIDC account as a conversion, and convert the account type.

Images

Login with wrong login type

Screenshot from 2023-06-28 16-09-47

Audit logs of a successful convert

Screenshot from 2023-06-28 16-10-18

Decisions to be made

  • After OIDC convert, should we log out the user?
  • Allow builtin login after conversion?
  • If email mismatch, should we update the users email? Should we make a new account? What do we do?
  • Should we allow convert back to password auth?

Future Work

  • Indicate the user's login type on the user's page for admins

Comment on lines 35 to 37
// postConvertLoginType replies with an oauth state token capable of converting
// the user to an oauth user.
func (api *API) postConvertLoginType(rw http.ResponseWriter, r *http.Request) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do users need to be able to do this? What if we only allowed admins to change the state manually?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User's trigger this. I took the idea from here #4505 (comment)

Essentially linking users to their OIDC account is tricky, as emails can match even if they are different people. Eg maybe an account was made with builtin called paul@coder.com. Paul leaves the company. The next Paul is hired and now it's difficult to know in the code if the Paul from OIDC is the same Paul from the builtin.

This resolves that by making it a user triggered action. The user goes to their Account > Settings. Hits "Convert to OIDC" and goes through the OIDC flow.

We generate a state string linking the OIDC login to the "Convert" request. So we know it is the same person as they were able to provide the password for the builtin AND provide the OIDC auth.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we encode something in the state instead? We already sign the value, I believe.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err, if we don't we can. We have a global signing key that we use anyways.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for the why: I've always found it sketchy to insert any OAuth state into the database. It allows for an unauthenticated user to directly insert into the DB.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did think about signing state, but all Oauth 2.0 specs I read said it should be a non-guessable random string. I don't see a security risk with signing a random string and making it a JWT for example. We should have expiration and user_id as apart of the state string, so there is information that needs to be encoded.

Copy link
Member Author

@Emyrk Emyrk Jun 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So some IDP's do have a maximum length state param. These values are different everywhere

From https://auth0.com/docs/secure/attack-protection/state-parameters

The allowed length for state is not unlimited. If you get the error 414 Request-URI Too Large, try a smaller value.

In practice, the smallest length I am finding is 255. It's not well documented anywhere.

Bunq: Max length 255

Github, Gitlab, Google, etc do not document this max length. The common language is that the state string carries some CSRF protection.

I did a test JWT and it was 371 characters long.


In summary, I agree a JWT would be simpler as it requires no database interactions to pull out the info. And I honestly prefer to go the JWT method. My only fear is that someone using coder might hit a state string too large error with their IDP, and there would not be any recourse.

On the "Sketchy to insert OAuth sate into the database" because of unauthenticated users. In this case, only authenticated users can generate this convert to OIDC state string. An unauthenticated user cannot trigger this codepath.

I have code that makes sure only 1 state string per user exists in the DB.

So in this particular case, we avoid your worries.

If you still think we should go JWT instead, I can make the switch. I just honestly don't know what state string limitations exist, and I spent over an hour trying to find information without any luck besides errors people hit in random github issues on random projects.

@Emyrk Emyrk requested a review from deansheather June 29, 2023 13:47
Copy link
Member

@deansheather deansheather left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BE LGTM

Comment on lines 5 to 7
login_type = @login_type
WHERE
id = @user_id RETURNING *;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should clear the stored hashed password too

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done and added a unit test.

Comment on lines 36 to 39
# If enabled, users can switch from password based authentication to oauth based
# authentication by logging into an oidc account with the same email address.
# (default: <unset>, type: bool)
enableOauthAuthConversion: false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bpmct do we need to add a new option for this? Is there any problem with this being default instead? It feels bad to add a new config option just for this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to drop this if that is the conclusion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think we should keep this atm.

This flag is hidden because we need to follow up this PR. See "Decisions to be made" section in the description.

I want to get this PR in as it's been hung up for awhile. Those decisions can come after this. Then we can drop this flag.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I should make it an experiment. Ideally the follow up is quick.

Copy link
Member

@bpmct bpmct Jun 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many of the "decisions to be made" feel like breaking changes if we merge this and users start using it. I know it's been hung up for a while, but what is the benefit of merging this PR?

Let's move forward with this:

After OIDC convert, should we log out the user?

Yes

Allow builtin login after conversion?

No. Users can only log in with their login type

If email mismatch, should we update the users email? Should we make a new account? What do we do?

We should update the user's email and expect that ignore_changes is used in templates to protect resources from destruction.

Should we allow convert back to password auth?

Yes, but the password has to be reset. The password should also be cleared when the login type is changed from builtin.

I'm fine with removing enableOauthAuthConversion and making always true and non-configurable. As an aside, we work with many customers whose security teams do not allow built-in accounts of any type. We should eventually add a flag for that (creating/converting/etc). I will make an issue for it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this an experiment. Just in review terms, adding these adds more re-reviewing. If it's an experiment, I can do quick follow ups. As long as it's an experiment, it will not just idle and get lost imo.

@Emyrk
Copy link
Member Author

Emyrk commented Jun 29, 2023

I might argue that if the route doesn't match what we'd actually call this then something is wrong. We should make sure they both make sense IMO.

I renamed to ConvertLoginType. If we need to change how this works in the future, we can update naming appropriately. Atm though this is ok.

@Emyrk Emyrk merged commit b5f26d9 into main Jun 30, 2023
@Emyrk Emyrk deleted the stevenmasley/merge_oidc_account branch June 30, 2023 12:38
@github-actions github-actions bot locked and limited conversation to collaborators Jun 30, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow conversion of email/password authenticated account to OpenID Connect authed account
5 participants