Skip to main content

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 allowed
  • allowed-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.

  1. 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.

  2. 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.

    Reminder

    Nidam 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:

  1. Form must have th:action="@{/login}" and method="post"
  2. Email input must have name="username" (yes, username, not email)
  3. Password input must use name="password"
  4. 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 for lockout-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!