We are facing some challenges while integrating frontend and backend server with cross domain.
Kindly have a look and assist us to resolve this issue.
Frontend (Reactjs UI.COM)
Backend (Spring Boot Okat Saml integrated Api.com) integrated with Okta SAMl Authentication
Spring security to saml authentication
Create getUser https://api.com/user Api that return user details
Created https://api.com/private controller to handle redirect
Approach##A
Frontend call fetch api Frontend/user and check if user details is present or not (Note: first user details will not present So it'll execute below steps)
2)Frontend will invoke redirect backend/private and come to backend server 3) then backend server will redirect to okta and get Saml Response 4)Now Backend server Spring Security will parse saml response and start session(i.e “BackendSession”) and prepare SamlUserDetails 5)Now time to redirect back to originater/frontend So Backend server will call redirect frontend and go back to frontend/UI and drop “BacknedSessionId” in browser cookie. (Note: Here cookie will drop aginst backend domain only) Problem Statement: Here frontend unable to read "BackendendSessionId" which is drop aginst backend 6) Further task: need to call backend/user api to get user details and this api expecting credential i.e SamlUserDetails (i.e inside "BackendendSessionId")
Approach##B:
1 to 4 same as above .
5)Now time to redirect back to originater/frontend So Backend server will call redirect frontend and also append "BackendendSessionId" into uri/query param and go back to frontend/UI and also drop “BacknedSessionId” in browser cookie. (Note: Here cookie will drop aginst backend domain only)
6)Now frontend get "BackendendSessionId" through uri/query param
7) Now frontend call backend/user api and pass "BackendendSessionId" into request header
Problem Statement: Here Backend/user api expecting credential i.e SamlUserDetails
Note: Client is not interested in cookies at all. So we have eliminated dropping cookies approach
Code reference
https://api.com/private controller to handle redirect
@GetMapping("/private")
public ModelAndView index(@SAMLUser SAMLUserDetails user, HttpServletRequest request, HttpServletResponse response){
Map<String, String> userAttributes = user.getAttributes();
log.info("userAttributes {}", userAttributes);
log.info("saml user {}", user);
log.info("saml userName {}", user.getUsername());
String location = request.getHeader("referer");
if (isBlank(location)){
location=srtUiUrl;
}
StringBuilder sb = new StringBuilder();
sb.append(location);
sb.append("?stateId=");
sb.append(request.getSession().getId());
log.info("RedirectUrl ",sb.toString());
return new ModelAndView("redirect:" + sb.toString());
getUser https://api.com/user api return user
@GetMapping("/receive/v1/user")
public ResponseEntity<User> getUser(@SAMLUser SAMLUserDetails user) {
log.info("SAMLUserDetails : ", user);
if (null==user) {
log.info("No user found Authentication required");
return new ResponseEntity<>(null, HttpStatus.OK);
} else {
Map<String, String> userAttributes = user.getAttributes();
log.info("SAMLUserDetails Attributes {}", userAttributes);
User userModel=new User();
userModel.setId(user.getUsername());
userModel.setFirstName(userAttributes.get(SrtConstant.FIRST_NAME));
userModel.setLastName(userAttributes.get(SrtConstant.LAST_NAME));
userModel.setEmail(userAttributes.get(SrtConstant.USER_EMAIL));
userModel.setShortName(userAttributes.get(SrtConstant.STORE_NAME));
userModel.setGroupName(userAttributes.get(SrtConstant.GROUPS));
userModel.setStoreId(userAttributes.get(SrtConstant.STORE_ID));
userModel.setStoreName(userAttributes.get(SrtConstant.STORE_NAME));
log.info("userModel :", userModel);
return ResponseEntity.ok().body(userModel);
}
}
Spring security to saml authentication
@Override protected void configure(final HttpSecurity http) throws Exception { log.info("configure : "+"spHost :"+ spHost +" ,KeystoreFilePath : "+keyStoreFile +" ,KeyStoreAlias : "+keyStoreAlias+" ,metadatpath : "+metadataPath); http .csrf().disable() .authorizeRequests() .antMatchers("/saml*").permitAll() .antMatchers(HttpMethod.GET, "/receive/").permitAll() .antMatchers(HttpMethod.POST, "/receive/").permitAll() .antMatchers(HttpMethod.PATCH, "/receive/").permitAll() .antMatchers(HttpMethod.DELETE, "/receive/").permitAll() .antMatchers(HttpMethod.PUT, "/user").permitAll() .anyRequest().authenticated() .and() .apply(saml()) .userDetailsService(samlUserDetailsService) .serviceProvider() .protocol("https") .hostname(spHost) .basePath("/") .keyStore() .storeFilePath(keyStoreFile) .keyPassword(keyStorePassword) .keyname(keyStoreAlias) .and() .and() .identityProvider() .metadataFilePath(metadataPath); }
Cors filter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
final HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", srtUiUrl);
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) req).getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
Same-site=none setting
server: port: 8443 servlet: session: cookie: same-site: none secure: true