diff --git a/README.md b/README.md index 5e5c5f4..dec0392 100644 --- a/README.md +++ b/README.md @@ -111,3 +111,25 @@ yarn start ``` Your application will then run on `http://localhost:3000` + +## Enable Offline Access (Refresh Tokens) + +To retrieve a refresh token for offline access, follow these steps: + +1. Enable refresh tokens for your application: + - Navigate to your [application settings](https://zitadel.com/docs/guides/manage/console/applications#application-settings) and enable the **Refresh Token** checkbox. + +2. Add the `offline_access` scope: + +```js +const config: ZitadelConfig = { + authority: "https://CUSTOM_DOMAIN", + client_id: "YOUR_CLIENT_ID", + redirect_uri: "http://localhost:3000/callback", + post_logout_redirect_uri: "http://localhost:3000", + response_type: 'code', + scope: 'openid profile email offline_access' +}; +``` + +3. After logging out and logging back in, you will see the refresh token container displaying the refresh token. diff --git a/src/App.tsx b/src/App.tsx index 4cb3e43..cc2e73f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import "./App.css"; import { createZitadelAuth, ZitadelConfig } from "@zitadel/react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; -import Navbar from "./components/Navbar" +import Navbar from "./components/Navbar"; import Login from "./components/Login"; import Callback from "./components/Callback"; @@ -32,6 +32,7 @@ function App() { const [authenticated, setAuthenticated] = useState(null); const [accessToken, setAccessToken] = useState(null); const [idToken, setIdToken] = useState(null); + const [refreshToken, setRefreshToken] = useState(null); useEffect(() => { zitadel.userManager.getUser().then((user) => { @@ -39,10 +40,12 @@ function App() { setAuthenticated(true); setAccessToken(user.access_token ?? null); setIdToken(user.id_token ?? null); + setRefreshToken(user.refresh_token ?? null); } else { setAuthenticated(false); setAccessToken(null); setIdToken(null); + setRefreshToken(null); } }); }, [zitadel]); @@ -53,7 +56,7 @@ function App() { - + { +const JWTContainer = ({ accessToken, idToken, refreshToken }: Props) => { const decodedAccessToken = accessToken ? JSON.stringify(decodeJWT(accessToken), null, 2) : null; const decodedIdToken = idToken ? JSON.stringify(decodeJWT(idToken), null, 2) : null; const [copyState, setCopyState] = useState<{ [key: string]: boolean }>({}); - const handleCopy = (target: "accessToken" | "idToken", text: string) => { + + const handleCopy = (target: "accessToken" | "idToken" | "refreshToken", text: string) => { navigator.clipboard.writeText(text).then(() => { setCopyState((prev) => ({ ...prev, [target]: true })); setTimeout(() => { @@ -48,8 +41,7 @@ const JWTContainer = ({ accessToken, idToken }: Props) => { Access Token {accessToken && ( handleCopy("accessToken", accessToken)} > @@ -59,15 +51,31 @@ const JWTContainer = ({ accessToken, idToken }: Props) => {
{decodedAccessToken || "No token generated yet."}
+ + {refreshToken && ( +
+
+ Refresh Token + handleCopy("refreshToken", refreshToken)} + > +
+
+
{refreshToken}
+
+
+ )} +
ID Token {idToken && ( handleCopy("idToken", idToken)} > @@ -82,4 +90,4 @@ const JWTContainer = ({ accessToken, idToken }: Props) => { ); }; -export default JWTContainer; \ No newline at end of file +export default JWTContainer;