SPA + API: API and SPA Configuration
In this section we will see how we can implement an API for our scenario.
Define the API endpoints
First we need to define the endpoints of our API.
What is an API endpoint?
An API endpoint is a unique URL that represents an object. In order to interact with this object you need to point your application towards that URL. For example, if you had an API that could return either order or customers, you might configure two endpoints:
/customers. Your application would interact with these endpoints using different HTTP methods, for example
POST /orders to create a new order, or
GET /orders to retrieve the dataset of one or more orders.
For this implementation we will only define 2 endpoints; one for retrieving a list of all timesheets for an employee, and another which will allow an employee to create a new timesheet entry.
HTTP GET request to the
/timesheets endpoint will allow a user to retrieve their timesheets, and an
HTTP POST request to the
/timesheets endpoint will allow a user to add a new timesheet.
Secure the Endpoints
When an API receives a request with a bearer Access Token as part of the header, the first thing to do is to validate the token. This consists of a series of steps, and if any of these fails then the request must be rejected with a
Missing or invalid token error message to the calling app.
The validations that the API should perform are:
- Check that the JWT is well formed
- Check the signature
- Validate the standard claims
Part of the validation process is to also check the Application permissions (scopes), but we will address this separately in the next paragraph of this document.
For more information on validating Access Tokens, refer to Verify Access Tokens.
Check the Application's Permissions
By now we have verified that the JWT is valid. The last step is to verify that the application has the permissions required to access the protected resources.
To do so, the API needs to check the scopes of the decoded JWT. This claim is part of the payload and it is a space-separated list of strings.
Determine user identity
For both endpoints (retrieving the list of timesheets, and adding a new timesheet) we will need to determine the identity of the user.
For retrieving the list of timesheets this is to ensure that we only return the timesheets belonging to the user making the request, and for adding a new timesheet this is to ensure that the timesheet is associated with the user making the request.
One of the standard JWT claims is the
sub claim which identifies the principal that is the subject to the claim. In the case of the Implicit Grant flow this claim will contain the user's identity, which will be the unique identifier for the Auth0 user. You can use this to associate any information in external systems with a particular user.
You can also use a custom claim to add another attribute of the user - such as their email address - to the Access Token and use that to uniquely identify the user.
Implement the SPA
In this section we will see how we can implement a SPA for our scenario.
To authorize the user we will be using the auth0.js library. You can initialize a new instance of the Auth0 application as follows:
You need to pass the following configuration values:
- clientID:The value of your Auth0 Client Id. You can retrieve it from the Settings of your Application at the Dashboard.
- domain: The value of your Auth0 Domain. You can retrieve it from the Settings of your Application at the Dashboard.
- responseType: Indicates the Authentication Flow to use. For a SPA which uses the Implicit Flow, this should be set to
token id_token. The
tokenpart, triggers the flow to return an Access Token in the URL fragment, while the
id_tokenpart, triggers the flow to return an ID Token as well.
- audience: The value of your API Identifier. You can retrieve it from the Settings of your API at the Dashboard.
- redirectUri: The URL to which Auth0 should redirect to after the user has authenticated.
- scope: The scopes which determine the information to be returned in the ID Token and Access Token. A scope of
openid profilewill return all the user profile information in the ID Token. You also need to request the scopes required to call the API, in this case the
read:timesheets create:timesheetsscopes. This will ensure that the Access Token has these scopes.
To initiate the authentication flow you can call the
After the authentication, Auth0 will redirect back to the redirectUri you specified when configuring the new instance of the Auth0 application. At this point you will need to call the
parseHash() method which parses a URL hash fragment to extract the result of an Auth0 authentication response.
The contents of the authResult object returned by parseHash depend upon which authentication parameters were used. It may include the following:
- idToken: An ID Token JWT containing user profile information
- accessToken: An Access Token for the API, specified by the audience.
- expiresIn: A string containing the expiration time (in seconds) of the Access Token.
Determine where best to store the tokens. If your single-page app has a backend server at all, then tokens should be handled server-side using the Authorization Code Flow, Authorization Code Flow with Proof Key for Code Exchange (PKCE), or Hybrid Flow.
If you have a single-page app (SPA) with no corresponding backend server, your SPA should request new tokens on login and store them in memory without any persistence. To make API calls, your SPA would then use the in-memory copy of the token.
Get the User Profile
Extract info from the token
This section shows how to retrieve the user info using the Access Token and the /userinfo endpoint. To avoid this API call, you can just decode the ID Token using a library (make sure you validate it first). If you need additional user information consider using our Management API from your backend.
client.userInfo method can be called passing the returned
authResult.accessToken in order to retrieve the user's profile information. It will make a request to the /userinfo endpoint and return the
user object, which contains the user's information, similar to the example below:
You can access any of these properties in the callback function passed when calling the
Display UI Elements Conditionally Based on Scope
Based on the
scope of the user, you may want to show or hide certain UI elements. To determine the scope issued to a user, you will need to store the scope which was initially requested during the authorization process. When a user is authorized, the
scope will also be returned in the
scope in the
authResult is empty, then all the scopes which was requested was granted. If the
scope in the
authResult is not empty, it means a different set of scopes were granted, and you should use the ones in
Call the API
To access secured resources from your API, the authenticated user's Access Token needs to be included in requests that are sent to it. This is accomplished by sending the Access Token in an
Authorization header using the
Renew the Access Token
As a security measure, it is recommended that the lifetime of a user's Access Token be kept short. When you create an API in the Auth0 dashboard, the default lifetime is
7200 seconds (2 hours), but this can be controlled on a per-API basis.
Once expired, an Access Token can no longer be used to access an API. In order to obtain access again, a new Access Token needs to be obtained.
Obtaining a new Access Token can be done by repeating the authentication flow, used to obtain the initial Access Token. In a SPA this is not ideal, as you may not want to redirect the user away from their current task to complete the authentication flow again.
In cases like this you can make use of Silent Authentication. Silent authentication lets you perform an authentication flow where Auth0 will only reply with redirects, and never with a login page. This does however require that the user was already logged in via Single Sign-on (SSO).