AuthRocket/Ruby

The authrocket gem works with both Rails and plain Ruby. It will auto-detect Rails and enable Rails-specific features as appropriate.

It covers all of our Core API and select portions of the Configuration API.

For our Getting Started guide, see Integration with Rails or Integration with Ruby.

Usage - Rails

AuthRocket includes a streamlined Rails integration that automatically handles logins and logouts. For a new app, we highly recommend this.

To your Gemfile, add:

gem 'authrocket', require: 'authrocket/rails'

Then ensure the following environment variable is set:

LOGINROCKET_URL    = https://sample.e2.loginrocket.com/

If you’ve changed the default JWT key type to HS256, you’ll also need this variable:

AUTHROCKET_JWT_KEY = jsk_SAMPLE

If you plan to access the AuthRocket API as well, you’ll need these variables too:

AUTHROCKET_API_KEY = ks_SAMPLE
AUTHROCKET_URL     = https://api-e2.authrocket.com/v2
AUTHROCKET_REALM   = rl_SAMPLE  # optional

Finally, add a before_action command to any/all controllers or actions that should require a login.

For example, to protect your entire app:

class ApplicationController < ActionController::Base
  before_action :require_login
end

Selectively exempt certain actions or controllers using the standard skip_before_action method:

class ContactUsController < ApplicationController
  skip_before_action :require_login, only: [:new, :create]
end

Helpers are provided to create login, signup, and logout links, as well as for users to manage their profile:

<%= link_to 'Login', ar_login_url %>
<%= link_to 'Signup', ar_signup_url %>
<%= link_to 'Logout', logout_path %>
<%= link_to 'Manage Profile', ar_profile_url %>

Both the current Session and User are available to your controllers and views:

current_session # => AuthRocket::Session
current_user    # => AuthRocket::User

The current Membership and Org (account) are accessible through helpers as well.

current_membership
current_org

If a user is a member of more than one org (account), current_membership and current_org will be reflect the currently selected account. Additional helpers are available to provide appropriate links to your users:

<%= link_to 'Manage current account', ar_account_url %>
<%= link_to 'Switch accounts', ar_accounts_url %>

See below for customization details.

Usage - everywhere else

If you aren’t using Rails, or if the streamlined integration above is too opinionated, use the gem without the extra Rails integration.

In your Gemfile, add:

gem 'authrocket'

Then set the following environment variables:

# If accessing the AuthRocket API:
AUTHROCKET_API_KEY = ks_SAMPLE
AUTHROCKET_URL     = https://api-e2.authrocket.com/v2  # must match your provisioned cluster
AUTHROCKET_REALM   = rl_SAMPLE  # optional
#
# If using JWT-verification of AuthRocket's login tokens:
AUTHROCKET_JWT_KEY = SAMPLE

Configuration

By default, AuthRocket automatically loads credentials from environment variables. This is optimal for any 12-factor deployment. Supported variables are:

AUTHROCKET_API_KEY = ks_SAMPLE
Your AuthRocket API key. Required to use the API (but not if only performing JWT verification of login tokens).

AUTHROCKET_JWT_KEY = SAMPLE
Used to perform JWT signing verification of login tokens. Not required if validating all tokens using the API instead. Also not required if LOGINROCKET_URL is set and RS256 keys are being used, as public keys will be auto-retrieved. This is a realm-specific value, so like AUTHROCKET_REALM, set it on a per-use basis if using multiple realms.

AUTHROCKET_REALM = rl_SAMPLE
Sets an application-wide default realm ID. If you’re using a single realm, this is definitely easiest. Certain multi-tenant apps might use multiple realms. In this case, don’t set this globally, but include it as part of the :credentials set for each API method.

AUTHROCKET_URL = https://api-e2.authrocket.com/v2
The URL of the AuthRocket API server. This may vary depending on which cluster your service is provisioned on.

LOGINROCKET_URL = https://SAMPLE.e2.loginrocket.com/
The LoginRocket URL for your Connected App. Used by the streamlined Rails integration (for redirects) and for auto-retrieval of RS256 JWT keys (if AUTHROCKET_JWT_KEY is not set). If your app uses multiple realms, you’ll need to handle this on your own. If you’re using a custom domain, this will be that domain and will not contain ‘loginrocket.com’.

It’s also possible to configure AuthRocket using a Rails initializer (or other initialization code).

AuthRocket::Api.credentials = {
  api_key:         'ks_SAMPLE',
  jwt_key:         'SAMPLE',
  loginrocket_url: 'https://sample.e2.loginrocket.com/',
  realm:           'rl_SAMPLE',
  url:             'https://api-e2.authrocket.com/v2'
}

Customizing the Rails integration

The built-in Rails integration tries to handle as much for you as possible. However, there may be times when you wish to modify the default behavior.

Logins

The Rails integration handles logins on any path by detecting the presence of ?token=.... It will process the login and then immediately redirect back to the same path without ?token=. This helps prevent browsers and bookmarks from accidentally saving or caching the login token.

Likewise, the built-in handler for before_action :require_login will automatically redirect to LoginRocket when the user is not currently logged in. ?redirect_uri=<current_path> will be automatically included so that the user returns to the same place post-login. You can override this behavior by replacing before_login.

# For example, to force the user to always return to "/manage":
def require_login
  unless current_session
    redirect_to ar_login_url(redirect_uri: "/manage"), allow_other_host: true
  end
end

AuthRocket will verify the domain + path to redirect to. You can configure this at Realm -> Settings -> Connected Apps -> (edit) -> Login URLs. The first URL listed will be the default, so it should generally match your “just logged in” path.

Paths are validated as “equal or more specific”. That is, if Login URLs contains https://my.app/manage, then any path starting with /manage will be allowed, but /other will not be allowed. If you want to allow any path at your domain, add https://my.app/ (since / will match any path).

Logouts

The default post-logout path

Upon logout, the user will be returned to the root path (“/”).

This default path may be changed using an initializer. Create/edit config/initializers/authrocket.rb and add:

AuthRocket::Api.post_logout_path = '/other'
/logout route

The default route for logout is /logout. To override it, add an initializer for AuthRocket (eg: config/initializers/authrocket.rb) and add:

AuthRocket::Api.use_default_routes = false

Then add your own route to config/routes.rb:

get 'mylogout' => 'logins#logout'
The logout action

AuthRocket’s default login controller automatically sets a logout message using flash.

You may customize this, or other logout behavior, by creating your own LoginsController and inheriting from AuthRocket’s controller:

class LoginsController < AuthRocket::ArController
  def logout
    super
    flash[:notice] = 'You have been logged out.'
  end
end

If you wish to replace all of the login logic, create a new controller that doesn’t inherit from AuthRocket::ArController (and also override the routes, as per above). You may wish to look at ArController as a reference.

Verifying login tokens

If you’re not using the streamlined Rails integration, you’ll need to verify login tokens on your own (unless you’re using the API to authenticate directly).

JWT verification

AuthRocket’s login tokens use the JWT standard and are cryptographically signed. Verifying the signature is extremely fast. Here’s an example:

def current_user
  @_current_user ||= AuthRocket::Session.from_token(session[:ar_token])&.user
end

from_token returns nil if the token is missing, expired, or otherwise invalid.

API verification

AuthRocket also supports Managed Sessions, which enables you to enforce logouts, even across apps (single sign-out!). In this instance, the session is regularly verified using the AuthRocket API.

def current_user
  @_current_user ||= AuthRocket::Session.retrieve(session[:ar_token], cache: { expires_in: 15.minutes })&.user
end

For better performance (and to avoid API rate limits), you will want to cache the results of the API call for 3-15 minutes.

If using Rails, Rails.cache is used by default. Otherwise, you must configure a cache store for AuthRocket. In either case, see Caching below.

Initial login

Each of the above are designed for ongoing use. The initial login isn’t going to be much different though. Here’s an example login action:

def login
  if params[:token]
    if AuthRocket::Session.from_token(params[:token])
      session[:ar_token] = params[:token]
      redirect_to '/'
      return
    end
  end
  redirect_to AuthRocket::Api.credentials[:loginrocket_url], allow_other_host: true
end

Changing locales

The AuthRocket Core API supports multi-locale access. See Localization for the currently supported locales and other details.

If you are using the streamlined Rails integration alongside LoginRocket, it may not be necessary to set the locale for API access. The locale is primarily used for generating localized error messages. This is only useful for API operations that might generate errors. When handling logins and signups via LoginRocket, LoginRocket will handle all of this for you.

When the Accept-Language header is not sent, the API defaults to English.

Global locale

To set a global locale for your app, add this to your AuthRocket initializer:

AuthRocket::Api.default_headers.merge!(
  accept_language: 'en'
)

Per-request locale

If your app supports multiple locales, then you’ll likely want to set the locale on a per-request basis. Add a headers: { accept_language: 'en' } argument to relevant API calls:

AuthRocket::User.create(
  email: 'jdoe@example.com',
  password: 'secret!',
  headers: { accept_language: 'en' }
)

Caching

The AuthRocket gem is capable of caching the results of GET requests. Since authentication and user data generally needs to be timely, this is opt-in on a per-request basis. The most common use is when validating sessions via the API.

To enable caching, a cache store must be configured. On Rails, authrocket automatically uses Rails.cache, so simply ensure that’s setup appropriately.

If not using Rails (or if you wish to use a different cache store even when using Rails), add this to your AuthRocket initializer:

cache_options = {} # app specific
AuthRocket::Api.cache_store = RedisCacheStore.new(cache_options)

Any Rails-compatible cache store should work.

Next, enable the cache for specific API calls:

# To avoid caching for too long, it's recommended to set a specific expiration time.
AuthRocket::Session.retrieve(token, cache: { expires_in: 5.minutes })

# However, it's possible to leave out :expires_in and use the cache store's default.
# Warning: Ensure the cache store has a default expiration, otherwise cache entries
# will last forever!
AuthRocket::Session.retrieve(token, cache: {})   # These are identical
AuthRocket::Session.retrieve(token, cache: true)

# All options in cache: { ... } are passed directly to the cache store, so anything
# supported by your cache store is valid.
AuthRocket::Session.retrieve(token, cache: { expires_in: 15.minutes, force: true })

Requests

See the AuthRocket API documentation for available APIs, parameters, and responses, all with Ruby-specific examples.

Overview of common requests

Most resources support some or all of the below. Check specific APIs to confirm existence of specific functions.

List [resources]

For any resource with a “List [resources]” method, both all() and first() are available.

# Retrieve multiple resources at once
results = AuthRocket::Org.all
results[0].id  # ID of the first returned resource

# Retrieve the first 20 resources
results = AuthRocket::Org.all(max_results: 20)

# Retrieve the next 20 resources
if results.more_results?
  results = AuthRocket::Org.all(
    max_results: 20,
    after: results.last.id  # ID of the last resource previously received
  )
end

# A convenience function to retrieve the first resource when the ID isn't known.
# If there are no matching resources, returns null.
result = AuthRocket::Org.first
result = AuthRocket::Org.first(
  state: 'active'
)
result.id  # ID of the returned resource

Get a [resource]

For any resource with a “Get a [resource]” method, both find() and retrieve() are available.

result = AuthRocket::Org.find('org_SAMPLE')
result.id  # ID of the returned resource
result.name  # Name field of the returned resource
# raises an AuthRocket::RecordNotFound exception if ID not found

result = AuthRocket::Org.find('org_SAMPLE',
  some: 'condition',
  ...
)

# retrieve() is identical to find() except that it returns nil if ID not found
if result = AuthRocket::Org.retrieve('org_SAMPLE')
  ...
else
  # not found
end

Create a [resource]

For any resource with a “Create a [resource]” method, both create() and create!() are available.

result = AuthRocket::Org.create(
  name: 'Widgets Inc'
)
if result.errors?
  result.errors  # array of error messages
end
if result.valid?  # opposite of result.errors?
  result.id  # ID of the new resource
end

# Like ActiveRecord, create!() raises an AuthRocket::RecordInvalid
# exception if the new resource cannot be created.
begin
  result = AuthRocket::Org.create!(
    name: 'Widgets Inc'
  )
rescue AuthRocket::RecordInvalid => e
  e.object  # contains the invalid resource
  e.object.errors  # array of error messages
end

Update a [resource]

For any resource with an “Update a [resource]” method, .update(), .update!(), #update(), and #update!() are all available.

# Update a resource without finding it first. Returns updated resource.
result = AuthRocket::Org.update('org_SAMPLE',
  name: 'Widgets Inc'
)
result.errors?  # like create() above
result.valid?   #  "
result.errors   #  "

# Like update(), but raises an AuthRocket::RecordInvalid exception if
# the new resource cannot be updated. See create!() above.
result = AuthRocket::Org.update!('org_SAMPLE',
  name: 'Widgets Inc'
)

# If the resource is already loaded, it can be updated directly too.
# In this case, #update() returns false if there was an error.
# #update!() raises an exception as expected.
resource = AuthRocket::Org.find('org_SAMPLE')
if resource.update(name: 'Widgets Inc')
  # success
else
  resource.errors  # array of error messages
end

resource.update!(name: 'Widgets Inc')

Delete a [resource]

For any resource with a “Delete a [resource]” method, .delete(), .delete!(), #delete(), and #delete!() are all available.

# Delete a resource without finding it first.
# In most cases, returns a pseudo-resource (with just the ID field) on
# success. On occasion the API returns the entire resource.
# In either case, #errors?, #valid?, and #errors are all available,
# just like with #update.
result = AuthRocket::Session.delete('kss_SAMPLE')
result = AuthRocket::Org.delete('org_SAMPLE', force: true)
result.errors?  # like create() above
result.valid?   #  "
result.errors   #  "

# Like delete(), but raises an AuthRocket::RecordInvalid exception if
# the new resource cannot be deleted/updated, likely due to some
# dependency or state validation requirement. See create!() above.
result = AuthRocket::Org.delete!('org_SAMPLE')

# If the resource is already loaded, it can be deleted directly too.
# In this case, #delete() returns false if there was a validation error.
resource = AuthRocket::Org.find('org_SAMPLE')
if resource.delete
  # success
else
  resource.errors  # array of error messages
end

resource.delete!

# Hint: in all cases, if the resource is already deleted, an
# AuthRocket::RecordNotFound exception will be raised.

Per-request credentials, caching, and headers

Credentials may be modified on a per-request basis. If :url is not provided, the global :url value is used. No other global values are inherited. Allowed values are the same as when setting AuthRocket::Api.credentials = { ... }. See Configuration above.

resource = AuthRocket::Org.find('org_SAMPLE',
  credentials: { ... }
)

# Credentials are remembered when updating or deleting an already
# retrieved resource.
resource.update(...)  # Uses same :credentials as above

Caching may also be enabled per-request. See Caching above for details.

resource = AuthRocket::Org.find('org_SAMPLE',
  cache: { ... }
)

Likewise, headers may be added or modified. Headers are merged with the default headers, so only new ones need to be provided.

AuthRocket::Org.create(
  name: 'Widgets Inc'
  headers: { accept_language: 'en' }
)

On the off chance that a resource field name collides with :credentials, :cache, or :headers, resource fields may be placed inside :params. Using :params is allowed at any time, and is occasionally handy for other purposes too.

It’s also worth noting that symbol and string keys are both allowed, and may be mixed and matched as long as they don’t duplicate one another. There’s no need to convert them ahead of time.

# These are the same, but the latter assures that field names cannot
# conflict with other arguments, like :credentials.
AuthRocket::User.create(
  first_name: 'John',
  'last_name' => 'Doe',
  credentials: { ... }
)
AuthRocket::User.create(
  params: {
    first_name: 'John',
    'last_name' => 'Doe',
  },
  credentials: { ... }
)

# Mixing inside and outside :params is not allowed.
AuthRocket::User.create(
  first_name: 'John',           # will be ignored because :params is present
  params: { last_name: 'Doe' }  # will be used
)

Responses

API methods that return multiple results will return an Array with an added more_results? method.

results = AuthRocket::Org.all(max_results: 20)
results.each{|resource| ... }
if results.more_results?
  # retrieve next set of results, show a Next button, etc.
end

Fields may be accessed directly or with Hash syntax.

# These are all the same.
resource.id
resource[:id]
resource['id']

Rails compatibility

Resource objects are quite ActiveModel-like and generally compatible with ActiveView, including Rails form helpers.

One difference is attributes cannot be changed in advance of persistence. Instead, call update(...) directly with new values.

# NOT allowed
resource.name = 'New name'
resource.save

# Do this instead
resource.update(name: 'New name')

While objects cannot be modified in advance, it can still be useful to build a new, empty object for the purposes of rendering a form.

def new
  @resource = AuthRocket::Org.new
end
<%= form_for @resource do |f| %>
  <%= f.text_field :name %>
  ...
<% end %>