diff --git a/.gitignore b/.gitignore
index fb7ac70..bbbc91b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,3 +40,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+
+src/app/api/auth/debug
diff --git a/README.md b/README.md
index b9d35b4..8fe906c 100644
--- a/README.md
+++ b/README.md
@@ -145,3 +145,5 @@ git tag -d v1.0.0; git push origin :refs/tags/v1.0.0; git tag -a v1.0.0 -m "Rele
- No error when streaming with too large model
- Translations
- When user stop generation, forward it to server
+- Message UI
+- UX (eg keep focus when press enter)
diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx
index b21d475..81724b1 100644
--- a/src/app/[locale]/layout.tsx
+++ b/src/app/[locale]/layout.tsx
@@ -7,8 +7,9 @@ import { routing } from '@/i18n/routing';
import { getMessages } from 'next-intl/server';
import { Providers } from '../providers';
import { getServerSession } from 'next-auth';
-import { authOptions } from '@/lib/auth';
import { cookies } from 'next/headers';
+import { authOptions } from '@/lib/auth';
+import SessionGuard from '@/components/SessionGuard';
export const metadata = {
title: 'Iceberg template',
@@ -42,13 +43,15 @@ export default async function RootLayout(props: {
-
+
+
-
- {props.children}
-
+
+ {props.children}
+
-
+
+
diff --git a/src/app/providers.tsx b/src/app/providers.tsx
index 56bd82c..3fd259c 100644
--- a/src/app/providers.tsx
+++ b/src/app/providers.tsx
@@ -16,7 +16,7 @@ export function Providers({
messages: AbstractIntlMessages;
}) {
return (
-
+
{
+ if (status === "unauthenticated" && !triedSilent.current) {
+ triedSilent.current = true;
+ signIn("keycloak", undefined, { prompt: "none" });
+ }
+
+ if (
+ session?.error === "RefreshTokenExpired" ||
+ session?.error === "RefreshAccessTokenError"
+ ) {
+ signIn("keycloak", undefined, { prompt: "none" });
+ }
+ }, [status, session?.error]);
+
+ if (status === "loading") {
+ return
+ }
+
+ return <>{children}>;
+}
\ No newline at end of file
diff --git a/src/components/spinner.tsx b/src/components/Spinner.tsx
similarity index 100%
rename from src/components/spinner.tsx
rename to src/components/Spinner.tsx
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
index 70e7357..d4d685c 100644
--- a/src/lib/auth.ts
+++ b/src/lib/auth.ts
@@ -1,30 +1,34 @@
-import { NextAuthOptions } from 'next-auth';
-import KeycloakProvider from 'next-auth/providers/keycloak';
-import { JWT } from 'next-auth/jwt';
+import KeycloakProvider from "next-auth/providers/keycloak";
+import { NextAuthOptions } from "next-auth";
+import { JWT } from "next-auth/jwt";
-declare module 'next-auth' {
- interface Session {
+declare module "next-auth" {
+ interface Session {
accessToken?: string;
+ idToken?: string;
roles?: string[];
error?: string;
}
}
-declare module 'next-auth/jwt' {
+declare module "next-auth/jwt" {
interface JWT {
- accessToken?: string;
- refreshToken?: string;
- accessTokenExpires?: number;
+ accessToken: string;
+ idToken: string;
+ refreshToken: string;
+ accessTokenExpiresAt: number;
+ refreshTokenExpiresAt: number;
+ roles: string[];
error?: string;
- roles?: string[];
}
}
-function decode(token: string) {
- try {
- const base64 = token.split('.')[1];
+const NEVER_EXPIRES = 9999999999;
- return JSON.parse(Buffer.from(base64, 'base64').toString('utf-8'));
+export function decode(token: string) {
+ try {
+ const base64 = token.split(".")[1];
+ return JSON.parse(Buffer.from(base64, "base64").toString("utf-8"));
} catch {
return null;
}
@@ -33,46 +37,62 @@ function decode(token: string) {
async function refreshAccessToken(token: JWT): Promise {
try {
if (!token.refreshToken) {
- throw new Error('No refresh token');
+ throw new Error("Missing refresh token");
}
const response = await fetch(
`${process.env.KEYCLOAK_ISSUER}/protocol/openid-connect/token`,
{
- method: 'POST',
+ method: "POST",
+ cache: 'no-store',
headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
+ "Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
client_id: process.env.KEYCLOAK_CLIENT_ID!,
client_secret: process.env.KEYCLOAK_CLIENT_SECRET!,
- grant_type: 'refresh_token',
+ grant_type: "refresh_token",
refresh_token: token.refreshToken,
}),
- },
+ }
);
+ const data = await response.json();
+
if (!response.ok) {
- throw new Error('Refresh failed');
+ throw new Error(`Refresh failed: ${response.status} ${JSON.stringify(data)}`);
}
- const refreshed = await response.json();
-
- const decoded = decode(refreshed.access_token);
+ const access = decode(data.access_token);
+ const refresh = decode(data.refresh_token);
+ const clientId = process.env.KEYCLOAK_CLIENT_ID!;
return {
...token,
- accessToken: refreshed.access_token,
- refreshToken: refreshed.refresh_token ?? token.refreshToken,
- accessTokenExpires: decoded?.exp
- ? decoded.exp * 1000
- : Date.now() + 5 * 60 * 1000,
+
+ accessToken: data.access_token!,
+ refreshToken: data.refresh_token!,
+ idToken: data.id_token!,
+
+ accessTokenExpiresAt: access.exp!,
+ refreshTokenExpiresAt: refresh.exp ?? NEVER_EXPIRES,
+
+ roles: [
+ ...new Set([
+ ...(access?.realm_access?.roles ?? []),
+ ...(access?.resource_access?.[clientId]?.roles ?? []),
+ ]),
+ ],
+
error: undefined,
};
- } catch {
+
+ } catch (err) {
+ console.error("[refreshAccessToken] failed:", err);
+
return {
...token,
- error: 'RefreshAccessTokenError',
+ error: "RefreshAccessTokenError",
};
}
}
@@ -83,67 +103,133 @@ export const authOptions: NextAuthOptions = {
clientId: process.env.KEYCLOAK_CLIENT_ID!,
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET!,
issuer: process.env.KEYCLOAK_ISSUER!,
- }),
+ authorization: {
+ params: {
+ scope: "openid offline_access",
+ },
+ },
+ })
],
- secret: process.env.NEXTAUTH_SECRET,
callbacks: {
async jwt({ token, account }) {
- if (token.error === 'RefreshAccessTokenError') return token;
- // initial login
- if (account) {
- const decoded = decode(account.access_token!);
+ const now = Math.floor(Date.now() / 1000);
+ // 1. LOGIN
+ if (account) {
+ console.log("[jwt] LOGIN - building initial token state");
+
+ const decodedRefreshToken = decode(account.refresh_token!);
+ const decodedAccessToken = decode(account.access_token!);
const clientId = process.env.KEYCLOAK_CLIENT_ID!;
- const realmRoles = decoded?.realm_access?.roles ?? [];
- const clientRoles = decoded?.resource_access?.[clientId]?.roles ?? [];
-
- token.roles = [...new Set([...realmRoles, ...clientRoles])];
-
- token.accessToken = account.access_token;
- token.refreshToken = account.refresh_token;
- token.accessTokenExpires = decoded?.exp
- ? decoded.exp * 1000
- : Date.now() + 5 * 60 * 1000;
+ token.accessToken = account.access_token!;
+ token.refreshToken = account.refresh_token!;
+ token.idToken = account.id_token!;
+ token.accessTokenExpiresAt = account.expires_at!;
+ token.refreshTokenExpiresAt = decodedRefreshToken.exp ?? NEVER_EXPIRES;
+ token.roles = [
+ ...new Set([
+ ...(decodedAccessToken?.realm_access?.roles ?? []),
+ ...(decodedAccessToken?.resource_access?.[clientId]?.roles ?? []),
+ ]),
+ ];
+ token.error = undefined;
return token;
}
- // stop loop if refresh already failed
- if (
- token.accessTokenExpires &&
- Date.now() > token.accessTokenExpires - 60_000
- ) {
- return { ...token, error: 'RefreshAccessTokenError' };
+ // 2. SESSION EXPIRED → FORCE LOGIN
+ if (now >= token.refreshTokenExpiresAt) {
+ return { ...token, error: "RefreshTokenExpired" };
}
- // still valid
- if (token.accessTokenExpires && Date.now() < token.accessTokenExpires) {
+ // 3. ACCESS TOKEN STILL VALID
+ if (now < token.accessTokenExpiresAt - 60) {
return token;
}
- // refresh expired token
return await refreshAccessToken(token);
},
async session({ session, token }) {
session.accessToken = token.accessToken;
+ session.idToken = token.idToken;
+ session.roles = token.roles ?? [];
session.error = token.error;
- if (!token.accessToken) {
- session.roles = [];
- return session;
- }
-
- const payload = decode(token.accessToken);
- const clientId = process.env.KEYCLOAK_CLIENT_ID!;
-
- const realmRoles = payload?.realm_access?.roles ?? [];
- const clientRoles = payload?.resource_access?.[clientId]?.roles ?? [];
-
- session.roles = [...new Set([...realmRoles, ...clientRoles])];
-
return session;
},
},
};
+
+// {
+// provider: 'keycloak',
+// type: 'oauth',
+// providerAccountId: '68ae60bf-277f-44b6-87a7-46e34072b9f4',
+// access_token: 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJublpLek04TkZHVmpWbGFPRXZpMUtFSTVHQWRwaGlsYjh3RHRLeG5JOENZIn0.eyJleHAiOjE3Nzc5MTY5MDIsImlhdCI6MTc3NzkxNjc4MiwiYXV0aF90aW1lIjoxNzc3OTEzNDc1LCJqdGkiOiJiZmRmMjc1ZC1jNWQ3LTRlZWQtYWJlYS04NDQ5YWRjY2E2NGMiLCJpc3MiOiJodHRwczovL2F1dGguaWNlYmVyZy5ibGFjay9yZWFsbXMvaWNlYmVyZyIsImF1ZCI6WyJnaXRlYSIsImplbGx5ZmluIiwiYWNjb3VudCJdLCJzdWIiOiI2OGFlNjBiZi0yNzdmLTQ0YjYtODdhNy00NmUzNDA3MmI5ZjQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJjaGF0LXdlYnNpdGUiLCJzZXNzaW9uX3N0YXRlIjoiMmUyNGNiZjctMTQxYi00NmUwLTk0N2YtOWFmY2JlMWY3NWI2IiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iLCJkZWZhdWx0LXJvbGVzLWljZWJlcmciXX0sInJlc291cmNlX2FjY2VzcyI6eyJnaXRlYSI6eyJyb2xlcyI6WyJ0ZW1wbGF0ZSIsImljZWJlcmciLCJhZG1pbiIsImRlbGV0YW5nIiwidXNlciJdfSwiY2hhdC13ZWJzaXRlIjp7InJvbGVzIjpbImFkbWluIiwidXNlciJdfSwiamVsbHlmaW4iOnsicm9sZXMiOlsiYWRtaW4iLCJ1c2VyIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIiwic2lkIjoiMmUyNGNiZjctMTQxYi00NmUwLTk0N2YtOWFmY2JlMWY3NWI2IiwibmFtZSI6Ikx1Y2FzIERlbGV0YW5nIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibHVjYXMiLCJnaXZlbl9uYW1lIjoiTHVjYXMiLCJmYW1pbHlfbmFtZSI6IkRlbGV0YW5nIn0.X5PMv6-H21hdWvP768X4NDtRc_f5E1NALM1mSxM44-S8B8pQkP3ROSKZR2jmf828LOlLVfC-moODHTBgPkrwGGfkDpDXOtBzCo9-w_hu2S63OSSSg-ej3EPkT_7OocykSeq0V9VWllrdll7T6PiVuNX4dVstizVCweGJ7gtZXjDdi15VExfSjiddWk6mF-ZFX9B3TJFqjLfoXEaaEUrZJoqErKWbr8JlIYFPeaekYWxdScB_yoDJUPkruDShHbk5tGATr6OfkX_lsVQ5oEDDPQ_EHQU5P_r4ILEbXoAk96yJMTJo2yQLEgaeAX3RxBGXRlNYs3rV6cHYCw67mg0Now',
+// expires_at: 1777916902,
+// refresh_expires_in: 1800,
+// refresh_token: 'eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2Nzc0ZGFiYi05NjAxLTQyOTYtYTkwYi0yOWFjYjVlM2U3N2UifQ.eyJleHAiOjE3Nzc5MTg1ODIsImlhdCI6MTc3NzkxNjc4MiwianRpIjoiODU3ZTZlMmEtZjI1MS00NTI3LWJjMDktYmY3MTM4ODE1NzQyIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmljZWJlcmcuYmxhY2svcmVhbG1zL2ljZWJlcmciLCJhdWQiOiJodHRwczovL2F1dGguaWNlYmVyZy5ibGFjay9yZWFsbXMvaWNlYmVyZyIsInN1YiI6IjY4YWU2MGJmLTI3N2YtNDRiNi04N2E3LTQ2ZTM0MDcyYjlmNCIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJjaGF0LXdlYnNpdGUiLCJzZXNzaW9uX3N0YXRlIjoiMmUyNGNiZjctMTQxYi00NmUwLTk0N2YtOWFmY2JlMWY3NWI2Iiwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSIsInNpZCI6IjJlMjRjYmY3LTE0MWItNDZlMC05NDdmLTlhZmNiZTFmNzViNiJ9.ydQzc6YWDdem_ubf4s9dw4Ys6hRnQSWBp8ZeweWZg7NqVCl7SvdxJ2XUgXNxMZ88cx5u_pE1bL6Kahsjvmq-wA',
+// token_type: 'Bearer',
+// id_token: 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJublpLek04TkZHVmpWbGFPRXZpMUtFSTVHQWRwaGlsYjh3RHRLeG5JOENZIn0.eyJleHAiOjE3Nzc5MTY5MDIsImlhdCI6MTc3NzkxNjc4MiwiYXV0aF90aW1lIjoxNzc3OTEzNDc1LCJqdGkiOiJhYjllYTliYi1lODkyLTQzNTctYmQ4NS0xYjk4NDg5ODliM2MiLCJpc3MiOiJodHRwczovL2F1dGguaWNlYmVyZy5ibGFjay9yZWFsbXMvaWNlYmVyZyIsImF1ZCI6ImNoYXQtd2Vic2l0ZSIsInN1YiI6IjY4YWU2MGJmLTI3N2YtNDRiNi04N2E3LTQ2ZTM0MDcyYjlmNCIsInR5cCI6IklEIiwiYXpwIjoiY2hhdC13ZWJzaXRlIiwic2Vzc2lvbl9zdGF0ZSI6IjJlMjRjYmY3LTE0MWItNDZlMC05NDdmLTlhZmNiZTFmNzViNiIsImF0X2hhc2giOiJFV1JkS0JYNmd0czZoNHFId0ViWG5nIiwic2lkIjoiMmUyNGNiZjctMTQxYi00NmUwLTk0N2YtOWFmY2JlMWY3NWI2IiwibmFtZSI6Ikx1Y2FzIERlbGV0YW5nIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibHVjYXMiLCJnaXZlbl9uYW1lIjoiTHVjYXMiLCJmYW1pbHlfbmFtZSI6IkRlbGV0YW5nIn0.e2Jn0nbJ1qzNwnuSCAJMjHn6J3JOpJJ84ttne-XiZqtUln9KD3OaPikXH0Rzl-bKy6WxW7ZOLNdEiRUZgliUXxnc3j8zT5Fincm6cEZ3FOXIozlzdoo5i59gjZ9QqvbQ0794RvQJ-24J4C8xMdn5g5baOehf3gJL4pTRYfCECgijbQ-oAnu04YLT3OZN9GpCreU4VoIfgdebX2eiDuyTyECnfpfJuDBeAyn19fNp5TzY2GQHUUbhW2CxiTvHSBhYSw5Zld_csgTmTisWVJ7wTbAM_A7xjn9VjxCs5UnexzufGWL_O02Pi2XdTbloSSbHcB7rMAtSNE-o0fBHjdaH2g',
+// 'not-before-policy': 0,
+// session_state: '2e24cbf7-141b-46e0-947f-9afcbe1f75b6',
+// scope: 'openid profile'
+// }
+
+// REFRESH
+// {
+// exp: 1777969679,
+// iat: 1777967879,
+// jti: 'cfb68e49-9383-46b7-af35-ad8cd357cd55',
+// iss: 'https://auth.iceberg.black/realms/iceberg',
+// aud: 'https://auth.iceberg.black/realms/iceberg',
+// sub: '68ae60bf-277f-44b6-87a7-46e34072b9f4',
+// typ: 'Refresh',
+// azp: 'chat-website',
+// session_state: '89ddf63b-401b-4bc7-a0e6-30b25a78b1f1',
+// scope: 'openid profile',
+// sid: '89ddf63b-401b-4bc7-a0e6-30b25a78b1f1'
+// }
+
+// ACCESS
+// {
+// exp: 1777968408,
+// iat: 1777968288,
+// auth_time: 1777967879,
+// jti: 'ef46047d-c911-427d-bd41-872c92b0ffd3',
+// iss: 'https://auth.iceberg.black/realms/iceberg',
+// aud: [ 'gitea', 'jellyfin', 'account' ],
+// sub: '68ae60bf-277f-44b6-87a7-46e34072b9f4',
+// typ: 'Bearer',
+// azp: 'chat-website',
+// session_state: '89ddf63b-401b-4bc7-a0e6-30b25a78b1f1',
+// realm_access: {
+// roles: [ 'offline_access', 'uma_authorization', 'default-roles-iceberg' ]
+// },
+// resource_access: {
+// gitea: { roles: [Array] },
+// 'chat-website': { roles: [Array] },
+// jellyfin: { roles: [Array] },
+// account: { roles: [Array] }
+// },
+// scope: 'openid profile',
+// sid: '89ddf63b-401b-4bc7-a0e6-30b25a78b1f1',
+// name: ' ',
+// preferred_username: '',
+// given_name: '',
+// family_name: ''
+// }
+
+// TOKEN OF REFRESH
+// {
+// access_token: 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJublpLek04TkZHVmpWbGFPRXZpMUtFSTVHQWRwaGlsYjh3RHRLeG5JOENZIn0.eyJleHAiOjE3Nzc5MTE1OTEsImlhdCI6MTc3NzkxMTQ3MSwiYXV0aF90aW1lIjoxNzc3OTA5MjAwLCJqdGkiOiJmNTI4ZWEzOC0zOTM0LTQ5ZTEtOGI0ZC1lNjU4OWY5ZTJjZDkiLCJpc3MiOiJodHRwczovL2F1dGguaWNlYmVyZy5ibGFjay9yZWFsbXMvaWNlYmVyZyIsImF1ZCI6WyJnaXRlYSIsImplbGx5ZmluIiwiYWNjb3VudCJdLCJzdWIiOiI2OGFlNjBiZi0yNzdmLTQ0YjYtODdhNy00NmUzNDA3MmI5ZjQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJjaGF0LXdlYnNpdGUiLCJzZXNzaW9uX3N0YXRlIjoiNjhhYThkMTItNGUwZS00N2M2LTg5NWYtMzM4NzA3MTE1MmY0IiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iLCJkZWZhdWx0LXJvbGVzLWljZWJlcmciXX0sInJlc291cmNlX2FjY2VzcyI6eyJnaXRlYSI6eyJyb2xlcyI6WyJ0ZW1wbGF0ZSIsImljZWJlcmciLCJhZG1pbiIsImRlbGV0YW5nIiwidXNlciJdfSwiY2hhdC13ZWJzaXRlIjp7InJvbGVzIjpbImFkbWluIiwidXNlciJdfSwiamVsbHlmaW4iOnsicm9sZXMiOlsiYWRtaW4iLCJ1c2VyIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIG9mZmxpbmVfYWNjZXNzIiwic2lkIjoiNjhhYThkMTItNGUwZS00N2M2LTg5NWYtMzM4NzA3MTE1MmY0IiwibmFtZSI6Ikx1Y2FzIERlbGV0YW5nIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibHVjYXMiLCJnaXZlbl9uYW1lIjoiTHVjYXMiLCJmYW1pbHlfbmFtZSI6IkRlbGV0YW5nIn0.NOol8mmBqxsvKwUFkWDzJE2GxmjU5DsIc-WX9BbNycFJtYcpcKLDeZfNMKlKid-JYnOd8pBt3phl_2OJtkYZPD_GVdq1QFfHkDCf7zEJPEf11lJd7pjT2S-adNzppentz2EJyW-Eb3m2Bqoeq0gaJbMi3gaEzfwgQWFTXxoNi5N7RHGVLoa9xtS4tU0p0IXKUOD4XV2aScSedv0qtTLog2x54ZyXw_mcH6UQNrNYU27Gf1fby_i2fgU-ZyD3CKPMD-kPlq2dwePVxh-z6tDGAOfwNw2gsHgScZMyQ4pO7UIkEs0IuxW-O-96FgRMhttKWE4F3sqcMbeQagw4SKs_Bg',
+// expires_in: 120,
+// refresh_expires_in: 0,
+// refresh_token: 'eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2Nzc0ZGFiYi05NjAxLTQyOTYtYTkwYi0yOWFjYjVlM2U3N2UifQ.eyJpYXQiOjE3Nzc5MTE0NzEsImp0aSI6ImE5MzQ2ZWJhLTk0NTMtNDZmZS1iMzk2LTIxMDFkYjM3YjA3MyIsImlzcyI6Imh0dHBzOi8vYXV0aC5pY2ViZXJnLmJsYWNrL3JlYWxtcy9pY2ViZXJnIiwiYXVkIjoiaHR0cHM6Ly9hdXRoLmljZWJlcmcuYmxhY2svcmVhbG1zL2ljZWJlcmciLCJzdWIiOiI2OGFlNjBiZi0yNzdmLTQ0YjYtODdhNy00NmUzNDA3MmI5ZjQiLCJ0eXAiOiJPZmZsaW5lIiwiYXpwIjoiY2hhdC13ZWJzaXRlIiwic2Vzc2lvbl9zdGF0ZSI6IjY4YWE4ZDEyLTRlMGUtNDdjNi04OTVmLTMzODcwNzExNTJmNCIsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgb2ZmbGluZV9hY2Nlc3MiLCJzaWQiOiI2OGFhOGQxMi00ZTBlLTQ3YzYtODk1Zi0zMzg3MDcxMTUyZjQifQ.zd83fs3rwAdO7KodcGRZoW-COaLISWmSfh3vK6NNQp3zB4mypI09lGieEoTO_XOUWzNgmtt9YmbEgGgXk-uW7w',
+// token_type: 'Bearer',
+// id_token: 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJublpLek04TkZHVmpWbGFPRXZpMUtFSTVHQWRwaGlsYjh3RHRLeG5JOENZIn0.eyJleHAiOjE3Nzc5MTE1OTEsImlhdCI6MTc3NzkxMTQ3MSwiYXV0aF90aW1lIjoxNzc3OTA5MjAwLCJqdGkiOiI3MGEzOTc1MS04NzJjLTRmMzUtODJjNC01OTI5ODFkN2Q2NzgiLCJpc3MiOiJodHRwczovL2F1dGguaWNlYmVyZy5ibGFjay9yZWFsbXMvaWNlYmVyZyIsImF1ZCI6ImNoYXQtd2Vic2l0ZSIsInN1YiI6IjY4YWU2MGJmLTI3N2YtNDRiNi04N2E3LTQ2ZTM0MDcyYjlmNCIsInR5cCI6IklEIiwiYXpwIjoiY2hhdC13ZWJzaXRlIiwic2Vzc2lvbl9zdGF0ZSI6IjY4YWE4ZDEyLTRlMGUtNDdjNi04OTVmLTMzODcwNzExNTJmNCIsImF0X2hhc2giOiI3OGcyYUFsNEdqVXFxS0loRmhDTDNRIiwic2lkIjoiNjhhYThkMTItNGUwZS00N2M2LTg5NWYtMzM4NzA3MTE1MmY0IiwibmFtZSI6Ikx1Y2FzIERlbGV0YW5nIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibHVjYXMiLCJnaXZlbl9uYW1lIjoiTHVjYXMiLCJmYW1pbHlfbmFtZSI6IkRlbGV0YW5nIn0.PdsFWknqvdNKv68SWg9hau9ekFWHdcyA1HRJvwS55H0-ijUlMR2iAhjQYqb44aboko90wtIF1LLIsd2JQMY3gTgcU0qAsf5LdPMX_zRrXoACMYR26rTWX64OmgBX9oWv2rSduGIngR8k6xuugXNBXSXiMwYbHeMSJ7H79a2tyhxqjmnuCxtnWfyQg8yJGIC9e92dDimM5gq9hvNbVQXesjWS3pGCnq0EhVw_90sTMOwk_4CUuPy_0ZgVUNJklyJzxYqMibYto_X2Mb2g2JT2gUZAym78URYhLiVg1A99vh9JTx4NC1vHktlIsEGZLySz52FvtVo7P-C3EqdLKMrSKw',
+// 'not-before-policy': 0,
+// session_state: '68aa8d12-4e0e-47c6-895f-3387071152f4',
+// scope: 'openid profile offline_access'
+// }
\ No newline at end of file