If the attacker can see the source code of the page, they can see the key if it is transmitted over the wire, and your only reasonable option is for the user to be responsible for entering a 256-bit or greater key.
If the attacker can see the live browser DOM, then there is nothing you can do.
I'll assume the first, which is awful from a usability perspective.
Once:
1. Generate a cryptographically random 256-bit key. Somehow share this securely with the user, out of band.
To encrypt:
1. Generate 128 cryptographically random bits on the server, and send them to the client
1. this will be the IV
2. Encrypt the data with AES-128-CBC using
1. the *first* 128 bits of the shared key as the key
2. the received IV
3. the plaintext
3. Calculate the HMAC-SHA-256 using
1. the *last* 128 bits of the shared key as the key
2. the received IV prepended to the ciphertext as the data
4. Transmit the ciphertext, the HMAC, and the IV to the server.
To decrypt:
1. Calculate the HMAC-SHA-256 using
1. the *last* 128 bits of the shared key as the key
2. the IV prepended to the ciphertext as the data
2. Compare the calculated HMAC with the received HMAC by
1. XOR the byte representations of the HMACs
2. AND all of the bits of the previous result
3. If the result is 1, abort
4. If the result is 0, continue
3. Decrypt the data using AES-128-CBC using
1. the *first* 128 bits of the shared key as the key
2. the received IV
3. the received ciphertext
Notes:
* good luck finding a Javascript cryptography library
* JavaScript currently has no access to a CSPRNG, so we generate IVs server-side
* if decryption fails, you *must not* indicate so in any way, e.g.:
* do not return failure
* do not perform fewer operations and thus return a response more quickly