Security Classes
-
IAuthenticationContext and implementation is what contains information about an authenticated user for a request. Includes things like the relevant
InstanceUserand any associated rights. -
IAuthenticationContextFactory and implementation is a factory for
IAuthenticationContexts. It handles things related to the database for a user's authentication. This includes loading their rights/associated instance user. It will also stop the request if the users token was issued before the last time their password orEnabledstatus was updated. -
AuthenticationContextClaimsTransformation is used to associate rights with a request context so that it may properly pass appropriate
TgsAuthorizeAttributes. -
ICrytopgraphySuite and implementation is used to generate secure strings and byte arrays. It also contains the password hashing and validation logic.
-
IIdentityCache and implementation is used to store
ISystemIdentitys for the duration of their associated tokens as IdentityCacheObjects. -
ITokenFactory and implementation is used to generate the Json Web Token for a session after a user successfully authenticates.
-
ISystemIdentitys represent a logon session with the operating system for a given user. It contains a method to run code under the security context of said user.
-
ISystemIdentityFactory is used to create
ISystemIdentitys by attempting to log the user in with the OS with a given username and password. -
TgsAuthorizeAttribute is a special attribute applied to controller methods to define which rights are required to run a verb.
-
OAuth contains classes related to OAuth 2.0 authentication
A Basic Rundown of the Authenticaton Pipeline
For the login request (POST /)
- An attempt to parse the
ApiHeadersis made. If they were valid, the API version check is performed. If it fails, HTTP 400 with anErrorMessageResponsewill be returned. - If, for some reason, the user attempts to use a JWT to authenticate this request, steps 2-4 of the non-login pipeline list below are performed.
- The
ApiControllerbase class inspects the request.- At this point, if the
ApiHeaders(MINUS theAuthorizationheader) cannot be properly parsed, HTTP 400 with anErrorMessageResponseis returned.
- At this point, if the
- The
HomeControllerinspects the request.- If the
ApiHeaderscould not be properly parsed, HTTP 400 (or 406 if theAcceptheader was bad) with anErrorMessageResponseis returned.- The
WWW-Authenticateheader will be set in this response.
- The
- If authentication succeeded using a JWT
Bearertoken, HTTP 400 with anErrorMessageResponseis returned. Refreshing a login using a token is not permitted. - At this point, the path diverges based on the credential type.
- If the user is using a username/password combo:
- The username and password combination is tried against the OS authentication system (currently a no-op on Linux).
- If it succeeds, the session is held on to for future reference and the database is queried for a user matching the SID/UID of the login session.
- Otherwise, the database is queried for a user matching the canonicalized username.
- The username and password combination is tried against the OS authentication system (currently a no-op on Linux).
- If the user is using an OAuth code:
- If the OAuth provider is disabled in the configuration, HTTP 400 with an
ErrorMessageResponseis returned. - The code is sent to the external provider for validation
- If the provider is GitHub, there's a chance that this could fail due to rate limiting. In this case, HTTP 429 is returned.
- If the provider rejects the OAuth code, HTTP 401 is returned.
- The database is queried for a user matching the OAuth provider and external user identifier sent with the OAuth provider's response.
- If the OAuth provider is disabled in the configuration, HTTP 400 with an
- If the user is using a username/password combo:
- If the query selected above produces no results, HTTP 401 is returned.
- For non-OAuth logins, maintenance is performed on the user's DB entry at this point
- For non-OS logins:
- The provided password is hashed and checked against the database entry. If it does not match, HTTP 401 will be returned.
- This can potentially cause a change to the DB's stored
PasswordHashif TGS has updated its dependencies and Microsoft has decided to deprecate the previous hashing method since the user last logged in.- If this occurs, it invalidates all previous logins for the user.
- This can potentially cause a change to the DB's stored
- The provided password is hashed and checked against the database entry. If it does not match, HTTP 401 will be returned.
- For OS logins:
- If the
PasswordHashin the DB isn't null, it is set as such. This invalidates all previous logins for the user. - If the
Namein the DB does not match the user's OS login, it is updated.
- If the
- For non-OS logins:
- If the user's database entry says they are not enabled, HTTP 403 is returned.
- A token is generated from the ITokenFactory.
- For OS logins, the user's login session is cached for the duration of the token's validity plus 15 seconds.
- The token is returned as a
TokenResponsewith an HTTP 200 status code.
- If the
For all other authenticated requests
- An attempt to parse the
ApiHeadersis made. If they were valid. The API version check is performed. If it fails, HTTP 400 with anErrorMessageResponsewill be returned. - The JWT, if present, is validated. If it is, the scope's AuthenticationContextFactory has
SetTokenNbfcalled. If not, HTTP 401 will be returned.- Inside ASP.NET Core, this initializes the calling user's identity principal and sets the "sub" claim to the TGS user ID parsed out of the JWT.
- We know it's the user ID because we set it up like that in the TokenFactory
- Inside ASP.NET Core, this initializes the calling user's identity principal and sets the "sub" claim to the TGS user ID parsed out of the JWT.
- The AuthenticationContextClaimsTransformation is run (this does not short circuit to responses).
- This invokes
IAuthenticationContextFactory.CreateAuthenticationContextusing the "sub" claim from the user's identity and the "nbf" timestamp set earlier (We don't get this from the scope's IApiHeadersProvider because there may be other errors preventing theApiHeadersfrom being parsed).- At this point, the database lookup using the user ID occurs. This hyrates the scope's AuthenticationContext (which is available at the start of the request, but uninitialized). If the user is a system user, their login session is pulled from the cache. This is also where the instance data for a request is loaded if the user has a valid
InstancePermissionSetfor that instance. The user needs to have a few prerequisites for a valid IAuthenticationContext to be generated:- The user with the matching ID must exist in the database.
- The last time the user's password or
Enabledstatus changed must be before the "nbf" of their token"
- If the user logged in with an OS login, the session is retrieved from the cache here and added to the scope's AuthenticationContext.
- At this point, the database lookup using the user ID occurs. This hyrates the scope's AuthenticationContext (which is available at the start of the request, but uninitialized). If the user is a system user, their login session is pulled from the cache. This is also where the instance data for a request is loaded if the user has a valid
- If a valid authentication context is returned from the IAuthenticationContextFactory, the AuthenticationContextClaimsTransformation uses the context to add claims for each permission bit to the user's identity principal.
- Internally, ASP.NET Core uses this to determine whether or not a request to an endpoint will 403 or not based on the parameters of its TgsAuthorizeAttribute.
- This invokes
- The authorization filter is invoked
- For non-SignalR hub requests, this is the
IAuthorizationFilterpart of the TgsAuthorizeAttribute. It does two simple things:- It checks the validity of the scope's IAuthenticationContext. If it is invalid (indicating the user is not authorized either due to not existing (Only possible with a forged and signed JWT) or if their token was outdated compared to the last time their password or
Enabledstatus was updated), HTTP 401 will be returned. - It checks the user's
Enabledstatus. If the user is disabled, HTTP 403 will be returned.
- It checks the validity of the scope's IAuthenticationContext. If it is invalid (indicating the user is not authorized either due to not existing (Only possible with a forged and signed JWT) or if their token was outdated compared to the last time their password or
- For SignalR hub requests, this is the AuthorizationContextHubFilter.
- If either IAuthenticationContext is either invalid OR unauthorized, it unceremoniously aborts the connection.
- For non-SignalR hub requests, this is the
- The
ApiControllerbase class inspects the request.- If the
ApiHeaderscould not be properly parsed, HTTP 400 (or 406 if theAcceptheader was bad) with anErrorMessageResponseis returned. - If the request is to an Instance component path:
- If there is no valid
Instanceheader, HTTP 400 with anErrorMessageResponseis returned. - If the active IAuthenticationContext has no instance data loaded (indicating the user is not authorized to access said instance), HTTP 403 is returned.
- If the instance is offline, HTTP 409 with an
ErrorMessageResponseis returned.
- If there is no valid
- If the request takes an API model as a parameters and the model included in the request body encountered validation errors, HTTP 400 with an
ErrorMessageResponseis returned.
- If the
- The request at this point, is considered authorized. Remaining behaviour is left up to each individual route to implement.