TL;DR: You rely on Authorization Service for issuing a token with correct scope(s). In "paranoid mode", the Resource Owner can introspect the token for granted scopes and allow or deny access on that basis.
There are two entities involved in this process: Authorization Service (AS) and Resource Owner (RO). AS issues access tokens to the client after successfully authenticating the resource owner and obtaining authorization. RO grants access to a protected resource based on the access token presented by the client. Even if the AS and RO might be physically under "one roof" (same server, same app, same process, ...), they have different responsibilities.
Management of scopes and making sure the client doesn't get a token with a "wider" scope than allowed is the responsibility of AS. Resource Owner should not be involved in validating the scope granted by AS. From oAuth spec:
The authorization and token endpoints allow the client to specify the
scope of the access request using the "scope" request parameter. In
turn, the authorization server uses the "scope" response parameter to
inform the client of the scope of the access token issued.
The authorization server MAY fully or partially ignore the scope
requested by the client, based on the authorization server policy or
the resource owner's instructions. If the issued access token scope
is different from the one requested by the client, the authorization
server MUST include the "scope" response parameter to inform the
client of the actual scope granted.
On paper, this is the answer to your question. You rely on AS to do its job.
In a less than perfect world with bad actors, Things Happentm
If the client asks for access with a wide scope and the AS policy allows granting that scope, the client has to be authorized for this grant to succeed. The step of doing so involves authentication of said client. How this happens depends on the chosen oAuth flow (grant type) and the implementation specifics of AS, RO and the client.
The various grant types and associated implementation nuances of oAuth 2.0 resulted in a wide attack surface. A number of Best Current Practices (BCPs) have been issued over the years to highlight some of these issues. oAuth 2.1 (currently in draft) is attempting to rectify this by removing all grant types other than authorization and removing a number of other loopholes.
If the AS sticks with authorization grant type as the only allowed grant and the AS is properly implemented, a user will have to explicitly provide their consent to granting access with requested scopes. Still, it is certainly possible for a client to misrepresent this in UX terms, especially in situations where AS doesn't strongly enforce the presentation of consent and AS/client boundary. Moreover, an attacker could redirect the client to their own AS and play from there. Finally if the access token gets issued with a wide scope despite AS policy prohibiting this, what can the Resource Owner do?
The RO has limited recourse. It can introspect the access token and find out the scope(s) associated with the token. This step requires AS to support the introspection service and the caller (RO in this case) must be authorized. This is in addition to the access token being introspected:
the endpoint MUST also require some form of authorization to access
this endpoint, such as client authentication as described in OAuth 2.0
[RFC6749] or a separate OAuth 2.0 access token such as the bearer
token described in OAuth 2.0 Bearer Token Usage [RFC6750]. The
methods of managing and validating these authentication credentials
are out of scope of this specification.
If you can jump through all of these hoops, then you're almost there. All you have left to do is create a policy-like construct in your RO that will somehow know when a particular client wielding an access token is exceeding their "ask" (requested scopes) based on the introspected (actual) token scopes. Note: you can't know the requested scopes so it'll have to be a blind-ish "deny if scopes = ... when client is blah" type of policy.