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

Create auth token links for email #70

Open
stuyam opened this issue Dec 17, 2019 · 6 comments
Open

Create auth token links for email #70

stuyam opened this issue Dec 17, 2019 · 6 comments

Comments

@stuyam
Copy link

stuyam commented Dec 17, 2019

I'm not sure if there is a preferred way of doing this or not. But what I want to be able to do is send an email to a user that wasn't user initiated that automatically signs them in. For example I want to email the user to prompt them to update a post and I want that link to be authenticated so if they aren't signed in they don't have to go through the sign in flow and make multiple trips to their email.

My idea was to use an auth_token in the url that is also checked in current_user.

Example:

1. Initiate Email Link

user = User.first # some user I am emailing
session = Passwordless::Session.create(
  remote_addr: 'generated_on_users_behalf',
  user_agent: 'generated_on_users_behalf',
  authenticatable: user
)
url = "https://example.com/posts/new?auth_token=#{session.token}

2. Link send via email

<a href="https://example.com/posts/new?auth_token=xxxxxxxxxx">Update Post</a>

3. Lookup via session or auth token

def current_user
  @current_user ||= authenticate_by_session(User) || authenticate_by_token(params[:auth_token])
end

def authenticate_by_token(token)
  return if token.blank?

  # For brute force, first checks if token is blank so this doesn't slow down every request
  BCrypt::Password.create(token)
  session = Passwordless::Session.find_by(token: token)
  return if session.blank?

  sign_in session
  session.authenticatable
end

What I like about this is you can auth in with an auth token in any action that uses require_user! and once rails signs them in with the auth token then they will be signed in and future requests will short circuit to the authenticate_by_session like normal.

This may be the preferred way of doing this, Im just not sure if there was a better way or a way it could be built into the project going forward. Open to suggestions and feedback, thanks for building a great package, really enjoying it! 😃

@stuyam
Copy link
Author

stuyam commented Dec 17, 2019

It seems like from the implementation that I have, there are two methods that could be helpful to have build in.

  1. Session creation with the 'generated_on_users_behalf' part made into a helper method for generating a new session for a user without requiring request data.

  2. If the authenticate_by_token method was built in it could make that easier too.

It is easy enough that it doesn't need to be built in, but just an idea.

@stuyam
Copy link
Author

stuyam commented Dec 17, 2019

I added this method to the user model to generate tokens easily:

def create_auth_token!
  Passwordless::Session.create(
    remote_addr: 'generated_by_rails',
    user_agent: 'generated_by_rails',
    authenticatable: self
  ).token
end

@mikker
Copy link
Owner

mikker commented Dec 17, 2019

I like this idea and have wanted to do something similar, however I think we should take the other way around and make the urls like /users/sign_in/:token?destination_path=/where/you/want/them/to/end/up. Work on this is already underway in #69

@stuyam
Copy link
Author

stuyam commented Dec 17, 2019

Yeah I think that is smart actually, then current_user and everything doesn't get muddied up. Plus I do need the ability to set a custom redirect url after sign in which is covered in that pr. Do you have any suggestions as of what to do with the remote_addr and user_agent, I guess some unique key like generated_by_rails seemed fine to me but I wasn't sure.

It would be nice if the passwordless_with :email class macro added a passwordless_token! and passwordless_url! which accepted an optional url that adds the destination_path if provided. I'll probably just do this in my project but might be interesting for people that want to solve a similar problem.

def passwordless_token!
  @passwordless_token ||= Passwordless::Session.create(
    remote_addr: 'generated_by_rails',
    user_agent: 'generated_by_rails',
    authenticatable: self
  ).token
end

def passwordless_url!(destination_path: nil)
  url_helper = Passwordless::Mailer.new.send(Passwordless.mounted_as)
  url = url_helper.token_sign_in_url(passwordless_token!)
  return url unless destination_path

  "#{url}?destination_path=#{destination_path}"
end

@mikker
Copy link
Owner

mikker commented Dec 17, 2019

I usually just put "N/A" when manually creating them. Don't have any better ideas than something like you already do.

Having a few helpers like you suggest would be nice. I'm not sure about the API. Will let it stew a bit in my mind and see what I can do 😊

@andreaskundig
Copy link
Contributor

andreaskundig commented Jan 19, 2024

I need this!

The code presented here no longer works with the latest version, but I managed to do something similar in my controller:

 def passwordless_url_to(authenticatable, destination_path = '')
      session = create_passwordless_session!(authenticatable)
      link = Passwordless.context.url_for(
        session,
        action: "confirm",
        id: session.to_param,
        token: session.token
      )
      return "#{link}?destination_path=#{destination_path}"
 end

It would be nice to have something like this in Passwordless::ControllerHelpers.

I don't understand the purpose of action: "confirm" though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants