Building first app - need architecture/design help

Regarding the first question:

You are correct that if the access token is meant for your own backend, it shouldn’t be an opaque string but a JWT, at least this is the format that Auth0 uses for such. The tokens are JWT and therefore self-contained.
In order to get the access token in JWT, and not this opaque string, is to first register your backend as API in Auth0, under Dashboard > APIs. You would provide an API identifier in the form of a URL (this doesn’t have to be publicly available, it’s just an identifier. Let’s say you name it https://example-api/

Then, when you make the authorization request within your iOS app, make sure you add the audience parameter to the request, like:

audience: https://example-api/

(you add it as parameter at the same place in your code where you also use the scope, clientID parameter, etc.)

You would then also define permissions/scopes for the API in the Auth0 Dashboard, which you can then request from your iOS app. This could be something like a scope add:friend, so when making the request to get the access token, you would extend the scope parameter like this for example:

scope: openid profile email add:friend

This way the access token you’d be getting back would then be valid to allow a user to add a friend via your backend API.

Note that both the ID Token and the Access Token contain a claim named sub (short for subject), which is actually already the unique user identifier (the user_idin a user profile in Auth0). Since you have it in both ID and access token, you could actually already use it. No need to additionally call the /userinfo endpoint necessarily here. And this is also the reference that you could use on your end to refer to users (as the foreign key in your DB tables so to speak).

This links might be helpful in general on how to protect your own backend/API:

https://auth0.com/docs/microsites/protect-api/protect-api

Regarding the second issue:

Storing the user_id as you do is fine, and makes sense. This is also what I would do, and using the user_id as the reference.

I feel like this approach is wrong as well as I have to call the management API every so often. I eventually get the Too Many requests. Is that from calling the API to validate my access token or is it from the way i’ve implemented my friend list?

You’re most likely hitting the rate limits here (it’s not about the access token validation).

I then call one of the management APIs and pass in each of the userIDs to get an email. I feel like this approach is wrong as well as I have to call the management API every so often.

Do you call the management API one by one for each user whose email address you want to fetch? Or are you calling the entire user list and caching the list for a while on your end? (Would this be an option to reduce the number of calls maybe?)