[api] feat: migrate to Clerk authentication#61
Merged
Conversation
added 30 commits
July 30, 2025 17:32
added 28 commits
August 23, 2025 18:05
…rios when starting a new Spring application context
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
The Spring Boot API now acts as an OAuth2 Resource Server, with Clerk as the external Identity Provider (IdP).
Any HTTP request to a protected endpoint must include a valid JWT in the
Authorization: Bearer <token>header.The Onboarding Flow
Before API access is granted, users must call
POST /api/v1/clerk/onboardingwith a valid JWT issued by Clerk.This action is required as Clerk does not provide a synchronous method of interacting directly with the user table. Sending this request guarantees that every Clerk user has a corresponding
UserandProfilein the local application, avoiding database sync issues.If the onboarding process is not completed, the client router will block access to the application, and any authenticated request to a protected API endpoint will result in
403 Forbidden, instructing the user to first complete the onboarding process.The flow itself is outlined below.
User signs up via Clerk
onboarded: falseecho_id: nullRedirect to Onboarding
onboarded: truein their active session's claims to the/onboardingroute.Onboarding with the API
POST /api/v1/clerk/onboarding.Userexists for the given Clerk user ID, found in thesubclaim on the authenticated session token.external_idwith the local UUID, and setspublic_metadata.onboarded = true.Session Refresh
onboardedandecho_id.onboarded: trueecho_id: <UUID>Access Granted
onboardedclaim is updated, the client router will redirect the user to/home, granting access.Userin db.Webhooks
user.updateduser.deleted. Any updates to the Clerk user, such asusernameandprofile_image_urlwill be reflected in the local db too.Changelog
GH Workflows
CLERK_ISSUER_URICLERK_JWK_SET_URICLERK_SECRET_KEYDocker Compose
redisEnvironment
application*.ymlfiles:application-{profile}.ymlfile now refers to its own.env.{profile}config file for environment variables..env.examplefile with templated instructions for configuring the required environment variables for each local environment.Dependencies
spring-boot-starter-data-redisspring-boot-starter-oauth2-resource-serverclerk:backend-api:3.1.0svix:1.9.0spring-boot-starter-webfluxDB Schema
accounttable touser.usertable schema:usernamecolumn - usernames are now handled within profiles.encrypted_passwordcolumn - auth handled by the IdP.enabledcolumn withstatuscolumn.external_idcolumn for linking external users provided by the IdP.profiletable schema:fk_usernamefk_avatar_idfk_banner_id.avatar_idandbanner_idcolumns withimage_urlcolumn.usernameandimage_urlare now synchronised to Clerk user directly.userandprofiletables.Config
RegexConfigconfiguration class entirely.SessionConfigconfiguration class entirely.ConstantsConfigsubclasses:Accountsubclass entirely.ApiConfigsubclasses:Authsubclass entirely.Accountsubclass entirely.Clerksubclass with static endpoint constants:ONBOARDINGconstant.WEBHOOKconstant.Devsubclass with static endpoint constants.PERSIST_ALLconstant.SYNC_ALLconstant.ClerkConfigconfiguration class.Clerkbean for SDK interaction.Webhookbean for webhook validation.propertiessubpackage for environment variable validation on application startup:AuthenticationPropertiesclass to validate OAuth-related secrets are included.ClerkPropertiesclass to validate Clerk-related secrets are included.CloudinaryPropertiesclass to validate Cloudinary-related secrets are included.DataSourcePropertiesclass to validate data source secrets are included.Security
AuthenticationConfig- the application now depends on Clerk as an external IdP.ClerkOnboardingFilterto analyse the token claims of an authenticated token, rejecting access with403 Forbiddenif the required claims are:AccessDeniedHandlerandAuthenticationEntryPointto ensure authentication-related exceptions are correctly handled byGlobalControllerAdvice.SecurityConfigSecurityFilterChain:formLoginwithoauth2ResourceServer.sessionManagement(stateless session creation policy).api/v1/dev/**andapi/v1/clerk/webhookas public request endpoints.ClerkOnboardingFilterto the filter chain usingaddFilterBeforeAuthorizationFilterControllers
AuthControllerand all associated endpoints.AccountControllerand all associated endpoints.ClerkControllerPOST /api/v1/clerk/onboarding: Completes onboarding for authenticated user.POST /api/v1/clerk/webhook: Handles Clerk webhook events.DevControllerPOST /api/v1/dev/clerk/persist-all: Persists all Clerk users to the local database.DELETE /api/v1/dev/clerk/sync-all: Updates the onboarding status of all Clerk users to false.NOTE:
DevControlleris only available under thedevprofile.Services
AuthServiceentirely.AccountServicetoUserService:upsertFromExternalSourcedeleteFromExternalSourcefor handling synchronisation between the external IdP and the local application.clerkpackage:ClerkSyncServiceto handle user synchronisation between Clerk and the local application.ClerkWebhookServiceto handle supported Clerk webhook events.ClerkSdkServiceto handle direct interactions with Clerk's backend API.DevService:persistAllUsersmethod for synchronising all development mode Clerk users to the local db.unsyncAllUsersmethod for removing the onboarded status from all development mode Clerk users for cases when the local db is dropped.SessionService:authenticateandreauthenticatemethods.getAuthenticatedUserIdgetAuthenticatedUserClerkIdisAuthenticatedUserOnboardedmethods which fetch the relevant claims from the authenticatedJwt, acting as a wrapper for Spring'sSecurityContextHolder.NOTE:
DevServiceis only available under thedevortestprofile.Repositories
AccountRepositorytoUserRepository.UserRepositorymethods:findByUsernameexistsByUsernamefindByExternalIdexistsByExternalIddeleteByExternalIdEntities
Accountentity toUserentity, and updated its fields to reflect changes made to theusertable.UserStatusenum, with single constantACTIVE. This will be built upon in the future.Profileentity fields to reflect changes made to theprofiletable.Profilestatic factory methodsforUserandforTest.Exceptions
GlobalControllerAdviceexception handlers for:InsufficientAuthenticationException- cases where no authentication is provided with the request.InvalidBearerTokenException- cases when the supplied bearer token is invalid for whatever reason.AccessDeniedException- cases when authorization has failed, such as when the user is rejected byClerkOnboardingFilterafter authentication has taken place.Exceptionclasses:UnauthorisedRequestException- unauthorised requests are instead rejected with more specific exceptions thrown by Spring Security's OAuth Resource Server defaults.IncorrectCurrentPasswordException- authentication now handled by an IdP.UsernameAlreadyExistsException- authentication now handled by an IdP.DeserializationExceptionfor cases when there was an issue deserializing an object with a custom implementation of a deserializer.ClerkExceptionfor cases when there was an issue interacting with the Clerk SDK.WebhookVerificationExceptionfor cases when there was an issue verifying the legitimacy of a received webhook via its signature.DTOs
accountrequest-related DTOs entirely.authrequest-related DTOs entirely.ClerkWebhookEventrecord:ClerkWebhookEventTypethat declares the supported event types.ClerkWebhookEventDatawith implementationsUserUpsertandUserDelete.PageDTOresponse object to use native JavaURItype forpreviousandnextfields, and updated thePageMapperto handle native URI construction using builders.ProfileDTOresponse object by removingbanner_urlfield entirely, and replacing theavatar_urlfield with a simpleimage_urlfield, as reflected in the changes to theprofileschema.SimplifiedProfileDTOresponse object by replacing theavatar_urlfield with a simpleimage_urlfield, as reflected in the changes to theprofileschema.adapterfor internal-use transfer objects:ClerkUserDTOadapter with aClerkUserMapperfor mapping Clerk SDK user responses to this smaller, easier to consume internal object.Misc
AccountLoaderand related user JSON data fromresources/data/users.json.Utilsclass with utility methodscheckNotNullconvertHeaders.OffsetLimitRequeststatic factory methodoffor easier usage.UsernameandPasswordannotation validators.Testing
Unit tests:
@WebMvcTestcontroller tests to useMockMvcTesterwithassertJassertThat.OffsetLimitRequest.PageMapper.ClerkWebhookEventTypestatic methodfromString.ClerkWebhookEventDeserializer.ClerkUserMapper.ClerkOnboardingFilter.ClerkSyncService.ClerkWebhoookService.SessionService.UserService.ClerkController.Integration tests:
IntegrationTestclass to connect to CI/CD Clerk instance via the backend SDK, creating and deleting Clerk users as part of the integration test lifecycle.ClerkTestUtilsutility class to interface interactions with the Clerk SDK during integration tests.@SpringBootTestcontroller tests to useWebTestClientwith native API assertions, replacing the oldTestRestTemplate.DatabaseCleaneras simple method of wiping relevant tables between integration tests, preventing dirty databases from affecting chained tests within the same context.resources/sql/post-interaction-cleanup.sqlresources/sql/profile-interaction-cleanup.sql.AccountRepositoryITclass entirely.UserServiceto ensure the IdP upsert logic.ClerkOnboardingFilterto ensure the filters logic with real tokens.ClerkController.