Hello developers! 
Today I am back to provide some insight in regards to how Auth0 Sessions work and some guides/information on Refresh Tokens and to correctly use them within specific SDKs or configurations on your Auth0 tenant.
Sessions
When talking about sessions within Auth0, we describe them as a group of interactions between a given user and the application they are using, regardless of the application type (SPA, Regular Web or Native). A user may have multiple sessions for multiple given applications or a single session shared between applications. These sessions usually consist of information about the activities of the given user during their lifetime (things such as events, interactions, transactions, and page visits).
A session within the Auth0 context is created by having the user interact with the Authorization Server directly (either by a redirect to the /authorize endpoint or leveraging the Authentication API to retrieve the necessary tokens. Having stated this point, a session cannot be created by the application and assigned to the user without their interaction or on their behalf.
In the case the user successfully authenticates with the Auth0 authorization server using a Database Connection (such as Username and Password), two sessions are created:
- The local session, which indicates to the application whether a user is authenticated.
- The authorization server session, which indicates to the server whether a user is authenticated. The server session can also optionally track details about the authentication.
In the case the user authenticates with an external IdP (either via a Social or Enterprise connection, three different sessions are created:
- The local session
- The authorization server session
- An identity provider (IdP) session (ex: Facebook, Google, Okta Workforce)
For the examples presented above, a local session is established once the user has initiated and completed the login flow. This local session is used to determine when the user is supposed to be reauthenticated. Unfortunately, this is not the case for applications without a backend (such as SPA applications). These types of application use a different approach known as silent authentication, which uses the session on the Authorization Server to determine if reauthentication is required or if the session is still valid. During Silent Authentication, a hidden iframe redirects login/authentication requests to the Authorization Server without requiring user input by passing an extra parameter (prompt=none). If their session is still valid, the server replies with an access token, otherwise, the user will be logged in and redirected for reauthentication
Within Auth0, there are typically 3 types of sessions that can be created after the user successfully authenticates:
- Application Session Layer
This is the session that is created within your application in order to track the user. This type of information is typically stored in a cookie for Regular Web Apps, whereas for SPAs, it is tracked by a valid Refresh Token or session (checked via Silent Authentication).
- Auth0 Session Layer
The user session is maintained on the Authorization Server, which stores their information inside a cookie. This is used so that when the user is redirected to Auth0 for login, their information will be remembered and allows SSO to be possible.
- Identity Provider Session Layer
If a user attempts to log in using an external IdP where they have a valid session already, instead of being prompted to sign in, they will be presented with a consent screen to give permission to share their information with Auth0 and the application
If a user logs out of the application, the IdP session will remain valid. To log the users out of the external IdP, you need to perform a Federated Logout to prevent the user from initiating SSO using the same identity. This is particularly useful if the user has multiple identities with an external IdP and they would like to switch between them instead of being forced by SSO into the same identity.
Configuring Session lifetimes for your applications
You can configure the lifetime of your sessions within the Auth0 Dashboard. You can find these settings under Dashboard → Settings → Advanced view → Session Expiration section.
Here you will find several options to configure your session:
-
Default Session Policy. This can be configured to be either:
- Persistent → Stores a cookie with a timestamp for the future expiration of the user’s session.
- Non-Persistent → Stores a cookie with the parameter
Expires=0which indicates that the session will be invalidated once the browser is being closed.
Please take into account that the persistence of these cookies depends on the browser implementation and that the non-persisted cookies will be deleted as expected. -
Idle Session Lifetime (both for Persistent and Non-Persistent)
This indicates the maximum lifetime in minutes in which a session will be invalidated. Inactivity is described as the last time the user has interacted with the Authorization Server. If the user is basically active (interacting with the application in different ways) on the website and does not interact with the Authorization Server, their session will be invalidated regardless. The above would require a custom implementation within the application, which would persist or invalidate the user’s session depending on their website activity (mouse clicks/movements, etc.). -
Maximum Session Lifetime (both for Persistent and Non-Persistent)
This indicates the maximum lifetime of a session, regardless of the user’s activity, and is determined by the timeframe when the session was initially created (available under the event object). This information can be viewed under thecreated_atattribute, and the expiration time is indicated by theexpires_atattribute within an Action.
These settings can also be configured by the Management API, however please take into consideration that the values are measured in HOURS whereas the dashboard configuration is measured in MINUTES.
Sessions can also be managed via Actions or by using the Management API (available for Enterprise plans) as mentioned in our documentation.
While the settings presented above in regards to session lifetime management are tenant-wide configurations, sessions can be managed using a PostLogin Action in order to have a more dynamic configuration depending on the user’s:
- Application
- Organization
- Group
- Roles
- Connection
Both the inactivity and maximum lifetime can be configured within the API object of the action. Please take note that these values are measured in MILISECONDS. A valid example of such an implementation would be:
exports.onExecutePostLogin = async (event, api) => {
const expireOne = 1728000000
const expireTwo = 2768000000
const idleOne = 728000000
const idleTwo = 1428000000
//this can be further customized depending on the users:
//organization -> event.user.organization
//roles -> event.authorization.roles
//application -> event.client.client_id
if(event.connection.name === "'Google-Connection"{
//apply specific session lifetime for users logging in using a Google connection
api.session.setExpiresAt(expireOne);
api.session.setIdleExpiresAt(idleOne);
}
else if(event.connection.name === "Password-Connection"){
//apply specific session lifetime for users logging in using Email+Password
api.session.setExpiresAt(expireTwo);
api.session.setIdleExpiresAt(idleTwo);
}
else{
//apply tenant level configuration
return;
}
};
Additionally, by using a PostLogin Action you can add and manage session metadata for CRUD requests - Create, Read, Update and Delete requests.
These operations are available in the API object as such:
exports.onExecutePostLogin = async (event, api) => {
//Create or Update
api.session.setMetadata("{{metadata_name}}", "{{metadata_value}}");
//Delete
api.session.deleteMetadata("{{metadata_key}}") //deletes the specified session metadata
api.session.evictMetadata() //deletes all session metadata
}
You can check out this use case regarding using session metadata.
Refresh Tokens
One of the most important features to provide a much better user experience is Refresh Tokens. Refresh tokens allow users to extend the session to your application without having to force them to reauthenticate several times during the day. Refresh tokens are part of the OAuth protocol, which allows the application to retrieve a new access token without user interaction, and also allows the authorization server to shorten the access token lifetime for security purposes
Due to the fact that refresh tokens basically allow the user to remain authenticated forever, you must keep the number of refresh tokens within a reasonable and manageable limit in order to maintain those credentials safe and secure. The risk of compromised refresh tokens can be reduced by using refresh token rotation for your application in order to issue new refresh tokens, which invalidate the previous ones with each request made to Auth0.
Refresh token rotation is compatible with the following authentication flows:
- Authorization Code Flow
- Authorization Code Flow with Proof Key for Code Exchange
- Device Authorization Flow
- Resource Owner Password Flow
Configuring Refresh Tokens for your Application
You can enable Refresh Tokens and Refresh Token Rotation for your Auth0 Application within the dashboard. These options can be found under Dashboard → Applications → Applications → Your Auth0 Application → Settings → Refresh Token Expiration//Refresh Token Rotation sections
Here you will have the following options available:
- Set Idle Refresh Token Lifetime – The maximum lifetime in seconds after which a refresh token will expire after a period of inactivity
- Set Maximum Refresh Token Lifetime - The maximum lifetime in seconds after which a refresh token will expire regardless of activity
- Allow Refresh Token Rotation:
– Enables rotating refresh tokens for your application
– Requires Maximum Lifetime to be enabled
–Invalidates Refresh Tokens after use - Rotation Overlap Period – Period of time in which the most recently used refresh token can be reused without triggering breach detection
Once Refresh Tokens have been configured for your application, you will need to:
- Make sure the Refresh Token Grant is enabled for your application. This can be found under the Application’s Settings → Advanced → Grant Types
- Register your API with Auth0 by going to Applications → APIs → Create API button
- If you wish to allow your users to refresh tokens while offline, you will need to enable offline access under your APIs Settings → Access Settings → Allow Offline Access
Once you have configured everything on the Auth0 tenant, some SDKs might need additional configuration in order to initiate a proper refresh token flow. Some of the most common mistakes are caused by not including the proper scopes and audience in the authorization parameters or when initializing the Auth0 client/provider. A proper example can be seen below in the case of the React SDK however, we will tackle other SDKs later in the post, since they might handle refresh tokens automatically.
//initialization of the Auth0 Provider using Refresh Tokens
<Auth0Provider
domain="YOUR_AUTH0_DOMAIN"
clientId="YOUR_AUTH0_CLIENT_ID"
cacheLocation={"localstorage"}
useRefreshTokens={true}
authorizationParams={{
redirect_uri: window.location.origin,
audience: "{{YOUR_API_IDENTIFIER}}",
scope: "openid email profile offline_access" //the offline access scope
//must be included if offline access
//is enabled on the APIs settings
}}>
//Example of an authorization request made with the proper parameters for refresh tokens
const Login = () => {
const { loginWithRedirect } = useAuth0();
return <button onClick={() => loginWithRedirect({
authorizationParams: {
audience: '<AUTH0 API IDENTIFIER>',
scope: 'openid profile email offline_access'
}
})}>Login</button>;
};
With all of these things taken into consideration, if you have integrated MFA for your application, however, if the refresh token exchange fails due to an mfa_required error, you can use this example in order to bypass any MFA logic using a PostLogin Action:
exports.onExecutePostLogin = async (event, api) => {
//Bypass the MFA whenever a refresh token exchange flow is used
if (event.transaction.protocol === "oauth2-refresh-token") {
return;
}
// Add your MFA logic
api.multifactor.enable("any");
};
Examples on how to configure Auth0 SDKs to work with refresh tokens
//Initialize your Auth0 Provider with the necessary configuration
<Auth0Provider
domain="YOUR_AUTH0_DOMAIN"
clientId="YOUR_AUTH0_CLIENT_ID"
useRefreshTokens={true}
cacheLocation={"localstorage"}
authorizationParams={{
redirect_uri: window.location.origin,
audience: "{{YOUR_API_IDENTIFIER}}",
scope: "openid email profile offline_access"
}}>
//Alternatively, the authorization parameters can be passed to the getTokenSilently hook directly
const token = await getAccessTokenSilently({
authorizationParams: {
audience: '{{YOUR_API_IDENTIFIER}}',
scope: "openid email profile offline_access"
}
});
//Make sure to initialize your Auth0 cline using the proper parameters
const auth0 = await createAuth0Client({
domain: '<AUTH0_DOMAIN>',
clientId: '<AUTH0_CLIENT_ID>',
useRefreshTokens: true,
authorizationParams: {
redirect_uri: '<MY_CALLBACK_URL>',
audience: 'https://your_audience',
scope: 'openid profile email offline_access'
}
});
const auth0Config = mergeApplicationConfig(appConfig, {
providers: [
provideAuth0({
domain: environment.auth0.domain,
clientId: environment.auth0.clientId,
useRefreshTokens: true,
useRefreshTokenFallbacks: true,
authorizationParams: {
redirect_uri: window.location.origin,
audience: '{{YOUR_API_IDENTIFIER}}',
scope: "openid email profile offline_access"
}
})
]
});
const app = createApp(App);
app.use(
createAuth0({
domain: "YOUR_AUTH0_DOMAIN",
clientId: "YOUR_CLIENT_ID",
useRefreshTokens: true,
cacheLocation: 'localstorage',
authorizationParams: {
redirect_uri: window.location.origin,
audience: "YOUR_API_AUDIENCE",
scope: "openid profile email offline_access"
}
})
);
//Renews the credentials using a refresh token if one is available
final credentials = await auth0.credentialsManager.credentials();
//Optionally, you can add the necessary scopes and audience in your login.dart file
try {
final credentials = await auth0.webAuthentication().login(
scopes: {
'openid',
'profile',
'email',
'offline_access'
},
audience: 'YOUR_API_AUDIENCE'
);
//The NextJS SDK handles refresh tokens automatically.
//Also, the /auth/access-token route automatically checks if a refresh token is available
//The offline_access scope must be defined as shown below
//Make sure the offline_access scope is defined in your env.local file
AUTH0_SCOPE="openid profile email offline_access"
//You can also force token refresh when calling the getAccessToken hook
export async function GET() {
const { accessToken } = await getAccessToken({
refresh: true // This forces a refresh
});
-
ASP.NET Core SDK (In your Program.cs file)
//This is a basic configuration in your code so that it requests a refresh token
//The handling of refresh tokens is not fully automatic and requires
//some explicit configuration and, in most cases, it would require a
//custom implementation to handle the login using the refresh token
builder.Services.AddAuth0WebAppAuthentication(options =>
{
options.Domain = builder.Configuration["Auth0:Domain"];
options.ClientId = builder.Configuration["Auth0:ClientId"];
options.ClientSecret = builder.Configuration["Auth0:ClientSecret"];
options.Scope = "openid profile email offline_access";
})
.WithConfiguredCookie(options =>
{
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = HandleTokenRefresh
};
});
// Inside your login.php add the following
use Auth0\SDK\Auth0;
$auth0 = new Auth0([
'domain' => 'YOUR_AUTH0_DOMAIN',
'clientId' => 'YOUR_CLIENT_ID',
'clientSecret' => 'YOUR_CLIENT_SECRET',
'cookieSecret' => 'YOUR_COOKIE_SECRET',
'redirectUri' => 'http://localhost/callback.php',
]);
$auth0->login(
null,
['scope' => 'openid profile email offline_access']
);
//The following is some dummy code provided as an example on storing the refresh token
// inside your callback.php
use Auth0\SDK\Auth0;
session_start();
$auth0 = new Auth0([ /* ... config ... */ ]);
try {
$auth0->exchange();
// Get the user's session data from the SDK
$session = $auth0->getCredentials();
if ($session !== null && $session->refreshToken !== null) {
// Store the refresh token in the PHP session
$_SESSION['refresh_token'] = $session->refreshToken;
$_SESSION['access_token'] = $session->accessToken;
$_SESSION['expires_at'] = time() + $session->accessTokenExpiration;
//Store user info if needed
$_SESSION['user'] = $session->user;
}
header('Location: /profile.php');
exit;
} catch (\Auth0\SDK\Exception\StateException $e) {
die('Invalid state: ' . $e->getMessage());
} catch (\Exception $e) {
die('Error in callback: ' . $e->getMessage());
}
//The following handles the refresh token logic needed in the PHP SDK
$needsRefresh = false;
if (empty($_SESSION['expires_at']) || $_SESSION['expires_at'] < (time() - 60)) {
$needsRefresh = true;
}
if ($needsRefresh) {
try {
$auth0 = new Auth0([ /* ... config ... */ ]);
$authApi = $auth0->authentication();
$newTokens = $authApi->refreshToken(
$_SESSION['refresh_token'],
['scope' => 'openid profile email offline_access']
);
//Store the new tokens on success
$_SESSION['access_token'] = $newTokens['access_token'];
$_SESSION['expires_at'] = time() + $newTokens['expires_in'];
// New rotating refresh token issued if enabled
if (isset($newTokens['refresh_token'])) {
$_SESSION['refresh_token'] = $newTokens['refresh_token'];
}
echo "Token was refreshed!";
} catch (\Exception $e) {
// Refresh failed (e.g., token expired or was revoked)
// Log the user out and send them to login
session_destroy();
header('Location: /login.php?msg=session_expired');
exit;
}
}
// In your ContentView.swift file
//This is an example using credentialsManager
func login() {
Auth0
.webAuth()
.audience("{{YOUR_API_IDENTIFIER")
.scope("openid profile email offline_access")
.start { result in
switch result {
case .failure(let error):
print("Failed with: (error)")
case .success(let credentials):
self.isAuthenticated = true
self.userProfile = Profile.from(credentials.idToken)
credentialsManager.store(credentials: credentials)
//You can print the values here to make sure the tokens are being returned and to
//help with debugging if necessary
print("Credentials: (credentials)")
print("ID token: (credentials.idToken)")
print("Access token: (credentials.accessToken)")
print("Refresh token: (credentials.refreshToken ?? "")")
}
}
}
//Example using KeyChain
let keychain = SimpleKeychain(service: "Auth0")
Auth0
.webAuth()
.audience("{{YOUR_API_IDENTIFIER")
.scope("openid profile offline_access")
.start { result in
switch result {
case .success(let credentials):
guard let refreshToken = credentials.refreshToken else {
// Handle error
return
}
// Store the tokens
do {
try keychain.set(credentials.accessToken, forKey: "access_token")
try keychain.set(refreshToken, forKey: "refresh_token")
} catch {
// Handle error
}
// You might want to route to your app's main flow at this point
case .failure(let error):
// Handle error
}
}
You can also check out this cool blog regarding Using Refresh Tokens with Swift.
The CredentialsManagers helps in handling refresh tokens.
I would highly recommend checking our documentation about saving and renewing
tokens here: Auth0.Android Save and Renew Tokens
//In your WebAuthProvider.login file
private fun login() {
WebAuthProvider
.login(account)
.withScheme(getString(R.string.com_auth0_scheme))
.withScope("openid profile email offline_access")
.withAudience(getString(R.string.login_audience, getString(R.string.com_auth0_domain)))
.start(this, object : Callback<Credentials, AuthenticationException> {
override fun onFailure(exception: AuthenticationException) {
showSnackBar(getString(R.string.login_failure_message, exception.getCode()))
}
override fun onSuccess(credentials: Credentials) {
cachedCredentials = credentials
showSnackBar(getString(R.string.login_success_message, credentials.accessToken))
updateUI()
showUserProfile()
}
})
}
// Inside your component
const { authorize, user } = useAuth0();
const onLogin = async () => {
try {
await authorize({
scope: "openid profile email offline_access",
audience: "{{YOUR_API_IDENTIFIER}}"
});
} catch (e) {
console.log(e);
}
};
final credentials = await auth0.credentialsManager.credentials();
//OR
final credentials = await auth0.webAuthentication().login();
//This method will automatically renew the credentials
//using a refresh token if one is available
//Angular SDK - app.module.ts file
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
AuthModule.forRoot({
domain: "{yourDomain}",
clientId: "{yourClientId}",
useRefreshTokens: true,
useRefreshTokensFallback: false,
authorizationParams: {
redirect_uri
}
}),
],
providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
bootstrap: [AppComponent],
})
//React SDK - index.tsx file
const root = createRoot(document.getElementById('root'));
root.render(
<Auth0Provider
domain="{{AUTH0_DOMAIN}}"
clientId="{{CLIENT_ID}}"
useRefreshTokens={true}
useRefreshTokensFallback={false}
authorizationParams={{
redirect_uri: "{{YOUR_PACKAGE_ID}}://{{AUTH0_DOMAIN}}/capacitor/{{YOUR_PACKAGE_ID}}/callback"
}}
>
<App />
</Auth0Provider>
);
//Vue SDK - main.ts file
const redirect_uri = "${{config.appId}}://{{AUTH0_DOMAIN}}/capacitor/${{config.appId}}/callback";
const app = createApp(App).use(IonicVue).use(router);
app.use(
createAuth0({
domain: "{{AUTH0_DOMAIN}}",
clientId: "{{CLIENT_ID}}",
useRefreshTokens: true,
useRefreshTokensFallback: false,
authorizationParams: {
redirect_uri
}
})
);
router.isReady().then(() => {
app.mount("#app");
});
Please take into consideration possible race conditions in your code. If a method//hook which helps you retrieve the refresh tokens depends on another method//hook, make sure to configure your code to await the previous function or to impose proper timeouts in order for the exchange to work.
This would conclude the small tutorial on using Refresh Tokens with the different Auth0 SDKs and how they must be configured. I did not include all of the available SDKs, mostly due to how popular the ones presented are. If you have any other questions regarding this topic or would like some extra examples/help with some questions, let me know by leaving a reply down below!
Until next time and see you all around here!
Nik
Auth0 Community Engineer