Hello, @rthill - welcome to the Auth0 Community!
Yes, this is expected, and is because of the reasoning of existence of each flow.
sub claim in the JWT token lists the subject for which the token was issued.
I assume that, for your SPA, you are logging a user in using, probably, Authorization Code + PKCE. When you do this, the user is authenticating on behalf of itself, and hence, the
sub (subject) of this token is the user itself. That is why you get the Auth0 user ID.
For machine to machine authentications, a machine is requesting access to another machine on behalf of itself. This means that the application itself is the one that’s requesting the token - not a user. Which is why the
sub (subject) claim of the token is the application itself.
Now, if you want to perform an authentication on behalf of the user, you should be using the resource-owner password grant (ROPG). This allows a secure server to call directly to the authorization server, with the username/email + password for the user you are trying to log in. This flow should be reserved for secure environments, as it directly handles the password for the user. However, in this scenario you are authenticating on behalf of the user, so the
sub (subject) of the token would be the user itself.
Hope this clarifies your situation.