Skip to content

Commit aa257b1

Browse files
authored
refactor frontend for OIDC based auth (#51)
* refactor frontend for OIDC based auth auth context is now the interface between the UI and vendors both Kratos and OIDC will implement fetchAuthState/login/logout so the vendor logic can live decoupled from UI logic * fix logo 404 when in subpath
1 parent 9de5812 commit aa257b1

File tree

13 files changed

+207
-109
lines changed

13 files changed

+207
-109
lines changed

templates/src/App.js

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import React from 'react'
22
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
33

44
import Home from './pages/Home'
5-
<%if eq (index .Params `userAuth`) "yes" %>
6-
import Auth from './pages/Auth'
7-
import Logout from './pages/Logout'<% end %>
5+
86
<%if eq (index .Params `billingEnabled`) "yes" %>
97
import BillingRoutes from './pages/Billing/routes'
108
<% end %>
@@ -13,33 +11,9 @@ import PageNotFound from './pages/PageNotFound'
1311

1412
import './App.css'
1513
import Navigation from './components/Navigation'
16-
<%if eq (index .Params `userAuth`) "yes" %>
14+
<%- if eq (index .Params `userAuth`) "yes" %>
15+
import { AuthRoutes } from './routes/Auth'
1716
import { AuthProvider } from './context/AuthContext'
18-
19-
function AuthRoutes () {
20-
return (
21-
<Switch>
22-
<Route path="/auth/login">
23-
<Auth page="login" title="Login" key="login" />
24-
</Route>
25-
<Route path="/auth/registration">
26-
<Auth page="registration" title="Regsiter" key="registration" />
27-
</Route>
28-
<Route path="/auth/profile">
29-
<Auth page="settings" title="profile" key="profile" />
30-
</Route>
31-
<Route path="/auth/recovery">
32-
<Auth page="recovery" title="Recovery" key="recovery" />
33-
</Route>
34-
<Route path="/auth/settings">
35-
<Auth page="settings" title="Settings" key="settings" />
36-
</Route>
37-
<Route path="/auth/logout">
38-
<Logout />
39-
</Route>
40-
</Switch>
41-
)
42-
}
4317
<% end %>
4418
function App() {
4519
return (

templates/src/api/kratos.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ const generateLogoutUrl = async () => {
3131
return authPublicURL + url;
3232
}
3333

34+
const logout = async() => {
35+
window.location = generateLogoutUrl();
36+
}
37+
38+
const login = async() => {
39+
window.location = `${config.authBackendURL}/auth`;
40+
}
41+
3442
const generateFormRequestUrl = async (type) => {
3543
let { url } = await getBrowserFlowParams(capitalize(type));
3644
// Workaround for bug in SDK specs: https://github.com/ory/sdk/issues/43
@@ -71,9 +79,9 @@ const fetchRequestData = async (type = "login", flowId) => {
7179
* given a valid session's cookie, kratos will respond with session information
7280
* that includes whether session is active, checking it then formatting for authContext
7381
*/
74-
const fetchAuthState = async() => {
82+
const fetchAuthState = async(dispatchSetAuth) => {
7583
const uri = await generateSessionUrl();
76-
const options = {credentials: "include"};
84+
const options = { credentials: "include" };
7785

7886
const response = await fetch(uri, options);
7987
const authResult = await response.json();
@@ -94,11 +102,11 @@ const fetchAuthState = async() => {
94102
}
95103
} */
96104
const isLoggedIn = !!authResult.active
97-
return {
105+
dispatchSetAuth({
98106
isAuthenticated: isLoggedIn,
99107
user: authResult.identity?.id,
100108
email: authResult.identity?.traits?.email
101-
};
109+
});
102110
};
103111

104-
export { fetchRequestData, fetchAuthState, generateFormRequestUrl, generateLogoutUrl }
112+
export { fetchRequestData, fetchAuthState, generateFormRequestUrl, generateLogoutUrl, logout, login }
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import config from '../config';
2+
3+
const fetchAuthState = async(dispatchSetAuth) => {
4+
const uri = `${config.authBackendURL}/whoami`
5+
const resp = await fetch(uri,{
6+
credentials: "include",
7+
});
8+
const { isAuthorized, context } = await resp.json();
9+
dispatchSetAuth({
10+
isLoading: false,
11+
isAuthenticated: isAuthorized,
12+
user: context?.name,
13+
email: context?.email,
14+
});
15+
}
16+
17+
const login = async() => {
18+
window.location = `${config.authBackendURL}/login`;
19+
}
20+
21+
const logout = async() => {
22+
window.location = `${config.authBackendURL}/logout`;
23+
}
24+
export { fetchAuthState, login, logout };

templates/src/components/Navigation.js

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,13 @@
11
import React<%if eq (index .Params `userAuth`) "yes" %>, { useContext }<% end %> from 'react'
22
import { Link } from 'react-router-dom'
3-
<%if eq (index .Params `userAuth`) "yes" %> import { AuthContext } from '../context/AuthContext' <% end %>
3+
<%- if eq (index .Params `userAuth`) "yes" %>
4+
import { AuthContext } from '../context/AuthContext'
5+
<% end %>
6+
import { AuthenticatedLinks, UnauthenticatedLinks } from '../routes/Auth'
47

58
import './Navigation.css'
6-
<%if eq (index .Params `userAuth`) "yes" %>
7-
function AuthenticatedLinks() {
8-
return (
9-
<React.Fragment>
10-
<li>
11-
<Link to="/dashboard">Dashboard</Link>
12-
</li>
13-
<li>
14-
<Link to="/auth/profile">User Settings</Link>
15-
</li>
16-
<%if eq (index .Params `billingEnabled`) "yes" %><li>
17-
<Link to="/billing/products">Billing</Link>
18-
</li>
19-
<% end %><li>
20-
<Link to="/auth/logout">Logout</Link>
21-
</li>
22-
</React.Fragment>
23-
)
24-
}
25-
26-
function UnauthenticatedLinks() {
27-
return (
28-
<React.Fragment>
29-
<li>
30-
<Link to="/auth/registration">Sign Up</Link>
31-
</li>
32-
<li>
33-
<Link to="/auth/login">Login</Link>
34-
</li>
35-
</React.Fragment>
36-
)
37-
}
389

10+
<%- if eq (index .Params `userAuth`) "yes" %>
3911
function NavLinks({ isAuthenticated, isLoading }) {
4012
return <React.Fragment>
4113
{isLoading ? (
@@ -72,7 +44,7 @@ function Navigation() {
7244
return (
7345
<nav className="app-nav">
7446
<Link to="/" className="app-nav-logo">
75-
<img src="./logo192.png" alt="logo" />
47+
<img src="/logo192.png" alt="logo" />
7648
</Link>
7749
<%if eq (index .Params `userAuth`) "yes" %>
7850
<NavLinks {...state}/><% else if eq (index .Params `userAuth`) "no" %><NavLinks /><% end %>
Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
{
22
"appName": "<% .Name %>",
3-
"environment": "development",
4-
"backendURL": "http://localhost:8080"
5-
<%- if eq (index .Params `billingEnabled`) "yes" %>,
3+
"backendURL": "http://localhost:8080",
4+
<%- if eq (index .Params `userAuth`) "yes" %>
5+
<%- if eq (index .Params `backendApplicationHosting`) "serverless" %>
6+
"authBackendURL": "https://<% index .Params `stagingHostRoot` %>",
7+
<%- else %>
68
"authBackendURL": "https://dev.<% index .Params `stagingHostRoot` %>",
7-
<% end %>
8-
<%- if eq (index .Params `billingEnabled`) "yes" %>,
9-
"stripePublishableKey": "<% index .Params `stagingStripePublicApiKey` %>"<% end %>
9+
<%- end %>
10+
<%- end %>
11+
<%- if eq (index .Params `billingEnabled`) "yes" %>
12+
"stripePublishableKey": "<% index .Params `stagingStripePublicApiKey` %>",
13+
<%- end %>
14+
"environment": "development"
1015
}

templates/src/context/AuthContext.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import React, { useReducer, useEffect} from 'react'
2-
import { fetchAuthState } from '../api/kratos'
3-
1+
import React, { useReducer, useEffect, /* useContext */} from 'react'
2+
<%- if eq (index .Params `backendApplicationHosting`) "serverless" %>
3+
import { fetchAuthState, login, logout } from '../api/serverless-auth'
4+
<%- else %>
5+
import { fetchAuthState, login, logout } from '../api/kratos'
6+
<% end %>
47
const SET_AUTH = 'set_auth'
58
const LOGOUT_ACTION = 'logout'
69

@@ -46,18 +49,19 @@ function reducer(state, action) {
4649
function AuthProvider({ children }) {
4750
const [state, dispatch] = useReducer(reducer, initialState)
4851
const value = {
49-
setAuth: payload => {
52+
state,
53+
setAuth: (payload) => {
5054
dispatch({type: SET_AUTH , payload })
5155
},
52-
state,
56+
logout,
57+
login,
5358
dispatch,
5459
}
5560

61+
5662
useEffect(() => {
57-
if (state.isLoading === true) {
58-
fetchAuthState().then(value.setAuth);
59-
}
60-
});
63+
fetchAuthState(value.setAuth);
64+
}, [state.isLoading]); // eslint-disable-line
6165
return (
6266
<AuthContext.Provider value={value}>{children}</AuthContext.Provider>
6367
);
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import React, { useState, useEffect } from 'react'
22
import { useLocation } from 'react-router-dom'
3-
import AuthForm from '../components/AuthForm'
4-
import Card from '../components/Card'
5-
import Logo from '../components/Logo'
6-
import { fetchRequestData, generateFormRequestUrl } from '../api/kratos'
3+
import AuthForm from '../../components/AuthForm'
4+
import Card from '../../components/Card'
5+
import Logo from '../../components/Logo'
6+
import { fetchRequestData, generateFormRequestUrl } from '../../api/kratos'
77

8-
import './Auth.css'
8+
import './Form.css'
99

1010
/**
1111
* AuthPage controller, this is the form wrapper to handle the request / error message

templates/src/pages/Auth/Login.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React, {useContext, useEffect} from "react";
2+
import { AuthContext } from '../../context/AuthContext'
3+
4+
const LoginPage = () => {
5+
const { login } = useContext(AuthContext);
6+
useEffect(() => {
7+
login();
8+
}, []); // eslint-disable-line
9+
10+
return <div className="content-container">
11+
If you're not redirected to the login page automatically, click button below <br/>
12+
<button onClick={login}>Login</button>
13+
</div>
14+
}
15+
16+
export default LoginPage;

templates/src/pages/Auth/Logout.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React, {useContext, useEffect} from "react";
2+
3+
import { AuthContext } from '../../context/AuthContext'
4+
5+
const Logout = () => {
6+
const {logout} = useContext(AuthContext);
7+
useEffect(() => {
8+
logout();
9+
}, []); // eslint-disable-line
10+
11+
return <div className="content-container">
12+
If you're not logged out automatically, click button below <br/>
13+
<button onClick={() => logout()}>Logout</button>
14+
</div>
15+
};
16+
17+
export default Logout;

0 commit comments

Comments
 (0)