opserver / Opserver

Stack Exchange's Monitoring System
https://opserver.github.io/Opserver/
MIT License
4.5k stars 823 forks source link

Security: Add OpenId Connect provider #387

Closed deanward81 closed 3 years ago

deanward81 commented 3 years ago

This changes a few fundamentals of how the SecurityProvider model works. It makes the provider itself responsible for creating a ClaimsPrincipal that is then persisted using the usual ASP.NET Core authentication bits (in this case by a cookie).

Additionally, it adds support for different kinds of "token" that each provider validates in order to allow a user to login. E.g. the ActiveDirectoryProvider expects a UsernamePasswordToken, whereas the OIDCProvider expects an OIDCToken containing an ID token encoded as a JWT retrieved from the IdP.

The login flow itself is modified to support the different types of authentication expected by the various providers - that means changes in the Login view and in the AuthController to add support for OpenId Connect authorization callbacks. If the provider expects UsernamePassword authentication then a username/password form is rendered in the view, if the provider only requires a username (EveryonesAnAdmin or EveryonesReadOnly) then it renders just a username field and if it's an OIDC flow a login button is rendered that initiates the OIDC authorization flow:

Username/Password Login View

image

Username only Login View

image

OpenId Connect Login View

image

Note that the username-only flow is a new concept, but fits with how EveryonesAnAdmin and EveryonesReadOnly work under the hood (only caring about the username for audit trail purposes). However, I'm not sure if that's how it's intended to be seen so happy to revert that bit if necessary.

Settings have been separated for each provider - there are base settings that are potentially applicable to any provider (kept in SecuritySettings) and then there are derived implementations for each individual provider that needs their own settings. Currently that means OIDCProvider and ActiveDirectoryProvider get their own settings classes. These implementations (and the base one) are registered in the container because the IOptions framework doesn't support polymorphism. Depending on the implementation requested the SecurityManager ensures the right settings are passed at runtime. A new abstract class SecurityProvider<TSettings, TToken> ensures that derived implementations get strongly-typed methods that they can operate against for token validation and accessing their settings.

Under the hood the OIDC flow redirects to the IdP's authorization URL and uses that authorization code to retrieve an access token. It extracts the ID token (encoded as a JWT) from this access token and uses it to extract the relevant claims - notably the name of the user and the groups that they belong to. Okta, OneLogin and Azure all support sending group claims in the ID token but Gsuite does not, making it effectively unsupported for now.

Finally, this adds documentation for configuring the OIDC provider and a smattering of unit tests to make sure it works properly.