The diagram from the IETF spec is illustrative on this topic.
The user’s browser (User Agent) acts as a proxy of sorts for getting the Authorization Code to the Client (app/dev server). The redirect happens in the user’s browser and the Authorization Code is passed by the users browser to the Client (app). Then in the next step, the Client (app) makes a call to the Authorization Server (SmartThings) to get the authorization token and the Authorization Server responds with the token.
This means the developer’s server doesn’t need to be externally accessible by the Authorization Server (SmartThings), it just needs to be able to make outbound calls to the Authorization Server.
This means that the Client (app) can sit behind a firewall and only be internally accessible while still being able to make outbound calls to the SmartThings to control your things and get status updates.
In step D/E of the diagram above, the Client app makes a request to get the Authorization Token using the Authorization Code and SmartThings responds (synchronously) with the Authorization Token.
The IETF spec is quite clear on this and expects the Authorization Server to include the Authorization Token in it’s response to the request from Step D, but you’ll find that some diagrams and even some implementations actually send the Authorization Token directly to the redirect_uri rather than responding directly to the Client request.
The spec states that if a redirect_uri is used in step A, then the redirect_uri must be included in step D and must match. It’s a safeguard of sorts.