Further Customization
This section covers additional configuration options to customize Nidam for your specific requirements, including redirection handling, security features, and branding.
π§ Where Users Go After Login and Logoutβ
When someone logs into your app, they expect to land back where they started - not on some random homepage. Nidam handles this automatically, but you need to tell it which URLs are safe to redirect to.
After Login: Getting Users Back Where They Belongβ
Think of this like a bouncer at a club - they'll only let people go where you've said it's okay.
In your BFF configuration, you'll see:
login:
success-redirect-param-name: post_login_success_uri
session-redirect-uri-attribute: POST_LOGIN_SUCCESS_URI
allowed-redirect-uri-prefixes:
- http://localhost:7080/react-ui
The allowed-redirect-uri-prefixes
is your safety list. Any URL that starts with http://localhost:7080/react-ui
gets the green light:
- β
http://localhost:7080/react-ui/dashboard
- Good to go - β
http://localhost:7080/react-ui/profile/settings?tab=security
- Also fine - β
https://evil-site.com/steal-data
- Blocked
SPA Implementation:
Your login Button logic should capture the current location and pass it to the BFF.
Nidam SPA sends the URL to the BFF via the post_login_success_uri
query parameter:
import React from "react";
import Link from "@mui/material/Link";
import { useLocation } from "react-router-dom";
const Login = () => {
const location = useLocation();
const handleLogin = (event) => {
event.preventDefault();
const currentPath = location.pathname + location.search + location.hash;
const loginUrl = new URL("http://localhost:7080/bff/oauth2/authorization/token-generator");
loginUrl.searchParams.append(
"post_login_success_uri",
`${process.env.REACT_APP_BASE_URI}${currentPath}`
);
window.location.href = loginUrl.toString();
}
return (
<Link onClick={handleLogin} style={{cursor: "pointer"}}>
Already have an account? Sign in
</Link>
);
}
export default Login;
After Logoutβ
Logout redirection follows a similar pattern but uses a header-based approach instead of query parameters.
SPA Implementation:
const Logout = () => {
const location = useLocation();
const [disabled, setDisabled] = useState(false);
const handleLogout = async () => {
setDisabled(true);
const currentPath = location.pathname + location.search + location.hash;
const response = await axios.post("/bff/logout", {}, {
headers: {
"X-POST-LOGOUT-SUCCESS-URI": process.env.REACT_APP_BASE_URI + currentPath
}
});
window.location.href = response.headers["location"];
setDisabled(false);
};
return (
<Button variant="contained" disabled={disabled} onClick={handleLogout}>
Logout
</Button>
);
}
BFF Configuration:
logout:
# ...
success-redirect-default-uri: http://localhost:7080/react-ui
allowed-redirect-uri-prefixes:
- http://localhost:7080/react-ui
Key configuration options:
success-redirect-default-uri
: Fallback URL when the SPA-provided URL is not allowedallowed-redirect-uri-prefixes
: List of allowed URL prefixes for logout redirection
π‘οΈ Google reCAPTCHAβ
Nidam includes Google reCAPTCHA support to protect your registration endpoint from automated abuse.
Available Endpointsβ
The registration service provides two endpoints:
/registerCaptcha
: Includes reCAPTCHA validation (recommended)/register
: No reCAPTCHA protection (development only)
Backend Configurationβ
Add your Google reCAPTCHA secret key to the registration service configuration:
custom:
recaptcha:
secret: "your-secret-key-here"
Frontend Implementationβ
In the Nidam React SPA signup page (src/container/SignUp.js
), reCAPTCHA is implemented using the following steps.
Adapt these instructions to suit your SPA framework.
-
Load the reCAPTCHA script:
useEffect(() => {
// Load reCAPTCHA script
const script = document.createElement('script');
script.src = 'https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY_HERE';
script.className = "external-script";
script.async = true;
script.defer = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, []);Replace
YOUR_SITE_KEY_HERE
with your actual reCAPTCHA site key. -
Handle form submission with reCAPTCHA validation:
const executeReCaptcha = (e) => {
e.preventDefault();
window.grecaptcha.ready(function() {
window.grecaptcha.execute('YOUR_SITE_KEY_HERE', {action: 'submit'})
.then(token => register(token));
});
}The
register()
function should validate inputs and call the/registerCaptcha
endpoint with the reCAPTCHA token as recaptchaKey.ReminderNidam Registration Service expects this JSON format:
Without Google reCAPTCHA:
POST "/register"
const user = { email: '', password: ''}
With Google reCAPTCHA:
POST "/registerCaptcha"
const user = { email: '', password: '', recaptchaKey: '' }
ποΈ Login Page Brandingβ
The login page is served by the authorization server, not your SPA. You can customize it using HTML, CSS, and JavaScript without requiring Java development.
File Structureβ
The login template is located at src/main/resources/templates/login.html
in your auth server.
Put your assets in src/main/resources/static/
:
static/
βββ css/
β βββ your-styles.css
βββ images/
β βββ your-logo.png
βββ js/
β βββ your-scripts.js
βββ fonts/
βββ your-fonts.woff2
Namespaceβ
Add this namespace to your HTML element:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
Asset Referencingβ
CSS Files:
<!-- In head section -->
<link th:href="@{/css/styles.css}" rel="stylesheet"/>
<!-- Inline styles -->
<style th:inline="css">
.page-bg {
background-image: url([[@{/media/images/bg-image.png}]]);
}
</style>
Images:
<!-- In head section -->
<link th:href="@{/media/app/favicon.ico}" rel="shortcut icon"/>
<!-- In body -->
<img th:src="@{/media/images/logo.png}" alt="Company Logo">
JavaScript Files:
<script th:src="@{/js/custom.js}"></script>
<!--inline scripts-->
<script>...</script>
Required Form Elementsβ
For Nidam to recognize your form, you need these exact attributes:
<form th:action="@{/login}" method="post">
<!-- Email field (note: name must be "username") -->
<input type="email" name="username" >
<!-- Password field -->
<input type="password" name="password" >
<!-- Submit button -->
<button type="submit">Sign In</button>
</form>
Critical Requirements:
- Form must have
th:action="@{/login}"
andmethod="post"
- Email input must have
name="username"
(yes, username, not email) - Password input must use
name="password"
- Submit button must have
type="submit"
π¦ Login Rate Limiterβ
Nidam includes a built-in rate limiter to protect against brute force attacks on the login endpoint.
Configurationβ
rate-limiter:
enabled: true
lockout-duration: 5 # Block for 5 minutes
max-attempts: 5 # After 5 failed attempts
cleanup-interval-ms: 60000 # Cleanup interval in milliseconds
Behaviorβ
- Failed login attempts are tracked per username and IP address combination
- After exceeding
max-attempts
, the user is blocked forlockout-duration
minutes - The cleanup process runs every
cleanup-interval-ms
milliseconds to remove expired records - Set
enabled: false
to disable rate limiting entirely
The rate limiter uses the combination username + "|" + IP
as the unique identifier for tracking attempts.
π οΈ Technicalβ
Why doesnβt automatic token refresh work during logout?β
OAuth 2.0 and Spring Authorization Server (the foundation of the token generator service) support the Refresh Token Grant Type. This allows Nidam, via the BFF, to transparently request a new token when the current one expires. The process runs in the background β no code changes are needed, and users donβt have to re-enter their credentials.
In normal use, this works exactly as expected. However, a problem arises when a user attempts to log out after a token has been refreshed: the Authorization Server rejects the logout request and throws an exception.
I spent over 3 days debugging this and concluded itβs a bug in Spring Authorization Server. I submitted an issue here: https://github.com/spring-projects/spring-authorization-server/issues/2136
The maintainers responded that their JUnit tests pass and requested a minimal reproduction, which I havenβt provided yet. If you discover the root cause in this or another Nidam repository, please let me know (my email is in the footer) or submit an issue. Iβd be happy to credit you in the documentation.
I wanted this feature to ship in this release, but itβs currently blocked by this bug. If you can confirm the issue lies in Spring Authorization Server, please comment on the GitHub issue above. Thanks!