Project

super_auth

0.0
The project is in a healthy, maintained state
Simple, yet super powerful authorization for you application
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

Runtime

>= 0
 Project Readme

SuperAuth

Build Status

Super auth is turn-key authorization gem that makes unauthorized access unrepresentable. Stop writing tests for authorization with confidence

The intent is to use with ruby applications, as well as centralize authorization for multiple applications. If you look at the OWASP top vulnerabilty, broken access control is the NUMBER 1 most common security risk in modern applications today. super_auth provides a authentication strategy that allows you to completely de-risk your application, solving this issue once confidently.

Installation

gem "super_auth"

Docs

How super_auth stacks up against other authentication strategies: Do you really understand Authorization

Graph Visualization

SuperAuth includes an interactive graph visualization tool to help you understand and debug your authorization rules!

SuperAuth Visualization

See the complete authorization graph with:

  • Color-coded nodes (Users, Groups, Roles, Permissions, Resources)
  • Interactive path finding
  • Real-time authorization queries
  • Example scenarios from the README

Quick Start:

# 1. Generate initializer
rails generate super_auth:install

# 2. Mount the engine in config/routes.rb
mount SuperAuth::Engine => '/super_auth'

# 3. Load sample data (optional)
rails runner "load File.join(SuperAuth::Engine.root, 'db/seeds/sample_data.rb')"

Then visit: http://localhost:3000/super_auth/visualization

See VISUALIZATION.md for complete documentation.

Postgres Row-Level Security (optional)

The ByCurrentUser scope enforces authorization at the ORM layer. On Postgres you can additionally enforce the same rules inside the database itself, so raw SQL, unscoped, background jobs, and any other client on the same database are subject to them too — unauthorized rows become invisible at the connection level. Enforcement is pure SQL: participating apps don't load this gem, or Ruby, at all. The gem's role is administrative — define the graph, compile authorizations, enable the policies — which is what makes super_auth usable as a central authorization service for apps in any language.

The contract (any language)

Identity is asserted per transaction by calling the super_auth_become function that SuperAuth::RLS.enable installs:

BEGIN;
SELECT super_auth_become(user_external_id => '42', user_external_type => 'AppUser');
-- run normal queries; rows the user isn't authorized for don't exist --
COMMIT;  -- identity dies with the transaction; there is nothing to clear

For a user managed inside super_auth, pass user_id => '7' instead; system => true bypasses the policies (migrations, seeds, admin jobs).

The assertion is anchored to the calling transaction: super_auth_become sets transaction-local identity settings plus a stamp of the current transaction id, and every policy requires a stamp from the current transaction. Outside a transaction the settings have already reverted, and identity smuggled in as session settings carries a dead transaction's stamp — either way queries return no rows and writes are rejected. Misuse fails closed, and the scheme works unchanged behind transaction-pooling proxies like pgbouncer, because a transaction is exactly what they keep on one server connection.

Setup (Rails)

1. Match column types to your primary keys — before your first migration. The policies compare super_auth_authorizations.resource_external_id directly against your tables' pks with no casting, so the columns must share a type:

# config/initializers/super_auth.rb
SuperAuth.setup do |config|
  config.external_id_type = :bigint   # Rails' default pk type; use :uuid, :string, ... to match yours
end

If super_auth is already migrated with the wrong type, alter the four external id columns (super_auth_users.external_id, super_auth_resources.external_id, super_auth_authorizations.user_external_id, super_auth_authorizations.resource_external_id) in a migration of your own.

2. Enable RLS on the tables you want protected:

rails generate super_auth:rls Document Invoice
rails db:migrate

This creates one migration calling SuperAuth::RLS.enable(:documents, resource_type: "Document") per model — you can also call that directly for tables outside Rails. resource_type must match the resource_external_type used in your authorization rows (the model's class name when you use the AR integration).

3. Connect as a role RLS applies to. Superusers and BYPASSRLS roles skip policies entirely, so the app must not connect as one (owning the tables is fine — the policies use FORCE ROW LEVEL SECURITY). The role needs SELECT on super_auth_authorizations, which the policies read; EXECUTE on super_auth_become is granted to PUBLIC by default, so no extra grant is needed:

CREATE ROLE app_runtime LOGIN PASSWORD '...';
GRANT SELECT, INSERT, UPDATE, DELETE ON documents, invoices TO app_runtime;
GRANT SELECT ON super_auth_authorizations TO app_runtime;

4. Wrap work in an identity assertion. In Ruby:

SuperAuth.as(current_user) do
  # every query in here is enforced by the database
end

SuperAuth.as opens a transaction and calls super_auth_become for you — use it in an around_action (or around a job) to cover a whole request. Non-Ruby apps use the SQL contract directly. Each policy checks super_auth_authorizations with the same semantics as ByCurrentUser: type-level authorizations (resource_external_id IS NULL) act as a wildcard, per-record authorizations match on id, system? users bypass.

Notes

  • Queries with no identity asserted see nothing, and writes are rejected — fail closed, by design. A client that has never heard of super_auth cannot accidentally reach protected rows.
  • Creating rows requires a type-level authorization for that resource type (or system context): the policy is FOR ALL with no WITH CHECK, so Postgres reuses its USING expression as the implicit WITH CHECK for INSERTs and UPDATEs.
  • The transaction stamp calls pg_current_xact_id(), which assigns a real transaction id even to read-only transactions — one extra xid per protected transaction. Negligible for almost everyone; revisit with a virtual-xid variant only if transaction id churn ever matters at extreme read volume.
  • One external_id_type covers the whole install, so every protected table across every participating app needs the same pk type.
  • Postgres 13+ only (pg_current_xact_id). On other databases SuperAuth::RLS raises, and the ORM scope remains the enforcement layer.

Configuration

# config/initializers/super_auth.rb
SuperAuth.setup do |config|
  # Raise an error when a query runs without a current user set.
  # Default is :none (returns empty results silently).
  config.missing_user_behavior = :raise
end
Option Values Default Description
missing_user_behavior :none, :raise :none Controls what happens when SuperAuth.current_user is blank. :none returns an empty result set. :raise raises SuperAuth::Error.
external_id_type :string, :bigint, :uuid, ... :string Column type for the external id columns, applied when the migrations run. Set it to your application's primary key type so every comparison against your tables' pks is natively typed — no casting anywhere.

Usage

SuperAuth is a rules engine engine that works on 5 different authorization concepts:

  • Users
  • Groups
  • Roles
  • Permissions
  • Resources

The basis for how this works is that the rules engine is trying to match a user with a resource to determine access. The engine determines if it can find an authorization route betewen a user and a resource. It does so by looking at users, groups, roles, permissions.

                     +-------+       +------+
                     | Group |<----->| Role |
                     +-------+\    / +------+
                         ^     \  /     ^
                         |      \/      |
                         |      /\      |
                         |     /  \     |
                         V    /    \    V
+---------------+    +------+/      \+------------+    +----------+      +-------------------+
| YourApp::User |<-->| User |<------>| Permission |<-->| Resource | <--> | YourApp::Resource |
+---------------+    +------+        +------------+    +----------+      +-------------------+
                         ^                                  ^
                         |                                  |
                         +----------------------------------+

The lines between the boxes are called edges. Note that Group and Role trees.

In general the super_auth has 5 different pathing strategies to search for access.

1. users <-> group[s] <-> role[s] <-> permission <-> resource
2. users <->              role[s] <-> permission <-> resource
3. users <-> group[s] <->             permission <-> resource
4. users <->                          permission <-> resource
5. users <->                                         resource

Edges can be drawn between any 2 objects, allowing super_auth can seamlessly scale in complexity with you. When Group and Role are used, the rules will apply to all descedants. If there are any edges between the specified user and the resource, then access is granted.

You can see usage examples spec/example_spec.rb.

We're going to need some users:

Users:
  - Peter
  - Michael
  - Bethany
  - Eloise
  - Anna
  - Dillon
  - Guest (Unknown User)

Let's see an example company structure:

Groups:
  - Company
    - Engineering_dept
      - Backend
      - Frontend
    - Sales Department
    - Marketing Department
  - Customers
    - CustomerA
    - CustomerB
  - Vendors
    - VendorA
    - VendorB

We're going to define a roles:

Roles:
  - Employee
    - Engineering
      - Señor Software Developer
      - Señor Designer
      - Software Developer
      - Production Support
    - Sales and Marketing
      - Marketing Manager
      - Marketing Associate
  - CustomerRole

We're going to define some permissions:

Permissions:
  - create
  - read
  - update
  - delete
  - invoice
  - login
  - reboot
  - deploy
  - sign_contract
  - subscribe
  - unsubscribe
  - publish_design

Finally, we need some resources:

Resources:
  - app1
  - app2
  - staging
  - db1
  - db2
  - core_design_template
  - customer_profile
  - marketing_website
  - customer_post1
  - customer_post2
  - customer_post3

So we have sufficient prerequisite data to do some interesting authorizations. Let's draw some edges:

Peter <-> Frontend # Peter is on the Frontend team. (via Company->Engineering_dept->Frontend)
Engineering_dept <-> Engineering # Group "Engineering_dept" has the Role "Engineering"
Engineering <-> create # Engineering role can do basic CRUD operations
Engineering <-> read   # Peter can CRUD too
Engineering <-> update
Engineering <-> delete
core_design_template <-> create # Now, those CRUD permissions apply to core_design_template resource
core_design_template <-> read
core_design_template <-> update
core_design_template <-> delete

With this, the following paths are created from Peter to the core_design_template:

Peter <-> Frontend <-> Engineering_dept <-> Engineering <-> create <-> core_design_template
Peter <-> Frontend <-> Engineering_dept <-> Engineering <-> read   <-> core_design_template
Peter <-> Frontend <-> Engineering_dept <-> Engineering <-> update <-> core_design_template
Peter <-> Frontend <-> Engineering_dept <-> Engineering <-> delete <-> core_design_template

Which completes the circuit using the path
user <-> group <-> group <-> role <-> permission <-> resource

When you create/delete an edge new authorizations are generated and stored in the super_auth database table. Since the path is stored with the record, it trivial to audit access permissions using basic SQL.

TODO: Write usage instructions here

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/JonathanFrias/super_auth.

License

The gem is available as open source under the terms of the GPL.