Oidc connection without database

Hello,

Just experimenting with a setup that will allow a user to authenticate via OIDC without having to have a database table for allowed users. Which means that the credentials need to be stored in the database.

Libraries:

I tried storing the claims value in the session. And regularly checking that the “exp” time specified hasn’t been exceeded.

Which seems to work at first, but then I noticed static files don’t download anymore, because the session cookie is now too big:

431 Request Header Fields Too Large

OK, fair enough, probably don’t want to make the request too big. That could be somewhat inefficient, especially for static files. This claims is a map of 9 values:

%{
  "at_hash" => "<22 bytes>",
  "aud" => "scrooge",
  "c_hash" => "<22 bytes>",
  "exp" => 1635136662,
  "groups" => ["admin"],
  "iat" => 1635050262,
  "iss" => "https://xxx.example.org",
  "name" => "Brian May",
  "sub" => "<30 bytes>"
}

So maybe I only need to store important fields that I need, like “name”, “exp”, “sub”, and “groups”. But I worry this could also be a problem.

ETS based sessions, instead of cookie based sessions, might also be a possibility too, I guess. But some of the caveats put me off, e.g. no sharing of sessions between servers.

Does this mean that my goals are a not feasible? Maybe I should just create an db entry for authenticated user, store the required details, and look it up for email, sub id, session id, or something, and have login use a more conventional based approach.

https://hexdocs.pm/phoenix/1.3.0-rc.0/sessions.html#ets

Just curious, as I have some IOT web applications that don’t necessarily need or want a database except for authentication if there is some way to fix up the authentication not to require it also.

Possibly I am not thinking clearly through this, a fresh point of view would be appreciated.

Regards

If you want to keep using Cookies, you could increase the max_header_value_length and max_request_line_length for cowboy_http, e.g:

config :my_app, MyAppWeb.Endpoint,                                                                                                                 
  http: [
    protocol_options: [
      max_request_line_length: @req_line_length, 
      max_header_value_length: @header_value_length
    ]
  ]

If you want sharing between servers, maybe take a look at mnesia. Not an expert there, put it has support for clustering.

Author of Plugoid here :slight_smile:

I’ve written extensive doc on cookies here: Cookie configuration. Also the library is maintained but not actively developed, simply because the initial scope is fully implemented (which is the base standard without the fancy additional standards that are being developed by the OIDC working group).

I tried storing the claims value in the session.

Which ones are needed and why?

OIDC returns the "sub" attributes which is a unique user identifier on a specific OpenID Provider (OP). This can be sufficient to determine which user you’re talking to. With Plugoid you can use Plugoid.subject/1 to retrieve it.

You can take a look at plugoid_demo to see how it works.

And regularly checking that the “exp” time specified hasn’t been exceeded.

The "exp" claim is the expiration of the ID token, that is the proof of authentication. In old SAML days, it would be short (a few minutes) so as it cannot be reused but long enough in case of clock skew. Although the OIDC specification is not crystal clear, it seems this is the same idea (Final: OpenID Connect Core 1.0 incorporating errata set 1). Plugoid handles that by redirecting the user to the OP from time to time to check if the user is still authenticated (see Plugoid - Session).

Does this mean that my goals are a not feasible?

Feasible if you can keep the data small in cookies (see @moogle19 answer to raise cookie size limits). Mnesia is hard to operate at the moment because you need to handle autoclustering and resolve netsplits by yourself. If you have another store such as Redis you could consider using it.

Hmmm. According to Firefox, request headers are 4.471kb, and default limit is 4kb. Not that far over. Tempting…

I already have clustering setup, and for occasional errors due to netsplit for session management probably not too bad. So might be worth investigating.

I attempted to use plugoid, but then stopped when I found the following dependency error, and then I noticed the age of the project.

Failed to use "phoenix_html" (version 3.0.4) because
  phoenix_ecto (version 4.4.0) requires ~> 2.14.2 or ~> 3.0
  phoenix_live_view (version 0.16.4) requires ~> 3.0
  plugoid (versions 0.4.1 and 0.4.2) requires ~> 2.0
  mix.lock specifies 3.0.4

For my application, would need, without database backing would need:

  • name → display name of logged in user.
  • groups → check access of user.
  • exp → check claim still valid.

I suspect I don’t really need anything else. I could see applications for requiring email, but for my purposes, not required.

I mentioned sub, but this probably would not be so important, because it has no real value except to look up information from some other source, e.g. a database.

If I filtered out the attributes I don’t need, that probably would help.

Thanks for the advice.

OK, this reduced header size (as reported in Firefox) to 4.3kb. Which curiously was acceptable. I guess Firefox must measure this value slightly differently to how cowboy does.

Later: Oops. I feel like an idiot. Suddenly woke up and realized I never call “clear_session” and old test values were accumulating in my session.

Now, after clearing session on login and logout, Firefox reports header size 1.7kb much better.

Later then later: I remember why I said I need sub before. My code has in login:

put_session(:live_socket_id, "users_socket:#{user.id}")

And for logout:

ScroogeWeb.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{})

As is recommended approach to force logout everywhere.

https://hexdocs.pm/phoenix_live_view/security-model.html#disconnecting-all-instances-of-a-live-user

Guessing I could replace #{user.id} with #{sub}. Does this make sense?

(Obviously if I am still logged in an oidc provider this is going to provide much security benefit, but this is something to worry about later)

Fixed in 0.4.3. Feel free to open issues next time it happens :slight_smile:

Again, you shouldn’t have to do that. This expiration time in the ID token is the expiration of the proof of authentication, not the expiration of the user’s session.

Disconnecting when the user’s session ends is actually very hard. There are 3 draft standards ( OpenID Connect Session Management 1.0 - draft 30, OpenID Connect Front-Channel Logout 1.0 - draft 04 and OpenID Connect Back-Channel Logout 1.0 - draft 06) but none is satisfying. Some use old tricks (invisible iframes for example) that are more and more blocked by browsers for security reason. This is why the best thing right now is probably to check from time to time the user is still authenticated on the external provider (OP).

Also note it’s the size per cookie, but you can store many cookies of 4kb :wink: What is the maximum size of a web browser's cookies value?

However all the cookies are sent in a single header, so you have to raise the Cowboy’s limit.

If your user.id is different on each device, and you want to disconnect the user from all devices, then you indeed have to use the sub claim.

Yes. The OP is the source of truth for authentication. But on logout you can trigger logout at the OP too.

Thank for your help.

Oops. I guess I should have filed a bug report. So use to dealing with dead projects where the maintainer doesn’t even look at a PR that fixes a critical issue. Sorry.

Wondering if I should switch to plugoid from my working openidc_connect. On the plus side:

  • In theory, could mean I use shared logic for login, checking session exists, logging out at OP, etc. Without having to remember to update each and every app as standards change, etc.
  • More likely to get security code peer reviewed, which is probably a good thing.

On the downside:

  • Obviously means more initial work.
  • Not sure if you can do everything I currently need, yet.
    • Can I add that put_session call for live sessions into the login process?
    • Can I check the user’s OIDC groups? Ideally need to be able to check user is in group before displaying link and need a plug to check that the user is in a required group.