Class: Talis::Authentication::Login

Inherits:
Object
  • Object
show all
Includes:
HTTParty
Defined in:
lib/talis/authentication/login.rb

Overview

Represents the user login flow for server-side applications. A prerequisite to using this class is having an application registered with Persona in order to obtain an app ID and secret. Application registration also provides Persona a callback URL to POST the login response back to the application.

Examples:

Redirect user to authentication provider.

# First create a login object and redirect the user, storing the state:
options = {
  app_id: 'my_app',
  app_secret: 'my_secret',
  provider: 'google',
  redirect_uri: 'https://my_app/secret_area'
}
 = Talis::Authentication::Login.new(options)
url = .generate_url
session[:state] = .state
redirect_to url

Handle data after user has logged in.

# After the user has logged in, handle the POST callback:
state = session.delete(:state)
.validate!(payload: params, state: state)
if .valid?
  session[:current_user_id] = .user.guid
  redirect_to .redirect_uri
else
  # handle invalid login
  puts .error
end

Logging out a user.

# When a user logs out, make sure to clean up the session:
session.delete(:current_user_id)
redirect_to .logout_url('some/logout/path')

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app_id:, secret:, provider:, redirect_uri:) ⇒ Login

Creates a new login object to manage the login flow.

Parameters:

  • app_id (String)

    ID of the application registered to Persona.

  • secret (String)

    secret of the application registered to Persona.

  • provider (String)

    name of the auth provider to use for login.

  • redirect_uri (String)

    where to redirect back to after login.



61
62
63
64
65
66
67
68
# File 'lib/talis/authentication/login.rb', line 61

def initialize(app_id:, secret:, provider:, redirect_uri:)
  @uuid = UUID.new

  @app = app_id
  @secret = secret
  @provider = provider
  @redirect_uri = redirect_uri
end

Instance Attribute Details

#errorString (readonly)

Returns if present, this will be the reason why the login failed.

Returns:

  • (String)

    if present, this will be the reason why the login failed.



52
53
54
# File 'lib/talis/authentication/login.rb', line 52

def error
  @error
end

#stateString (readonly)

Returns a non-guessable alphanumeric string used to prevent CSRF attacks. Store this in the user session after generating a login URL.

Returns:

  • (String)

    a non-guessable alphanumeric string used to prevent CSRF attacks. Store this in the user session after generating a login URL.



46
47
48
# File 'lib/talis/authentication/login.rb', line 46

def state
  @state
end

#userTalis::User (readonly)

Returns the logged-in user. This will be nil unless validation has passed.

Returns:

  • (Talis::User)

    the logged-in user. This will be nil unless validation has passed.



49
50
51
# File 'lib/talis/authentication/login.rb', line 49

def user
  @user
end

Instance Method Details

#generate_url(require: nil) ⇒ String

Use this URL to redirect the user wishing to login to their auth provider. After generating the URL the state will be available to store in a session.

Parameters:

  • require (Symbol) (defaults to: nil)

    (nil) If :profile, upon successful login, the user will be redirected to a profile form if they do not have one.

Returns:

  • (String)

    the generated URL.



77
78
79
80
81
82
83
84
85
86
87
# File 'lib/talis/authentication/login.rb', line 77

def generate_url(require: nil)
  @state = Digest::MD5.hexdigest("#{@app}::#{@uuid.generate}")
  params = {
    app: @app,
    state: @state,
    redirectUri: @redirect_uri
  }
  params = params.merge(require: 'profile') if require == :profile
  params = params.to_query
  "#{self.class.base_uri}/auth/providers/#{@provider}/login?#{params}"
end

#logout_url(redirect_url) ⇒ String

Logs a user out by terminating the session with Persona.

Parameters:

  • redirect_url (String)

    where to return the user on logout.

Returns:

  • (String)

    the URL to redirect the user to when logging out.



133
134
135
# File 'lib/talis/authentication/login.rb', line 133

def logout_url(redirect_url)
  "#{self.class.base_uri}/auth/logout?redirectUri=#{redirect_url}"
end

#redirect_uriString

The redirect to follow once login has successfully completed.

Returns:

  • (String)

    the URL to redirect to.



126
127
128
# File 'lib/talis/authentication/login.rb', line 126

def redirect_uri
  @payload.present? ? @payload['redirect'] : @redirect_uri
end

#valid?Boolean

Indicate whether or not the login succeeded.

Returns:

  • (Boolean)

    true if the login is valid, otherwise false.



120
121
122
# File 'lib/talis/authentication/login.rb', line 120

def valid?
  @error.nil?
end

#validate!(payload:, state:) ⇒ Object

Validate a login request after the user has logged in to their auth provider. Validation will fail if the provided payload:

  • Is not a hash.

  • When decoded, contains invalid JSON.

  • Has no state or the state it contains does not match the param state.

  • Has an invalid signature.

If validation succeeds, the #user attribute will return the logged-in user. If it fails, check the #error attribute for the reason why.

Parameters:

  • payload (Hash)

    the payload POSTed to the application server.

  • state (String)

    use the value stored at the start of the session.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/talis/authentication/login.rb', line 100

def validate!(payload:, state:)
  return @error = 'payload is not a hash' unless payload.is_a? Hash

  key = 'persona:payload'
  return @error = "payload missing key #{key}" unless payload.key? key

  @payload = decode_and_parse_payload(payload)
  return @error = 'payload is not valid JSON' if @payload.nil?

  state_error = 'payload state does not match provided'
  return @error = state_error if @payload['state'] != state

  signature_error = 'payload signature does not match expected'
  return @error = signature_error unless signature_valid?(@payload)

  @user = build_user(@payload)
end