The Crystal Programming Language Forum

Introducing *Shield* -- Comprehensive security for Lucky framework

Shield is a comprehensive security solution for Lucky framework. It features robust authentication and authorization, including user registrations, logins and logouts, password resets and more.

Shield is secure by default, and exploits defence-in-depth strategies, including pinning an authentication session to the IP address that started it – the session is invalidated if the IP address changes.

User IDs are never saved in session. Instead, each authentication gets a unique ID and token, which is saved in session, and checked against corresponding values (hashed) in the database.

Shield is designed to be resilient against critical application vulnerabilities, including brute force, user enumeration, denial of service and timing attacks.

On top of these, Shield offers seamless integration with your application. For the most part, include a bunch of module s in the appropriate class es, and you are good to go!

Find source and documentation on Github:

9 Likes

I haven’t looked at the code yet, but I like the idea. Has the Lucky team given any feedback on this shard at all? Getting their stamp of approval on a shard like this would probably help establish it’s trustfulness as a good security solution.

@paulcsmith thought it “looks cool”.

@jwoertink thinks it’s “nice!”

@luckyframework (on twitter) calls it “extremely secure”.

I’m not sure these constitute a “stamp of approval” though.

If you find the time to check the code yourself, would be glad to know what you think.

2 Likes

I don’t know much about security, but just looking through the code, it looks pretty good to me! So I support it :+1:

1 Like

I did a brief look and it looked pretty sound. I have not done anything super in-depth, but it looks secure to me.

I like that it also makes it easy to send emails when a password or email changed, which is a good security practice. Tying the session to the IP also helps with security, but may not always be a good idea for user convenience. If you start a session on one IP and then switch to another (for example going from cell to WIFI) it would log you out. So there are some tradeoffs. Depends on how secure you want it to be, and how users will react.

Overall though I think this is a great idea and I think we may use some of this in the default Lucky auth in the future. For example, I think sending emails for password changes or email chagnes is a great thing to include by default!

1 Like

I like that it also makes it easy to send emails when a password or email changed,…

Currently, email notifications are sent for password changes and user logins (if the user opts for them), not for email changes. I’m currently working on email confirmations (verifications), which should send an email for the purpose of verifying the new email address.

Tying the session to the IP also helps with security, but may not always be a good idea for user convenience.

This is agreeable. A team should assess the security needs of their app, and disable any they find an overkill for their specific use case. In Shield, most features can be disabled by skipping the relevant action pipes.

Overall though I think this is a great idea and I think we may use some of this in the default Lucky auth in the future.

If you do, let me know if there are specific areas I can help with.

Since we are into “cool” and all… :slight_smile:

One cool feature I should probably highlight is that if password changes, either via a user updating their profile or through a password reset, the user is logged out on all devices (except the current one).

This ensures that when a user resets their password after a credential compromise, the attacker has no access to the account after that point, since their session would be invalidated.

3 Likes

This would be a massive problem on mobile connections, as dynamic IPv6 addresses are reassigned extremely frequently. I heard as frequently as every 10 minutes, although I can’t remember where. I would default this feature to “off”.

I heard as frequently as every 10 minutes, although I can’t remember where

I should look into this. If you have any links/leads, I would be happy to look at them.

This worries me, since every client on a NATed networks (ex: coffee shop wifi) share the same external IP address. Wouldn’t it be possible to recover another client’s session if both share the same external IP? Also, even if you used (client IP, src port) tuple, it might still be possible for a malicious client to bruteforce connections from behind the same NAT until the malicious client lands on another user’s (client IP, src port) combination.

Wouldn’t it be possible to recover another client’s session if both share the same external IP?

No. Sessions in Lucky are stored as cookies (encrypted and signed) on the client. Unless an actor manages to steal your cookies, they cannot pose as you.

Stealing your cookies may be easier if you are on the same network as a bad actor. In this case, disabling this specific Shield feature would not make your security any better.

Also, even if you used (client IP, src port) tuple, it might still be possible for a malicious client to bruteforce connections from behind the same NAT until the malicious client lands on another user’s (client IP, src port) combination

Your login status is not determined by your IP address.

Pinning an IP to the login session in Shield is an added defence layer. If your cookies get stolen, the actor needs be on the same IP as you, to be able to use it.

I hope I addressed your concerns?

3 Likes

Ah ha. So the session cookies are never persisted on the server-side and can not be retrieved? Some applications do prefer to use a database backed session store in order to forcibly expire user sessions.

1 Like

From a usability perspective, do I have to login again each time my device gets a new IP address for any reason?

If I’m using the browser on my phone and it switches cell towers, I could get a new IP address. Same if I leave my house and my phone switches from wifi to LTE.

1 Like

Shield can, and does, forcibly expire user sessions. Every successful login gets an entry in the :logins table of the database. This table has a :status column, which is the canonical determiner of login status.

If this column is set to anything other than Started, session cookies are rejected; whether or not they are valid would not matter.

This is how Shield is able to log a user out of all devices when their password changes, for instance.

2 Likes

This is a very valid concern. To quote an earlier answer:

This is agreeable. A team should assess the security needs of their app, and disable any they find an overkill for their specific use case. In Shield , most features can be disabled by skip ping the relevant action pipes .

In sum, Shield does not make any assumptions about an application’s security policy, or usability requirements. It simply locks everything down, by default.

It is up to the application developer(s) to disable specific features it does not need.

I’ve added notes in the relevant sections of the documentation about this.

1 Like

I guess what I don’t understand is how are sessions “pinned” to the IP address. If another user on the same IP requests a new session cookie, are both sessions valid or will the new session override the other pre-existing session? Is it ever possible to recover a pre-existing session if you have access to the IP address?

I guess what I don’t understand is how are sessions “pinned” to the IP address

That’s OK. I’ll try to explain:

When a registered user logs in, Shield generates a cryptographically secure random token, salts and hashes it. The token digest is saved to the database, along with the IP address of the user who just logged in, and some other details.

After a successful database save, Shield stores the ID of the database entry, and the raw token, in the user’s session.

When a user tries to access a route that requires login, Shield checks their session for an ID and token that matches a corresponding ID and token digest in the database.

If a valid match is found (and the pin-login-to-ip feature is disabled), they are allowed access to the route. Otherwise, access is denied.

If a valid match is found, and the pin-login-to-ip feature is enabled (it is, by default), the client’s current IP address is compared with the IP address that was previously saved to the database after successful login.

If they 2 IPs match, access is granted. Otherwise, access is denied.

If another user on the same IP requests a new session cookie, are both sessions valid or will the new session override the other pre-existing session?

Sessions are unique to a client. So 2 different users get 2 different, separate sessions. Even the same user on 2 different browsers get 2 different, independent session cookies on each browser. Same IP or not does not matter. Sessions are not tied to the IP address.

Is it ever possible to recover a pre-existing session if you have access to the IP address?

No. You must have access to the same client/browser (or steal the cookies via some other means). Then, if the session has not expired, you may be able to use it.

If the pin-login-to-ip feature is disabled, you may be able to use the stolen cookies right away.

If it is enabled, Shield would perform an extra check that your current IP address matches the IP address in the database.

Hope this helps?

3 Likes

Shield looks really nice.

3 questions:

  1. Does it support some kind of api_key/token authentication? For use in APIs on per transaction basis. ie. AuthLogic for Ruby
  2. Does it support OAuth/OAuth2 authentication? Something that can be used to authenticate user against Facebook, Github, Google and the likes?
  3. Authorization at this point is just checking if current_user has basic rights. Do you have any plans on expanding it? Do you plan to go Pundit or CanCan route, or some kind of other approach?

Thank you. Looks like a great shard!

I think you can use Shield in conjunction with https://github.com/confact/lucky_can for the Pundit type stuff. (I haven’t tried it, but I would assume it works)

2 Likes

Does it support some kind of api_key/token authentication? For use in APIs on per transaction basis. ie. AuthLogic for Ruby

I’ve thought about adding bearer authentication to the roadmap, for APIs sake. I think it should be such that adding the full OAuth2 flow in future should be seamless. I’m still thinking about this. I’ll look into AuthLogic for possible inspiration.

Does it support OAuth/OAuth2 authentication? Something that can be used to authenticate user against Facebook, Github, Google and the likes?

No. See previous answer.

Authorization at this point is just checking if current_user has basic rights. Do you have any plans on expanding it? Do you plan to go Pundit or CanCan route, or some kind of other approach?

Shield supports authorization at the action level. It uses a #check_authorization action pipe to enforce authorization. This pipe checks for the return of a #authorize? : Bool method in the action.

If this method returns true access is granted to the action. Otherwise, access is denied (the default).

You define authorization rules for an action, in the action. For instance:

def authorize? : Bool
  current_user!.level.admin? || post.user_id == current_user!.id
end

I feel this is simpler and more Luckyish. It should fulfill most authorization use cases, I believe.

You may look into Praetorian or LuckyCan, if Shield’s is not enough for your use case.

Thank you. Looks like a great shard!

You’re welcome :+1:

2 Likes