Skip to content

Authentication

Kwandes edited this page Nov 11, 2021 · 2 revisions

Treecreate uses Spring Security to manage authentication of API requests. We have decided to avoid using third party providers like Okta and Auth0 in order to decrease complexity and not compromise on needed features.

The authentication is based on an access token in a form of a JSON Web Token (JWT). The API creates said token during the login flow, and the client can use it to authenticate future requests.

Frontend

The frontend applications use local storage to save the access token along with other authentication-related data like basic user information. While the local storage can be modified by the client, any confidential data is fetched from the API and undergoes authorization, thus eliminating the risk of bypassing the authentication system by modifying data on the client.

API requests

Each request to the API includes a bearer header which contains the access token. The header is set in the AuthInterceptor. The token is used to authorize the requests, and in case it fails the API will deny access to the given resource.

Limiting page access

The routing modules utilise the AuthGuard to verify that the given user has the necessary role to access the given route.

AuthUser object

When user logs in via the API, a AuthUser object is returned. The object includes the access token, as well as some basic user information like the email and roles.

API

Config

Spring security is configured in the WebSecruityConfig. Some of the properties are obtained from the application.properties file.

The config sets up the various Beans needed to configure and run the authentication system, as well as a list of rules regrading the endpoint authorization and handling of requests.

Making an endpoint public

By default, every endpoint is private and requires authentication in order to be accessed. If the user is not authenticated they will get UnAuthorized error. Once authenticated, the request still needs to have access to the specific roles the given endpoint requires. Those role requirements are defined on a per-endpoint basis

If you wish to make an endpoint open to the public (for example, login), it needs to be set in the WebSecruityConfig as one of the matched paths.

If you want to make an endpoint /foo open, you should add it to the list after .authorizeRequests() method. It would look something like this:

.authorizeRequests()
            .antMatchers("/other-endpoints").permitAll()
            .antMatchers("/foo").permitAll()
            .anyRequest().authenticated();

User Roles

The system currently has 3 roles defined: USER, DEVELOPER, and ADMIN. This is likely to change in the future.

The roles are used to authorize specific endpoints. While being authenticated is enough to access the endpoint by default, we want to have a more fine-grained control over the authorization. Because of this, each private endpoint has the following rule above them

    @PreAuthorize("hasRole('USER') or hasRole('DEVELOPER') or hasRole('ADMIN')")

The roles can be chained in any order. If the user does not have any of the roles, they will be denied access.

Example: endpoint that only allows users with DEVElOPER or ADMIN role

    @GetMapping()
    @Operation(summary = "Get all users")
    @ApiResponses(value = {
        @ApiResponse(code = 200, message = "A list of users",
            response = GetUsersResponse.class)})
    @PreAuthorize("hasRole('DEVELOPER') or hasRole('ADMIN')")
    public List<User> getUsers()
    {
        return userRepository.findAll();
    }

The roles require some manual updating in order to change. If you wish to add new ones or edit an existing role, each instance of it has to be updated. Existing database entries will have to be adjusted, most likely manually. The Role Enum has to be adjusted as well. The signup logic also has the values hardcoded, and will require manual modification

Endpoints

Currently the system has two endpoints used for authentication: signup and log in Their details can be viewed via Swagger UI

The auth/test endpoints are used purely for testing and don't actually have any purpose beyond that

Detailed explanation

The current implementation has been inspired and partially copied from Bezkoder's guide on JWT authentication. The details of the how the JWT is managed etc are explained there already,a dn thus this doc won't go into the details of that

One major difference from Bezkoder's solution is that we don't use the username as its own field. Instead, email and username are duplicates of each other, and the user logs in with an email

I have spent as much time as we've valuable trying to remove the username from spring security, but it turned out to be too complex to be worth the time. Thus, we have a bit less optimal approach of keeping the username and duplicating it into the email

Clone this wiki locally