OAuth 2.0 Client Application Developer Guide
Some Implementation Notes
A Note about Access Tokens
The access tokens provided by Projector are similar to Bearer Tokens, but are not exactly Bearer Tokens. They are Projector Session Tickets, the same kind of token returned by the Soap Service PwsAuthenticate
. When you are granted an access token (AKA Session Ticket) via the OAuth 2 flow, you will also get the information about both the soap endpoint authority and the rest endpoint authority at which that token is valid. You will also be provided the scopes applied to the access token, a refresh token, and expiration information about the access token. Projector Session Tickets may be used to invoke any of the Soap Services provided by Projector or any of the Restful Reporting Services implemented by Projector, as long as the authenticated user has the permissions to use the requested services and the client app requested any necessary scopes, which map to Projector Permissions.
A Note about PKCE (Code-Challenge)
Projector’s OAuth Server does not require the use of PKCE, but it is STRONGLY recommended and all client apps are further recommended to set the code_challenge_method
to S256
if the client environment in use supports it.
Registering an OAuth Client App in Projector
To register your OAuth App, or to edit the registration parameters of the OAuth client app, you will need to use the Management Portal and go to the OAuth panel on the Integration Tab. There you can create, update and delete registrations for your app (as well as manage enablement for globally available OAuth client apps, and manage user connections to the apps). This will give you the opportunity to establish or update the name, description, redirect_url and optional logo for your client app, and to retrieve the client_id and client_secret.
Obtaining a New OAuth Token from Projector
The authorization endpoint in Projector includes your account code. If (in this example) your account code is “acme-industries”, your authorization endpoint for your app would be:
https://app.projectorpsa.com/oauth2authorize/acme-industries
Only users in your account will be allowed to authenticate. When the user is redirected to the authentication page, their account code will be locked to the account code you specified in the URL.
To begin the OAuth flow to obtain a new token, the client app points an interactive browser tab to one of the endpoints above, and then appends a query string including the following URL-Encoded parameters, which adhere to the OAuth V2 standard:
- response_type (Required as per OAuth standard. Projector supports only the response_type of “code”.)
- client_id (Required. Must match the client ID in your app registration).
- redirect_uri (Required. Must match the redirect URI in your app registration. Also must be a valid URI to which the browser tab can be redirected with the authorization code).
- state (Optional. A URL-safe string that will be returned verbatim to the redirect url with the code.)
- scope (Optional. If no scope is provided, the token that is subsequently acquired will have no elevated Projector Permissions. Scopes and Projector Permissions are further discussed in their own section.)
- code_challenge (Highly recommended but not required. Must be present if code_challenge_method is specified).
- code_challenge_method (If specified, must be either “plain” or “S256”. If framework supports it, “S256” is highly recommended).
So a full URL that the browser is pointed to to begin the OAuth flow might be:
This URL, if it is valid, will redirect to an authorization page in which the user will be asked for Projector Credentials. If those credentials are entered correctly, the browser will be redirected to yet another page in which the user will be asked to accept the OAuth connection, including some verbiage about scopes if requested. If the user does accept the connection, the browser will be redirected to the registered (and provided) redirect uri with a short-lived authorization code and the provided state
.
When the redirect URI is hit, the underlying program logic should issue an HTTP POST request to the token endpoint, which is:
https://app.projctorpsa.com/oauth2token
Form-URL-encoded parameters on this POST request should be:
- grant_type=code
- code={The short-lived authorization code you got back from the authorize redirect}
- client_id={The client ID for the registered OAuth client app, same id sent in authorize request}
- client_secret={The client secret for the registered OAuth client app}
- redirect_uri={Same one you sent on the OAuth request, which must also be the registered one}
- code_verifier={If you used PKCE as recommended, code verifier, otherwise do not include}
Assuming all goes well, your response will be a block of JSON something like this:
{ "access_token": "BpL+vLckFcvBby0aVEYKlQ==", "token_type": "projector_session_ticket", "expires_in": 604800, "refresh_token": "E2BgYNB04XXVZRbkKDI6LlB0WVeOt6BoPys1uSS_SNff3yXqq5YTR8ZNDwA", "scope": "enterTime", "soap_service_authority": "https://secure2.projectorpsa.com", "rest_service_authority": "https://app2.projectorpsa.com", "messages": { "warnings": [ "Warning Message Number One", "Warning Message Number Two" ], "info": [ "Info Message Number One", "Info Message Number Two" ] } }
Please note it is pretty unlikely that you will get any warning or info messages. These are included for completeness in the context of how our architecture works. If you do get any of these messages, you may want to consider logging them or sharing them to the end-user or both.
The access token is, under the hood, a Projector Session Ticket. If you are using it for Projector Soap Services, you should target the base endpoint that is the “soap_service_authority”. If you are using it for Projector Restful Reporting Services, you should target the base endpoint that is the “rest_service_authority”.
The scope returned is the actual scope granted to the access token. This may or may not be exactly what the client app requested. If the user who authenticated and authorized the connection does not have the projector permissions granted to their account that would be necessary to grant the requested scope, the scope returned will be different (and more restrictive) than the scope requested. The client app may use this information in any way, including to revoke the token (if the user in question simply won't be able to accomplish what the app is for). Please note that the order of permissions listed in the scope is in now way guaranteed or expected to be the same order that was specified in the authentication request.
Note that the “Authorize” portion of this flow can only be done via an HTTP GET to the authorize endpoint. Once you get the authorization code, the documentation above shows how to exchange it for an access token using an HTTP POST request, which is part of the OAuth standard. However, there is an alternative available if you prefer it to use the soap service PwsAcquireOauth2Token
.
Refreshing a Projector OAuth Token
Perhaps, based on the expiry information that an access token in your possession has, your client code notices that the access token is about to expire, or perhaps a service invocation fails and the error indicates the reason is an expired or invalid session ticket. In these cases, you will want to refresh the access token and most likely re-invoke the service with a new access token.
To refresh the access token, you should issue an HTTP POST request to the token endpoint. If you are able to build the token endpoint by appending the string “/oauth2token”
to the rest_service_authority on the access token you are refreshing, it will be most efficient and fastest. Following our made-up example access token above, the token refresh endpoint would be:
https://app2.projectorpsa.com/oauth2token
but if it is difficult or inconvenient to build it that way, you can use the default token refresh endpoint of:
https://app.projectorpsa.com/oath2token
Either will work, but if you use the rest_service_authority version, you will get better performance. Form URL-encoded parameters on this POST request should be:
- grant_type=refresh_token
- refresh_token={The refresh token you got back on the access token}
- client_id={The client ID for the registered OAuth client app}
- client_secret={The client secret for the registered OAuth client app}
Assuming all goes well, your response will be a block of JSON just like the one shown previously on the code exchange. Note that it is possible (although likely rare) that a given user's Projector Permissions have changed between the time the previous access token was acquired and now when you have refreshed. As such, it is also possible (and again likely rare) that the scope returned on the refresh will be different from what was returned previously. If this is important to your client code, it should anticipate this possibility.
Again, if you prefer to use SOAP, you can use the PwsAcquireOauth2Token
Soap service.
Revoking a Projector OAuth Token
If your code needs to revoke a token previously granted, thereby terminating the connection between your app and Projector, you should issue an HTTP POST request to the revocation endpoint. If you are able to build the revocation endpoint by appending the string “/oauth2revoketoken”
to the rest_service_authority on the access token you are revoking, it will be most efficient and fastest. Following our made-up example access token above, the token refresh endpoint would be:
https://app2.projectorpsa.com/oauth2revoketoken
but if it is difficult or inconvenient to build it that way, you can use the default token revocation endpoint of:
https://app.projectorpsa.com/oauth2revoketoken
Either will work, but if you use the rest_service_authority version, you will get better performance. Form URL-encoded parameters on this POST request should be:
- client_id={The client ID for the registered OAuth client app}
- client_secret={The client secret for the registered OAuth client app}
- token={The refresh token you got back on the access token}
- token_type={Optional, but if present may only be the default value of “refresh_token”}
If all goes well, your response will be empty with the HTTP Status Code of 200 OK. Both the access token (AKA Session Ticket) and refresh token will be revoked and no longer valid.
If you prefer to use a SOAP service for token revocation, you can use the PwsRevokeOauth2RefreshToken
service.
Please note that the OAuth standard anticipates the possibility of revoking just an access token (AKA Session Ticket), but leaving the associated refresh token intact. In our server context this is something of a non-sequitur. You can always revoke a projector session ticket with the PwsUnauthenticate
Soap Service, but the restful revocation endpoint can only fully revoke refresh tokens at this time.
Scopes
The OAuth 2.0 Standard anticipates that client apps will request scopes
from the server when an Authentication Request is made. The semantics of scopes
is left up to the server implementation. Projector has chosen to map scopes
to Projector Permissions, which are granted to users and applied to sessions for the session lifetime. Please note that in the Projector OAuth Server implementation, the access tokens are session tickets, and designate a session. The Projector Server implementation of scopes will ensure that an access token acquired through OAuth will point to a session which never has more permissions than are requested through scopes. It may have fewer – if the authenticated user does not have the permissions being requested.
If an OAuth 2 Authentication Request is made to Projector without any scope, the resulting token when acquired will point to a session that has no elevated Projector Permissions. There are still services that the Client App may successfully invoke without any Projector Permissions.
There is a "Special" scope that is available to a Client App when constructing an OAuth 2 Authentication request. That "Special" scope is "allowFullPermissions". if scope=allowFullPermissions
is present on the Authentication Request query string, then the resulting access token will have the maximal permissions available to the user. This scope designation may not be combined with any other scope.
The more general case is that scope
may be specified as a URL-Encoded space-delimited list of individual Projector Permission tags. Projector Permission tags are strings that uniquely identify Projector Permissions. Permissions that may be requested and masked through OAuth scopes are either Global Permissions or Cost Center Permissions. Global Permission tags in the scope
list must be prefixed with the letter "V" (for View) or the letter "U" (for Update) and a colon. Cost Center Permission tags in the scope
list are not prefixed, as these are binary permissions.
So, for example, imagine that your client app seeks the permission to view Cost Center information (a global permission), to update User information (a global permission) and to enter time on behalf of others (a cost center permission) the scope would be designated on the query string of the authentication request URL as follows:
- Build up the space-delimited list of permissions:
V:maintainCostCenters U:maintainUsers enterTime
- URL-Encode that string:
V%3AmaintainCostCenters%20U%3AmaintainUsers%20enterTime
- Include it in the authentication request URL:
https://app.projectorpsa.com/oauth2authorize/acme-industries?response_type=code&client_id=29dd1cbb-953e-4126-9c2f-0bf8eeff5bab&redirect_uri=https%3A%2F%2Fmyapp.com%2Fprojector%2Foauth%2Fcode%2Fhandler&state=myurlencodedstate&scope=V%3AmaintainCostCenters%20U%3AmaintainUsers%20enterTime&code_challenge=ad463dfsdf23agrt56&code_challenge_method=s256
The list of permissions may be rendered in any order, but the request will fail with the invalid_scope
error if any of the following are true:
- A global permission is designated with out a prefix, or with an unsupported prefix.
- A cost center permission is designated with a prefix.
- A tag does not point to a valid Projector permission.
- A tag is supplied more than once in the same query string
- The specifically disallowed global permission
webServicesAccess
is specified. - The special scope
allowFullPermissions
is specified and any other permission tags are included
When the designation of scope
is valid on the authentication URL, and other parameters on that URL are correct, the user will be presented with an authentication page by Projector that solicits their credentials. If the correct credentials are supplied, then the user is presented with an additional page that will show them which scopes were requested and granted (because they have those permissions) and which scopes were requested and denied (because they do not have those permissions). If the user accepts that grant, the client app will get an authentication code (see above) and will then use that code to acquire an access token. The access token will include in the payload the list of permission tags as actually granted to the session, which may or may not be the full set of permissions requested. There is no guarantee of any particular order in the list returned as part of the access token payload. The client app may parse that space-delimited list in order to restrict access to the user to some portion of the app and/or simply revoke the token and terminate the connection if the user does not have appropriate permissions. This is all within the purview of the app and its specified use.
Please note that the grant of permissions based on the scope
request is valid for the lifetime of the access token. When the access token is refreshed, the available scopes are re-calculated based on the user permissions at the time of refreshing. It is possible (but likely rare) that the user's permissions will have changed. If the investigation of actual scopes returned is important to the app, it should be done not just when the initial access token is acquired, but also any time it is refreshed.