Web Client Session Validation
Frontend validation of sessions often serves as the first line of defense and is commonly implemented in many web applications. This approach checks the authenticity of user sessions directly from the user's browser or client-side application. The main advantages of this approach are its simplicity and the reduction of server load, as validation happens on the client side.
If you're looking to set up backend validation, check out our Backend Validation page. The session management article gives an overview of the session validation in Descope. This article focuses on the client-side validation of sessions.
If you're using Next.js and looking to protect API routes and protect pages, checkout our blog: Add Descope Authentication to a Next.js 13 App Using NextAuth.
Install SDK
npm i --save @descope/react-sdk
npm i --save next-auth
npm i --save @descope/web-js-sdk
npm i --save @descope/vue-sdk
<!-- Not applicable for HTML -->
Import and initialize SDK
import { AuthProvider } from '@descope/react-sdk'
import { Descope, useDescope } from '@descope/react-sdk'
const AppRoot = () => {
return (
<AuthProvider
projectId="__ProjectID__"
// If the Descope project manages the token response in cookies,
// a custom domain must be configured
// (e.g., https://auth.app.example.com)
// and should be set as the baseUrl property.
// baseUrl = "https://auth.app.example.com"
>
<App />
</AuthProvider>
);
};
// app/api/auth/[...nextauth]
import { NextAuthOptions } from "next-auth"
import NextAuth from "next-auth/next";
export const authOptions: NextAuthOptions = {
providers: [
{
id: "descope",
name: "Descope",
type: "oauth",
wellKnown: `https://api.descope.com/${process.env.DESCOPE_PROJECT_ID}/.well-known/openid-configuration`,
authorization: { params: { scope: "openid email profile" } },
idToken: true,
clientId: process.env.DESCOPE_PROJECT_ID,
clientSecret: process.env.DESCOPE_ACCESS_KEY,
checks: ["pkce", "state"],
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
}
},
},
]
}
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
// app/provider.tsx
'use client'
import { SessionProvider } from "next-auth/react"
export default function NextAuthSessionProvider(
{ children }:
{ children: React.ReactNode }
) {
return (
<SessionProvider>
{ children }
</SessionProvider>
)
}
// app/layout.tsx
import NextAuthSessionProvider from './provider'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<NextAuthSessionProvider>
<div>
{children}
</div>
</NextAuthSessionProvider>
</body>
</html>
)
}
import DescopeSdk from '@descope/web-js-sdk';
try {
// baseUrl="<URL>" // you can also configure the baseUrl ex: https://auth.company.com - this is useful when you utilize CNAME within your Descope project.
const descopeSdk = DescopeSdk({ projectId: '__ProjectID__', persistTokens: true, autoRefresh: true });
} catch (error) {
// handle the error
console.log("failed to initialize: " + error)
}
import { createApp } from "vue";
import App from "@/App.vue";
import descope, { getSdk } from "@descope/vue-sdk";
const app = createApp(App);
app.use(router);
app.use(descope, {
projectId: '__Project_ID__',
baseUrl: "<base url>", // Optional
});
const sdk = getSdk();
sdk?.onSessionTokenChange((newSession) => {
// here you can implement custom logic when the session is changing
});
<head>
<script src="https://unpkg.com/@descope/web-js-sdk@x.x.x/dist/index.umd.js"></script>
<!-- Please replace `x.x.x` with the latest version of the WebJS SDK, which you can get from [here](https://www.npmjs.com/package/@descope/web-js-sdk) -->
</head>
Sending session token to application server
If you are using Client SDK or using Descope Flows, then your application client must send the session token to you application server. ThegetSessionToken()
function gets the sessionToken
from local storage via JS which you can then include in your request.NOTE: NextAuth does not use Descope's Client SDK but does use JWTs for session management.
import { getSessionToken } from '@descope/react-sdk';
const sessionToken = getSessionToken();
// example fetch call with authentication header
fetch('your_application_server_url', {
headers: {
Accept: 'application/json',
Authorization: 'Bearer '+ sessionToken,
}
})
// server components
import { getServerSession } from 'next-auth'
import { authOptions } from '@/app/_utils/options'
const session = await getServerSession(authOptions)
// client components
'use client'
import { useSession } from "next-auth/react"
const { data: session } = useSession()
import descopeSdk from '@descope/web-js-sdk';
const descopeSdk = descopeSdk({projectId: __ProjectID__});
const sessionToken = descopeSdk.getSessionToken();
// example fetch call with authentication header
fetch('your_application_server_url', {
headers: {
Accept: 'application/json',
Authorization: 'Bearer '+ sessionToken,
}
})
<script>
const descopeSdk = Descope({projectId: "__ProjectID__"});
const sessionToken = descopeSdk.getSessionToken();
// example fetch call with authentication header
fetch('your_application_server_url', {
headers: {
Accept: 'application/json',
Authorization: 'Bearer '+ sessionToken,
}
})
</script>
At any time, your application client should only send the session token to your application and the application server should validate the session token using Descope Backend SDK.
Logout using Client SDK
If you are integrating using the Descope Client SDK, then you must use the Client SDK to logout. If you are using Descope Flows with React SDK, refer to the Quick Start for details. If you are Descope Client SDK without flows, then refer to the sample code below for logout.
import DescopeSdk from '@descope/web-js-sdk';
const descopeSdk = Descope({projectId: "__ProjectID__"});
// Logout from the current session
const resp = await descopeSdk.logout();
// Logout from all the sessions
const resp = await descopeSdk.logoutAll();
// client component
'use client'
import { signOut } from "next-auth/react"
signOut({callbackUrl: "/"})
const descopeSdk = Descope({projectId: "__ProjectID__"});
// Logout from the current session
const resp = await descopeSdk.logout();
// Logout from all the sessions
const resp = await descopeSdk.logoutAll();
<script>
const descopeSdk = Descope({projectId: "__ProjectID__"});
// Logout from the current session
const resp = await descopeSdk.logout();
// Logout from all the sessions
const resp = await descopeSdk.logoutAll();
</script>
Checking token expiration
One important step in validating a session token is to ensure that the token has not expired. Descope SDKs allows you to check if the session is expired using theisJwtExpired
function.import { getSessionToken, useDescope } from '@descope/react-sdk'
const sessionToken = descopeSdk.getSessionToken();
const sdk = useDescope()
if(sdk.isJwtExpired(sessionToken)) {
console.log('Session token has expired.');
} else {
console.log('Session token is valid.');
}
'use client'
import { getSessionToken, isJwtExpired } from '@descope/react-sdk'
import { useSession } from "next-auth/react"
const { data: session } = useSession()
if(isJwtExpired(session)) {
console.log('Session token has expired.');
} else {
console.log('Session token is valid.');
}
const descopeSdk = descopeSdk({projectId: '__ProjectID__'});
const sessionToken = descopeSdk.getSessionToken();
if(isJwtExpired(sessionToken)) {
console.log('Session token has expired.');
} else {
console.log('Session token is valid.');
}
<script>
const descopeSdk = Descope({projectId: "__ProjectID__"});
const sessionToken = descopeSdk.getSessionToken();
if(isJwtExpired(sessionToken)) {
console.log('Session token has expired.');
} else {
console.log('Session token is valid.');
}
</script>
Roles
The role of a user is determined from their session token in your application. You can extract this information using Descope's SDKs, specifically thegetJwtRoles
function.import { getSessionToken, getJwtRoles } from '@descope/react-sdk'
const sessionToken = descopeSdk.getSessionToken();
const roles = getJwtRoles(sessionToken);
console.log('User roles:', roles);
const descopeSdk = descopeSdk({projectId: '__ProjectID__'});
const sessionToken = descopeSdk.getSessionToken();
const roles = getJwtRoles(sessionToken);
console.log('User roles:', roles);
<script>
const descopeSdk = Descope({projectId: "__ProjectID__"});
const sessionToken = descopeSdk.getSessionToken();
const roles = getJwtRoles(sessionToken);
console.log('User roles:', roles);
</script>
Permissions
Permissions granted to a user can also be extracted from the session token using thegetJwtPermissions
function from Descope's SDKs.import { getSessionToken, getJwtPermissions } from '@descope/react-sdk'
const sessionToken = descopeSdk.getSessionToken();
const permissions = getJwtPermissions(sessionToken);
console.log('User permissions:', permissions);
const descopeSdk = descopeSdk({projectId: '__ProjectID__'});
const sessionToken = descopeSdk.getSessionToken();
const permissions = getJwtPermissions(sessionToken);
console.log('User permissions:', permissions);
<script>
const descopeSdk = Descope({projectId: "__ProjectID__"});
const sessionToken = descopeSdk.getSessionToken();
const permissions = getJwtPermissions(sessionToken);
console.log('User permissions:', permissions);
</script>