Scopes

OA4MP supports the scope parameter for servers. Unfortunately, scope has been completely overused and has acquired several meanings. To be completely clear, scopes are requests that ask for certain claims to be asserted in various tokens.

  • requests for user meta data (e.g. email, profile, etc. These are returned in the ID token
  • requests for permissions to access resources, e.g. storage.read:/public/physics/muon and are returned in the access token.

Remember that, unhelpfully, if you send in a scope request, the resulting claim is asserted using the scope tag again. This completely confuses many people since the request and response look the same. It's the specification though, so we get to live with it. A fuller discussion about scopes generally to be found here. This blurb is concerned with configuring a server or client to use scopes.

The id token is a JWT (JSON Web Token) which has a header (signing information), payload (a base 64 encoded JSON object), and signature, plus a system for verifying the signature. Historically, since this could be trusted by checking with the server if it had been altered, people started using it as a type of access token. That's both a good and bad idea, since it is not really designed for granting access to things, but, yes, it can be verified. At this point, all the tokens an OAuth 2 server could create (authorization, access, refresh) started to mutate into JWTs. This is a good thing. OA4MP lets you do this, it also will issue old style opaque tokens.

A note about native tokens in OA4MP. These are unforgeable, self-describing and unique (unlike many OAuth servers where they are indeed just random strings). Here is an example of one:

    https://cilogon.org/oauth2/6c41b109f62d6776976bc23816e97f5a?type=refreshToken&ts=1683061597117&version=v2.0&lifetime=500000000

If you do not want/need JWTs for your service, you will get these. If you do get JWTs, these native tokens will be used as the jti unique identifier.

This sure is messy you think. Is there another way to request scopes? Sort of. Another attempts was to create essentially an entire request language per claim in the claim parameter, but that turned into such a mess and most servers don't support that. OA4MP most certainly does not mostly since the requests are even hard to write. I am relating this history because people who are new to this often have a devil of a time figuring out why the scope parameter does what it does.

The basic supported scopes in OA4MP are

  • openid - (optional) treat the request as OIDC, meaning an id token is created and at the least will contain the sub claim for the user.
  • email - returns the user's email address, if available.
  • profile - returns information about the user's profile
  • org.oa4mp:userinfo - similar to org.cilgon.userinfo. The difference is that if the client is set to use restricted scopes, only a restricted subset of scopes is returned. If the client is not so restricted, this is equivalent to org.cilogon.userinfo.
  • org.cilogon.userinfo - returns all enabled information about the user from the service and allows access to the user information endpoint.
  • edu.uiuc.ncsa.myproxy.getcert - (deprecated!) the server returns a certificate from the getCert endpoint. If the client omits this, then attempts to get a certificate will be rejected. However, requests to the user info endpoint will still be processed.
  • offline_access - (optional) some clients send this when requesting a refresh token. If it is included, the prompt parameter must be set to consent (this is just the spec.) Note that OA4MP clients are simply configured to issue refresh tokens or not, so this parameter is ignored. It cannot, for instance, be used to force the server to issue refresh tokens.

Nota Bene: Clients on the server (as opposed to free standing clients) by default are configured with the strict_scopes set to true. What that means is that any scope not in its configured scope list will raise an error. This is done since clients that typically need a handful of specific scopes should be alerted if an unknown scope is requested. The spec says that unknown scopes may be ignored and that is common enough for most OAuth 2 servers, but our experience is that the vast majority of times there is a typo in the scope (so requesting emaile not email) and it is far better that the request fail initially so it can be fixed rather than have a mystery that the email claim is not asserted.

If you need a lot of flexibility in setting claims (such as for SciTokens or WLCG token requests that are asserted in the access token), set the strict_scopes to false. You can pass in anything at that point and the more standard OAuth 2 behvaior of just ignoring anything unknown will be in effect.

Setting a handler (server configuration only!)

There is a handler attribute in the scopes tag. This allows you to specify a claim source implementation that will be invoked automatically for every phase, adding the resulting claims to the set a class that has a no-argument. While there are claim sources that are included in the standard OA4MP distribution, (such as for an LDAP claim source), the aim is that you can write your own and have it run. Generally this should not be used, except as a very specific custom extension to OA4MP. In particular, this contract has no way to pass along any configuration to the handler.

If you want a claim source to be run automatically, the easiest way is in a QDL script to add it (with the ~ operator) to the claim_sources. So a typical use in the pre or post_auth phase in your QDL script would be:

              cfg. := claims#new_template('file');
   cfg.'claim_key' := 'eppn'; // configures it as per here
    claim_sources. := claim_sources. ~ [claims#create_source(cfg.)]; // automate getting claims by the system

This creates a file claim source and adds it to the list of claim sources the server runs. You need to add this once and it will be managed ever thereafter. The downsides to this approach are that you no longer have control over it and it may be invoked a great deal more that you would want or need. Generally, adding to the list of claim sources is discouraged. It is simply better the get the claims you want and add them where you want them, e.g. from the NCSA client script:

    cfg. := claims#new_template('ncsa');
    cfg. := claims#create_source(cfg.);
 claims. := claims. ~ claims#get_claims(cfg., claims.'uid');

Which creates a claim source, gets the claim claims and adds them to the existing claims.

Setting scopes in the configuration

The configuration allows you to set statically which scopes are requested as follows. This is in both the client configuration, where it is used to construct the request with the given scopes, and the server configuration, where it sets a fixed list of scopes that will be strictly honored (by default). The top-level tag is the scopes tag and that in turn contains scope tags. These scope tags contain a single scope and supports a single attribute:

Name Required Default Description
enabled N true Enable or disable this scope.

A client example

<config>
   <client name="my-cfg">
    <scopes>
        <scope>custom.scope</scope>
    </scopes>
        <!-- other stuff.. -->
    <client>
</config>

The client will include custom.scope in requests to the server in addition to the standard scopes. The default OA4MP client behavior is to request all the standard scopes (exception offline_access). Additions to the scopes element are therefore additive. To disallow a scope, disable it:

    <config>
       <client name="my-cfg">
        <scopes>
            <scope enabled="false">edu.uiuc.ncsa.myproxy.getcert</scope>
        </scopes>
            <!-- other stuff.. -->
        <client>
    </config>

This would omit the getcert scope, but request all the other scopes.

A server example

<config>
   <service name="my-server">
    <scopes>
    <scope enabled="false">edu.uiuc.ncsa.myproxy.getcert</scope>
    <scope>my.custom.scope</scope>
    </scopes>
        <!-- other stuff.. -->
    </service>
</config>

In this case, in the registration page, the custom scope of my.custom.scope would be presented as an option, but the getcert scope would not. These scopes would then be added to the client's configuration so that requests with my.custom.scope would be accepted. Of course, some form of processing to do somethign with that scope is needed, e.g. QDL scripting.

Final caveat for OA4MP clients

The standard OA4MP client sends along only the scope parameter in the initial request. You may also send along scopes in the token, refresh and exchange phases but you need to customize to do that.