Is it good practice to only delete through a trigger to enforce 'deleted by' auditing?
I am not sure that using an IsDeleted column would create unnecessary headache.   Locking down the table, and allowing non-administrative action via a view which filters on that column, while not displaying it, isn't very difficult.  Add an Instead Of Delete trigger to the view, and you're all set.

Given that it's essentially boiler plate once you have the initial pattern, you could very easily write a procedure locks down the table and (re)generates an associated view, adding the filtering and trigger when it finds it has IsDeleted.  It could add user logging requirements if there was a deleted_by_user_id field as well.  I'd recommend parameter that allow rebuilding the world, or a pattern that is matched against table names (and/or schema).

In addition, versus forcing a deleted_by_user_id to be directly populated, I would look at leveraging SESSION_CONTEXT.  The trigger would check against an agreed upon session context variable (e.g. 'AppUser'), throw if not set, then use that for populating deleted_by_user_id from that.  

On the application side, you should be able to create sub-class SqlConnection and override Open.  After calling the base Open, you should be able to run a EXEC sp_set_session_context N'AppUser', '{username}';, there by setting the App user for the session.    

The advantage of using IsDeleted is that you don't have to write a whole row of data when doing a delete.  If you do bulk deletes, or a lot of deletes in general, this could be advantageous.  Though I would couple with Partial Index, cover just undeleted records. The advantage removing and writing to another table is that after reorganization you should have much more contiguous data, which in many cases generally makes things faster.