Generally you would use encrypt-then-MAC before encoding. There is no need to process the expanded ciphertext before encoding, and you may be able to send the
message both in binary and ASCII-armored with base 64.
Note that you would have to base 64 encode the authentication tag (the HMAC output) anyway if you would choose encrypt-then-encode-then-MAC, it would be encrypt-then-encode-then-MAC-then-encode.
Choosing the encrypt-then-MAC-then-encode does require you to:
- Establish where the tag is situated:
Generally this is performed by just appending the tag and then count 32 bytes backwards (assuming the entire 256 bit output is used for the MAC, the default) or to include a length encoding for the ciphertext. However, a full ASN.1 structure could also be defined to create a so called container format.
- Make sure that your base 64 decoder is not vulnerable against attack:
Because the base 64 decoding will now happen before the message is authenticated (but this is also the case if the tag is base 64 encoded separately, so yeah, make sure your base 64 codec is secure).
If you decide on using a container format (CMS or your own container format) then please note that attacking the format may expose security issues before the authentication can be verified. Windows famously had such vulnerabilities before user authentication took place. But many crypto related libraries such as OpenSSL and NSS have suffered similar issues.
EDIT: On Jason
You've specified JSON as message format. That's fine, but please note that JSON doesn't have an explicit way to encode binary values. Therefore you would have yourself to define the exact encoding of the binary message in
payload, e.g. base 64 as defined here. Otherwise you may run into interop issues (base64url, padding or no padding, line breaks or no line breaks at specific locations, etc.
Furthermore, HMAC takes binary data as input. So that would mean that you'd have to specify the character encoding as well, at least for calculation of the authentication tag. JSON defaults to UTF-8 (which is compatible with ASCII for base 64 encoded values) but it also allows UTF-16 and UTF-32. Note that the encoding of the JSON and the encoding for the HMAC input could differ.
Because of these kind of issues, it is infinitely easier to just calculate the value over the binary value. You don't want to end up with the XML-digsig standard. Of course you'd still have to define the character set for the JSON message and base 64 encoding for the values within it, but at least a single bit wrong won't break your implementation.