Skip to content

Commit

Permalink
veda auth react app
Browse files Browse the repository at this point in the history
  • Loading branch information
ShivangMishra authored and lahirujayathilake committed Jun 10, 2024
1 parent 1355306 commit e08499f
Show file tree
Hide file tree
Showing 17 changed files with 3,449 additions and 0 deletions.
21 changes: 21 additions & 0 deletions veda-app-samples/veda-react-app/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.2' } },
plugins: ['react-refresh'],
rules: {
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
24 changes: 24 additions & 0 deletions veda-app-samples/veda-react-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
20 changes: 20 additions & 0 deletions veda-app-samples/veda-react-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Veda React App

This project provides a minimal setup to integrate veda auth in a React app.

### Steps to run the project
After cloning the project, you need to update the config in `auth.js` file with your Veda app's client id and client secret.

**Note**: The client secret must never be exposed in the frontend code in a real-world scenario.

<br>After the config is updated, you can run the following commands to start the project:
```
yarn install
yarn run dev
```

* After running the above commands, go to `http://localhost:5173` or `http://localhost:5173/login` to see the login page.
* Click on the Institution Login button to start the login flow.
* You'll be redirected to the Veda login page.
* After successful login, you'll be redirected back to the app on the user-info page.

13 changes: 13 additions & 0 deletions veda-app-samples/veda-react-app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Veda Auth React Demo</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
32 changes: 32 additions & 0 deletions veda-app-samples/veda-react-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "veda-auth-demo",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.7.2",
"bootstrap": "^5.3.3",
"buffer": "^6.0.3",
"dotenv": "^16.4.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.23.1",
"vite-plugin-node-polyfills": "^0.22.0"
},
"devDependencies": {
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"vite": "^5.2.0"
}
}
1 change: 1 addition & 0 deletions veda-app-samples/veda-react-app/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions veda-app-samples/veda-react-app/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
18 changes: 18 additions & 0 deletions veda-app-samples/veda-react-app/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import './App.css';
import {Route, BrowserRouter, Routes} from "react-router-dom";
import CallbackPage from "./pages/callback/CallbackPage.jsx";
import LoginPage from "./pages/login/LoginPage.jsx";
import UserInfoPage from "./pages/userInfo/UserInfoPage.jsx";

function App() {
return (<BrowserRouter>
<Routes>
<Route path="/" element={<LoginPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/callback" element={<CallbackPage />} />
<Route path="/user-info" element={<UserInfoPage />} />
</Routes>
</BrowserRouter>);
}

export default App
91 changes: 91 additions & 0 deletions veda-app-samples/veda-react-app/src/api/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import axios from "axios";
import { Buffer } from 'buffer/';

/*This config object is just for this demo project.
In a real-world scenario, you should store client id and client secret securely as per React best practices.
Client secret must never be stored on the frontend.
The base url and redirect uri should be loaded from .env file.*/
const config = {
clientId: "_your_client_id_",
clientSecret: "_your_client_secret_",

vedaAuthBaseUrl: "http://localhost:8081/api/v1",
redirectUri: "http://localhost:5173/callback/",
}

const axiosInstance = axios.create({
baseURL: config.vedaAuthBaseUrl,
withCredentials: false,
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
}
});

const getClientSecret = async (clientId) => {
const {data: {custos_client_secret}} = await axiosInstance.get(
`/identity-management/credentials`,
{
headers: {
'Authorization': `Bearer ${sessionStorage.getItem('access_token')}`
},
params: {
'client_id': clientId
}
}
);
return custos_client_secret;
}

const getClientAuthBase64 = async (clientId = null, clientSec = null) => {
if (clientId === null && clientSec === null) {
clientId = config.clientId;
clientSec = config.clientSecret;
} else if (clientId !== null && clientSec === null) {
clientSec = await getClientSecret(clientId);
}

let clientAuthBase64 = `${clientId}:${clientSec}`;
clientAuthBase64 = Buffer.from(clientAuthBase64).toString('base64');
clientAuthBase64 = `Bearer ${clientAuthBase64}`
return clientAuthBase64;
}

const fetchAuthorizationEndpoint = async () => {
const openIdConfigEndpoint = "/identity-management/.well-known/openid-configuration";
const redirectUri = config.redirectUri;
const {data: {authorization_endpoint}} = await axiosInstance.get(openIdConfigEndpoint,
{params: {'client_id': config.clientId,}});
window.location.href = `${authorization_endpoint}?response_type=code&client_id=${config.clientId}&redirect_uri=${redirectUri}&scope=openid&kc_idp_hint=oidc`;
}

const fetchToken = async ({code}) => {
const clientAuthBase64 = await getClientAuthBase64();

const {data} = await axiosInstance.post("/identity-management/token", {
code: code,
redirect_uri: config.redirectUri,
grant_type: 'authorization_code'
}, {
headers: {
'Authorization': clientAuthBase64
}
});
return data;
}

const fetchUserInfo = async () => {
const clientAuthBase64 = await getClientAuthBase64();
const {data} = await axiosInstance.get("/user-management/userinfo", {
params: {
'access_token': sessionStorage.getItem('access_token')
},
headers: {
'Authorization': clientAuthBase64
}
});
return data;
}

export {fetchAuthorizationEndpoint, fetchToken, fetchUserInfo}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions veda-app-samples/veda-react-app/src/main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import 'bootstrap/dist/css/bootstrap.min.css';

ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {fetchToken} from "../../api/auth.js";
import {useEffect} from "react";
import {useNavigate} from "react-router-dom";

const CallbackPage = () => {
const navigate = useNavigate();
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
console.log("CODE: " + code);
if (code !== null && code !== "") {
fetchToken({code}).then((tokenResponse) => {
console.log("TOKEN DATA: ", JSON.stringify(tokenResponse));
sessionStorage.setItem("access_token", tokenResponse.access_token);
navigate('/user-info');
});
}
}, [navigate]);

return (
<div>
<h1>Redirecting</h1>
</div>
);
}

export default CallbackPage;
13 changes: 13 additions & 0 deletions veda-app-samples/veda-react-app/src/pages/login/LoginPage.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
h2 {
font-size: 35px;
font-weight: 900;
color: #203a43;
text-align: center;
}

.h2-sub {
font-size: 22px;
color: #203a43;
text-align: center;
}

31 changes: 31 additions & 0 deletions veda-app-samples/veda-react-app/src/pages/login/LoginPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {useEffect} from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import './LoginPage.css';
import custos_home from '../../assets/custos_home.png';
import {fetchAuthorizationEndpoint} from "../../api/auth.js";

function LoginPage() {
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
for(let key of urlParams.keys()) {
console.log(key, urlParams.get(key));
}
}, []);

return (
<div className="container">
<div className="row align-items-start justify-content-center">
<div className="col justify-content-center align-items-center">
<h2>Welcome to VEDA Auth Central</h2>
<p className="h2-sub">Sign up and start authenticating</p>
<img style={{width: "60%"}} src={custos_home} alt="Custos Home"/>
<div className="p-2 text-center">
<button className="btn btn-primary mt-3" onClick={fetchAuthorizationEndpoint}>Institution Login</button>
</div>
</div>
</div>
</div>
);
}

export default LoginPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {useEffect, useState} from "react";
import {fetchUserInfo} from "../../api/auth.js";

const UserInfoPage = () => {
const [userInfo, setUserInfo] = useState(null);

useEffect(() => {
fetchUserInfo().then((userInfo) => {setUserInfo(userInfo)});
}, []);
return (
<div>
<h1>Logged in User Info</h1>
<pre>{JSON.stringify(userInfo, null, 2)}</pre>
</div>);
};

export default UserInfoPage;
8 changes: 8 additions & 0 deletions veda-app-samples/veda-react-app/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import {nodePolyfills} from "vite-plugin-node-polyfills";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), nodePolyfills()],
})
Loading

0 comments on commit e08499f

Please sign in to comment.