As a user who has fallen prey to badly implemented bootloaders with little to no fail safe's , I can say its not a fun rabbit hole to go down.
Most implement a bootloader in it's own right with app being compiled ass separate entity ,
here is a example
https://github.com/darkspr1te/stm32f407_vet_bootloader/blob/master/platformio.iniit for stm32f407 but the main code principle is the same for most stm, it looks for a bin file on sd-card and writes it to flash @0x4000 and then resets into it, (that just mean i load the vector table into ram and the reset into the new vector table)
or this one which has more complicated setup,
https://github.com/darkspr1te/mkstft35_bl_vet_fsmc this one runs a small crc check as it reads the data , if crc fails it wont write, if it passes it's decrypted then written (it's open sources version of the nasty and stupid encrypted MKS robin bootloader )
I have many more code examples of bootloaders and apps in platformio / stm32cube base if needed, you will note most have a encrypt/decrypt option as i've found the need to hack quite a few badly done bootloaders in order to repair the device which has since bricked and more care was put into encrypting the IP than ensuring correct updates.
You will also find it hard to do the A/B flash space on many stm's you look at, even though I have implemented such myself on stm's and others I have found it works best on spi flash based devices offering more /swappable memory space.
While most of my loaders work with sd-card/usb flash/uart over usb there are much more modern ones like the new st example which make the device appear as a flash card and you just drag and drop a bin file(a check is implemented that the first few bytes point into a proper vector table or it wont write) , or the older DFU version (buggy as heck even in silicon based DFU)
The main thing i have learned in all this re-writing bootloaders is it's key to allow bootloader activation even if firmware corrupts, one device i have always goes to bootloader if powered via USB yet it was not implemented ,it was there in the code but it was skipped by a prior instruction , bug or left test code i dont know but once i NOP'ed out that it allowed me to write correct firmware and it lived again.
another lesson is it's key to have a decent check method in place, for MKS they simply used RSA128 code to act as a form of crc but they made a mistake in the code in that it erased first, then checked the incoming packets for correct rsa sequence decrypt and if they passed were written, if failed it would error out but has already started the erase procedure rendering the device bricked.
How you provide the firmware to the bootloader can also effect your implementation, a example would be if delivered via sd-card I have in the past used headers and footers added to the binary file that include crc data as as the complete file is available to the mcu then a crc check can be run on the entire file prior to writing, however if you sending via another method then you wont have the memory space normally to receive the entire file and then run a crc on it, this is where some implement transfer systems like zmodem/xmodem which have inbuilt packet based crc's and were common in modem days and are popular among some as the code is small and is the same for host/client making deployment a little easier.
That leads me onto the last item, the app that delivers the update service, also important. Again i have run foul of many badly done updaters that either fail due to age/windows support, driver support, dodgy chinese dll's requiring admin privilege's to just write to serial port ? no valid file checking , no valid servers anymore my fav android issue no BT device seen !. I have to maintain a windows 7 VM just for a few devices with PL2303 chips because windows has banned the driver and my OBD device has chinese spyware clear as day in it but it's the only way to clear out the saved reports, almost like they did that on purpose.
so if you can eliminate the update APP requirement you will remove one large headache.
darkspr1te