I use Spring Boot at backend and ionic 6.20.4 and Angular 12.x.x at frontend. At the backend I have the following rest service to authenticate users:
@CrossOrigin(origins = { "*" }, allowedHeaders = { "Content-Type", "x-auth-token" })
@RestController
public class AuthRestService extends AbstractRestService {
...
@RequestMapping(value = "/login", method = { RequestMethod.POST }, consumes = "application/json;charset=utf-8")
public UserTransferObject login(final @RequestBody LoginData loginData, final HttpServletRequest request) throws BusinessException {
...
and the following XAuthTokenFilter:
@Component
@EnableConfigurationProperties
@ConfigurationProperties(locations = "classpath:application.properties", ignoreUnknownFields = true, prefix = "myconfigfile.login")
public class XAuthTokenFilter extends GenericFilterBean {
protected final Logger logger = LoggerFactory.getLogger(getClass());
private UserDetailsService detailsService;
private final TokenUtils tokenUtils = new TokenUtils();
private String xAuthTokenHeaderName; // configured in application.properties
public XAuthTokenFilter() {
}
public UserDetailsService getDetailsService() {
return detailsService;
}
public void setDetailsService(final UserDetailsService detailsService) {
this.detailsService = detailsService;
}
@ResponseStatus(HttpStatus.OK)
@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain)
throws IOException, ServletException {
try {
final HttpServletRequest httpServletRequest = (HttpServletRequest)request;
final String authToken = httpServletRequest.getHeader(this.xAuthTokenHeaderName);
logger.debug("filter request for token '{}' in XAuthTokenFilter#doFilter", authToken);
if (StringUtils.hasText(authToken)) {
final String username = this.tokenUtils.getUserNameFromToken(authToken);
final UserDetails details = this.detailsService.loadUserByUsername(username);
final boolean isTokenValid = this.tokenUtils.validateToken(authToken, details);
logger.debug("token is valid = '{}' in XAuthTokenFilter#doFilter for user with user name '{}'", isTokenValid, username);
if (isTokenValid) {
final UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(details,
details.getPassword(), details.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(token);
} else {
logger.info("token '{}' is not valid for user '{}' in AuthTokenFilter", authToken, username);
// throw new Exception("token is not valid in XAuthTokenFilter#doFilter");
}
}
final HttpServletResponse resp = (HttpServletResponse)response;
resp.setHeader("Access-Control-Allow-Origin", "*");
resp.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, DELETE, PATCH, OPTIONS");
resp.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, x-auth-token");
resp.setHeader("Access-Control-Max-Age", "3600");
if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest)request).getMethod())) {
resp.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(request, response);
}
} catch (Exception ex) {
// @ExceptionHandler is not available at filter-therefore own json-response is created and sent to client
((HttpServletResponse)response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
final ErrorAdviceHolder errorAdviceHolder = new ErrorAdviceHolder();
errorAdviceHolder.setTimestamp(new Timestamp(System.currentTimeMillis()));
errorAdviceHolder.setError(ex.getStackTrace()[0].toString());
errorAdviceHolder.setMessage(ex.getMessage());
errorAdviceHolder.setPath("XAuthTokenFilter#doFilter");
errorAdviceHolder.setStatus("Error");
errorAdviceHolder.setException(ex.getClass().getName());
final ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
final String errorAdviceHolderJson = ow.writeValueAsString(errorAdviceHolder);
((HttpServletResponse)response).getWriter().write(errorAdviceHolderJson);
((HttpServletResponse)response).getWriter().flush();
((HttpServletResponse)response).getWriter().close();
}
}
public String getxAuthTokenHeaderName() {
return xAuthTokenHeaderName;
}
public void setxAuthTokenHeaderName(final String xAuthTokenHeaderName) {
this.xAuthTokenHeaderName = xAuthTokenHeaderName;
}
@Override
public String toString() {
return "[XAuthTokenFilter: xAuthTokenHeaderName: " + xAuthTokenHeaderName + "]";
}
}
In frontend I use the following environment.ts file:
export const environment = {
production: true,
envName: 'prod',
apiEndpoint: 'https://app.mydomain.com/api/'
};
and the following methods to invoke the backend login method:
this.authService.login(this.ngForm.value).subscribe(response => {
// ...
}, error => {
this.errorMessage = error;
// ...
});
// authService
login(loginData: LoginData):Observable<any> {
const url = environment.apiEndpoint + 'login';
return this.httpClient.post(url, loginData);
}
The app-http-interceptor looks like this:
@Injectable()
export class AppHttpInterceptor implements HttpInterceptor {
constructor(protected authService: AuthService, private router: Router) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return from(this.authService.getToken()).pipe(
switchMap((token) => {
const tokenString: string = token as string;
if(token) {
let authReq = req.clone({
setHeaders: {
'x-auth-token': tokenString,
'Content-Type': 'application/json'
}
});
return next.handle(authReq);
} else {
return next.handle(req);
}
})
);
}
}
Now if I start the frontend either with ng serve or ionic serve I can login at backend without any problems - all works fine.
But if I deploy the app on an android device with ionic capacitor run android, deployment works also fine but if I try to login than I get the following error:
errorMessage : { { "headers": {"normalizedNames": {}, "lazyUpdate": null, "headers": {}},
"status": 0, "statusText": "Unknown Error", "url": "https://app.mydomain.com/api/login", "ok":
false, "name": "HttpErrorResponse", "message": "Http failure response for
https://app.mydomain.com/api/login: 0 Unknown Error", "error": {"isTrusted": true} }
Does anyone have any idea what I am doing wrong in order to get this error. Thanks for any hint! I’m still a little bit desparate.