API and SPA Configuration (SPAs + API)
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. To interact with this object, you need to point your application to its URL. For example, if you had an API that could return either orders or customers, you might configure two endpoints: /orders
and /customers
. Your application would interact with these endpoints using different HTTP methods; for example, POST /orders
could create a new order or GET /orders
could 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.
An 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.
See the implementation in Node.js.
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, see Validate Access Tokens.
See the implementation in Node.js.
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.
See the implementation in Node.js.
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.
See the implementation in Node.js.
Implement the SPA
In this section we will see how we can implement a SPA for our scenario.
Authorize the user
To authorize the user we will be using the auth0.js library. You can initialize a new instance of the Auth0 application as follows:
var auth0 = new auth0.WebAuth({
clientID: '{yourClientId}',
domain: '{yourDomain}',
responseType: 'token id_token',
audience: 'YOUR_API_IDENTIFIER',
redirectUri: '{https://yourApp/callback}',
scope: 'openid profile read:timesheets create:timesheets'
});
Was this helpful?
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
. Thetoken
part, triggers the flow to return an Access Token in the URL fragment, while theid_token
part, 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 profile
will 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 theread:timesheets create:timesheets
scopes. This will ensure that the Access Token has these scopes.
To initiate the authentication flow you can call the authorize()
method:
auth0.authorize();
Was this helpful?
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 or Authorization Code Flow with Proof Key for Code Exchange (PKCE).
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.
For an example of how to handle sessions in SPAs, check out the Handle Authentication Tokens section of the JavaScript Single-Page App Quickstart.
See the implementation in Angular 2.
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.
The 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:
{
"email_verified": "false",
"email": "test@example.com",
"clientID": "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH",
"updated_at": "2017-02-07T20:50:33.563Z",
"name": "tester9@example.com",
"picture": "https://gravatar.com/avatar/example.png",
"user_id": "auth0|123456789012345678901234",
"nickname": "tester9",
"created_at": "2017-01-20T20:06:05.008Z",
"sub": "auth0|123456789012345678901234"
}
Was this helpful?
You can access any of these properties in the callback function passed when calling the userInfo
function:
const accessToken = authResult.accessToken;
auth0.client.userInfo(accessToken, (err, profile) => {
if (profile) {
// Get the user’s nickname and profile image
var nickname = profile.nickname;
var picture = profile.picture;
}
});
Was this helpful?
See the implementation in Angular 2.
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 authResult
.
If 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 authResult.scope
.
See the implementation in Angular 2.
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 Bearer
scheme.
See the implementation in Angular 2.
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).
See the implementation in Angular 2.