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

Support for returning refresh tokens in the external module (OAuth)? #201

Closed
risafj opened this issue Jul 27, 2019 · 13 comments
Closed

Support for returning refresh tokens in the external module (OAuth)? #201

risafj opened this issue Jul 27, 2019 · 13 comments
Labels
enhancement New feature or request

Comments

@risafj
Copy link

risafj commented Jul 27, 2019

Hi, do you have any plans to return not only the access_token but also the refresh_token when logging the user in via the external module? The refresh_token allows the access_token to be refreshed when it expires in one hour.

Right now, the @user_hash being returned looks like this (I'm using Google as the provider), and the refresh_token is nil.

{:token=>"<long string>", :refresh_token=>nil, :expires_at=> <integer>, :expires_in=> <integer>, :user_info=>{"id"=>"<numbers>", "email"=>"email@email.com", "verified_email"=>true, "name"=>"<full name>", "given_name"=>"<first name>", "family_name"=>"<last name>", "picture"=>"<jpg url>", "locale"=>"ja", "hd"=>"<domain>"}, :uid=>"<numbers>"}

@joshbuker joshbuker added the enhancement New feature or request label Oct 3, 2019
@joshbuker
Copy link
Member

If anyone would like to extend the return hash to include refresh token, I would certainly take a look at the PR and merge it in if everything looks fine. That being said, ideally we will be refactoring sorcery to depend on Omniauth for the external module, and I'm sure Omniauth already has this functionality implemented for most if not all providers.

@mtomov
Copy link

mtomov commented Oct 30, 2019

Hi @risafj ,

The refresh_token is actually part of the access_token, as the access_token is actually not a string, but an instance of OAuth2::AccessToken.

image

If I do @user_hash after sorcery_fetch_user_hash("google"), then I get the :refresh_token in there as well:

image

Maybe check if you have the scopes for Google right:

  config.google.scope = "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"

So, simplistically, if you add refresh_token and expires_at columns to Authentication, the whole thing becomes:

def callback
    provider = "google"

    if (@user = login_from(provider))
      @user.authentications.find_by(provider: provider).update_columns(refresh_token: @user_hash[:refresh_token], expires_at: Time.at(@user_hash[:expires_at]))
      
      redirect_to root_path, notice: "Logged in from #{provider.titleize}!"
    else
      begin
        @user = create_from(provider)
        @user.authentications.find_by(provider: provider).update_columns(refresh_token: @user_hash[:refresh_token], expires_at: Time.at(@user_hash[:expires_at]))

        reset_session # protect from session fixation attack
        auto_login(@user)
        redirect_to root_path, notice: "Logged in from #{provider.titleize}!"
      rescue
        redirect_to root_path, alert: "Failed to login from #{provider.titleize}!"
      end
    end
  end

@risafj
Copy link
Author

risafj commented Oct 31, 2019

Hi @mtomov,

Thank you for the response and the code snippet!
Ultimately, the issue our team found was that Sorcery's request to Google didn't state that the request was for offline access. Google doesn't provide a refresh token unless you specify as such (source), and that's why our refresh tokens were coming back empty.

@auth_url = '/o/oauth2/auth'

https://github.com/Sorcery/sorcery/blob/master/lib/sorcery/providers/google.rb#L18

So we overrode the auth_url like this in config/initializers/sorcery.rb.

config.google.auth_url = '/o/oauth2/auth?access_type=offline'

Then, we could access the refresh token like this in the controller (because as you say, "the refresh_token is actually part of the access_token"): @access_token.refresh_token.

I raised this Github issue before learning that overriding the auth_url was an option. Now that I know, this is a non-issue for me, though I still think it may be kinder to users if offline access was enabled by default.
Please feel free to close this issue if necessary.

@joshbuker
Copy link
Member

I think it would make sense to add this to the wiki, and add a note in the initializer for the Google oauth config.

I assume that offline access would require additional permissions from the end user, so changing the default behavior would be a breaking change for applications that don't utilize the refresh token.

@risafj
Copy link
Author

risafj commented Nov 1, 2019

@athix That's a good point. Let me check again if the auth_url is the only item that needs to be overridden for refresh tokens to become available, and then I'll update the external module wiki.

@joshbuker
Copy link
Member

Sounds good, thanks @risafj!

@mtomov
Copy link

mtomov commented Nov 1, 2019

@risafj In my brief tests, with the default configuration (no change in auth_url), I still am getting the refresh token. It might be something on google's end with the way oauth is set-up or something. Not sure.

@risafj
Copy link
Author

risafj commented Nov 2, 2019

@mtomov Hmm... I wonder what the difference between our configurations are.
I have this small app with the minimal Sorcery setup necessary to use the external module, and@user_hash.refresh_token and @access_token.refresh_token still both come back as nil.

(byebug) @user_hash
{:token=>"ya29.Il-vB50lIA0eo-...", :refresh_token=>nil, ...}
(byebug) @access_token
#<OAuth2::AccessToken:0x00007f81f94b2878 @token="ya29.Il-vB50lIA0eo-..., @refresh_token=nil, @expires_in=3600, @expires_at=1572689334, ...}

I just overrode the auth_url and now the responses come back with a refresh token.

This is the repo - please check it out if you have a chance: https://github.com/risafj/goo_oauth

@mtomov
Copy link

mtomov commented Nov 3, 2019

Maybe it's related to this?
https://github.com/oauth-xx/oauth2/issues/321

Edit: Not really, no

@mtomov
Copy link

mtomov commented Nov 3, 2019

Actually I figured it out. In my first try, I followed the API demo on the google api pages where there was a one-button-click way to create oauth credentials. Those turned out to be of type "other", which doesn't require setting redirect urls, and a few other settings, it doesn't persist for the user, and seems like it gives a refresh_token by default.

image

I now went to create a new Oauth flow, and chose the web application type

image

and, yes, I don't have a refresh token with the default settings : )


Adding config.google.auth_url = '/o/oauth2/auth?access_type=offline' absolutely works! Thanks a lot @risafj ! I'm happy to submit a PR as suggested with a comment on this setting.

Btw, also be careful not to loose the refresh token - as you only even get one per authorization. People were puzzled on that - see here googleapis/google-api-python-client#213 (comment)

@risafj
Copy link
Author

risafj commented Nov 5, 2019

Awesome, thank you @mtomov :)
Oh, just realised I'd missed @athix 's suggestion about raising a PR with a comment. I just updated the wiki with a note at the end, I'll raise a PR with a comment too, later.
https://github.com/Sorcery/sorcery/wiki/External

@mladenilic
Copy link
Contributor

@athix, @risafj Can this issue be closed as well?

@joshbuker
Copy link
Member

@mladenilic I think so, yes. There's plenty of information here for anyone looking to solve this in the future, and SEO typically does a good job of finding these issues.

For anyone finding this: if this doesn't cover your specific scenario, please feel free to open a new issue with additional details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants