Rails Authentication for Compliance
Tags: rails, security
Published: 2023-10-28 10:42:49

Suppose you are working on a Rails application that needs to meet specific security compliance requirements like **PCI**, **ISO 27001**, or **SOC2**. In that case, one of the objectives is to have proper authentication and access control.

The requirements differ between standards, but I gathered the most important ones from all of them to go through them.

### Authenticate access to critical assets
Let's see what we need to do to satisfy this requirement.

First of all, you must have a **strong password policy**.<br/>
I recommend asking for a minimum of 12 characters, with at least one uppercase letter and one number.

You can use Active Record validations for this. If you have a password attribute on your model, you can add a validation similar to this example:

```ruby
validate :password_complexity

def password_complexity
  if password.present? and !password.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{12,}$/)
    errors.add :password, "must include at least one lowercase letter, one uppercase letter, one digit, and needs to be minimum 12 characters."
  end
end
```

Additionally, you should validate that the password is not leaked. Luckily, there is a gem for that: <a href="https://github.com/philnash/pwned" target="_blank">https://github.com/philnash/pwned</a>.<br />
After installing the gem, All you need to do is add the following validation to the model:

```ruby
  validates :password, not_pwned: { on_error: :valid }
```

This will make a request to haveibeenpwned.com, mark the password as invalid if it has been pwned, and mark it as valid in case of a network or API error. You can find information about various configurations in the readme of the gem.

The final thing to prevent is credentials leaking. For this, you should store the passwords hashed with a robust hashing algorithm such as bcrypt.

### By default, restrict all access to critical assets to only those accounts and services that require such access

This one is more of an authorization than an authentication requirement. You should always use a whitelist approach for permissions and only permit access to certain information or features to the accounts that require it.

### Accompany authentication information by additional authentication factors

Your authentication mechanism should include **multiple factors**, something the user knows and something the user has. If you are using Devise, you can use the [devise-two-factor](https://github.com/devise-two-factor/devise-two-factor) gem. If you have custom authentication, you can use the [rotp](https://github.com/mdp/rotp) gem to generate OTP codes and verify those during login.

One common mistake with multi-factor authentication implementations is to not require the second factor after a password reset, which defeats the purpose of the feature, so make sure that doesn't happen.

###  Don't display sensitive system or application information to avoid providing an unauthorized user with any unnecessary assistance

You should show a generic message if someone tries to access a page requiring authentication.

During the login process, you should never indicate which part of the login credentials are incorrect, otherwise it is possible to enumerate the usernames. Also, you should validate the username and password together to ensure there is no indication of which one is incorrect.

When validating the password, you should prevent timing attacks by using a constant time algorithm during password comparison. Devise does this by default. If you have a custom authentication implementation, ensure you are not vulnerable to this attack.

### Protect against brute force log-on attempts and credential stuffing

You should ensure the password of accounts in your system can't be brute-forced or taken over by a credential stuffing attack from a list of leaked user credentials.

The first line of defense should be to put rate-limiting on your login endpoints. [rack-attack](https://github.com/rack/rack-attack) can help with that. I recommend to limit the login attempts to 5 per minute for a username and block the IP for 30 minutes. You should also limit the number of login attempts from the same IP address, but this needs to be adjusted to the application you are working on, because if it is a tool used in classrooms, it might be legit to have 50 logins within a few minutes from the same IP. (I have a few post written about <a href="/tags/rack-attack" target="_blank">rack-attack</a>)

After configuring rate-limiting, you should also introduce a captcha on the login page and trigger that for suspicious login attempts.

You could also send an email with a verification link in case of a suspicious login, for instance, from a non-usual location, device, etc.

Storing successful and unsuccessful login attempts can help to detect suspicious attempts. If you are using Devise, you can use the [ authtrail ]( https://github.com/ankane/authtrail ) gem for that.

### Don't leak clear text passwords

In 2023, everybody should use SSL/TLS, which prevents leaking clear text passwords over the network connections.

Also, you should never log passwords in clear text or let them leak if an exception is raised.

### Terminate inactive sessions after a defined period of inactivity, especially in high-risk locations

Your "remember me" feature should be opt-in, and the session length should be adjusted to the necessary security level of the application.

### Prevent concurrent sessions

You shouldn't allow concurrent sessions for an account to prevent session hijacking. To achieve this, you can save a fingerprint of the user on a successful login and trigger an MFA verification if the fingerprint changes during the requests.

I believe I covered all of the important points for compliance of the authentication, but if you think I missed something, please reach out.