I store my session in database, I have columns with user id, UUID and expiration date. Before any request from Angular I would like to send request to the method in api which check expiration date and let angular send another request if is valid or logout user and remove local storage with token with message about expiration date of my session. I'm looking for similar solution to HTTPInterceptor which add headers automatically to every request instead of add headers to any method with request before.
I'm using Angular 10 and Spring Boot 2.3.1.
EDIT.
I found the solution for catching errors in any request in my interceptor on the Angular side.
#Injectable()
export class HttpInterceptorService implements HttpInterceptor {
constructor() {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
if (localStorage.getItem('username') && localStorage.getItem('basicauth')) {
req = req.clone({
setHeaders: {
Authorization: localStorage.getItem('basicauth')
}
})
}
return next.handle(req).pipe(
catchError(response => {
console.log(response.status)
// do something, example clear LocalStorage...
return throwError(response);
})
)
}
}
EDIT 2.
I made Interceptor with preHandle() method in Spring-Boot to check session expiration date before request and if session is expired I set unique response status to 495 which tell Angular to logout the user and clear LocalStorage.
preHandle() method in Spring-Boot:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//if (request.getRequestURL().toString().endsWith("/api/basicauth"))
String GUID = request.getHeader("GUID") == null? "1" : request.getHeader("GUID");
if (sessionService.getSessionByGuid(GUID).isPresent()) {
Session session = sessionService.getSessionByGuid(GUID).get();
if(session.getExpirationDate().isBefore(LocalDateTime.now())) {
sessionService.deleteSession(GUID);
response.setStatus(495);
return false;
} else {
sessionService.renewSession(session);
return true;
}
}
return true;
}
Interceptor method in Angular:
intercept(req: HttpRequest<any>, next: HttpHandler) {
if (localStorage.getItem('username') && localStorage.getItem('basicauth')) {
req = req.clone({
setHeaders: {
Authorization: localStorage.getItem('basicauth'),
GUID: localStorage.getItem('GUID')
}
})
}
return next.handle(req).pipe(
catchError(response => {
if (response.status == 495) {
this.auth.removeSessionAndStorage();
this.openSnackBar("Twoja sesja wygasłą!",);
this.router.navigate(['login', 'session-expired']);
}
return throwError(response);
})
);
}
I think your design is a little flawed. It really sounds like you are wanting to do JWT authentication but you're getting confused with how the token is validated before actually following through and executing your request.
In your interceptor, as long as you are adding Authorization: Bearer <token> with every request, you just need to create some auth middleware in your backend. This middleware would check the header of every request, grab the JWT and validate it. If it's valid, it then follows through and executes the request. If it fails, it returns an Unauthorized/Forbid result (up to you on what you want to return).
There's a couple of different ways to set this up, but here's a nice medium article that describes how to do it with Node.js. Some other frameworks, like .NET, make it a lot easier. So, you may want to evaluate the options before going with one.
Example code from article:
let jwt = require('jsonwebtoken');
const config = require('./config.js');
let checkToken = (req, res, next) => {
let token = req.headers['x-access-token'] || req.headers['authorization']; // Express headers are auto converted to lowercase
if (token.startsWith('Bearer ')) {
// Remove Bearer from string
token = token.slice(7, token.length);
}
if (token) {
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
return res.json({
success: false,
message: 'Token is not valid'
});
} else {
req.decoded = decoded;
next();
}
});
} else {
return res.json({
success: false,
message: 'Auth token is not supplied'
});
}
};
module.exports = {
checkToken: checkToken
}
So, essentially, the process you should be going for is made up of a couple of things:
The token is sent as a header with every single request
A middleware exists on your backend to validate the token before any API method execution
https://medium.com/dev-bits/a-guide-for-adding-jwt-token-based-authentication-to-your-single-page-nodejs-applications-c403f7cf04f4
If you stick with Spring Boot, it looks like there is some sample code on how to set this up here
Related
I want to be able to fetch a param from the redirect url whenever it is automated. I am having difficulties doing this as I am getting a bad request after I created another endpoint to effect this.
I have an endpoint that works fine. The endpoint is a get method. Loading the endpoint takes a user to a page where they need to provide some necessary details. Once these details have been verified, the user is redirected to my redirecr_uri. The redirect_uri now contains important information like session_id, code, etc. The most important thing I need is the code. I need to pass the code into yet another endpoint which will return an access token.
I have manually done this process and it works but I want it to be done automatically because I can't keep doing that when I push the code to staging or production.
Here is the endpoint that redirects as well as the method.
#GetMapping("/get-token")
public RedirectView getBvn() throws UnirestException {
return nibss.getAccessToken();
}
This is the method that the controller calls
public RedirectView getAccessToken() throws UnirestException {
String url = "https://idsandbox.nibss-plc.com.ng/oxauth/authorize.htm?scope=profile&acr_values=otp&response" +
"_type=code&redirect_uri=https://www.accionmfb.com/&client_id=0915cd00-67f2-4768-99ac-1b2ff9f1da2e";
RedirectView redirectView = new RedirectView();
redirectView.setUrl(url);
return redirectView;
}
When the user provides the right information they are redirected to something like this
https://www.accionmfb.com/?code=9ad91f13-4698-4030-8a8f-a857e6a9907e&acr_values=otp&scope=profile&session_state=fa525cabc5b62854c73315d0322fd830c12a5941b89fd8e6e518da369e386572.b78a3d21-e98e-4e9a-8d60-afca779d9fad&sid=fd60ab92-ef37-4a5b-99b9-f8f52321985d
It is important to state that this 3rd party API I am trying to consume uses oath2.0 client authentication.
I created this endpoint to get the code from the redirected_uri
#GetMapping("/redirect-url")
public void handleRedirect(#RequestParam("code") String code) throws UnirestException {
if(Objects.nonNull(code) || !code.isEmpty()){
nibss.getToken(code);
log.info("Code is not being passed {}", code);
} else {
log.info("Code is not being passed {}", code);
}
}
public String getToken(String code) throws UnirestException {
log.info("This is the code here oooooooooo {}", code);
String url = "https://idsandbox.nibss-plc.com.ng/oxauth/restv1/token";
String parameters = "client_id=0915cd00-67f2-4768-99ac-1b2ff9f1da2e&code="+code+"&redirect_uri=https://www.accionmfb.com/&grant_type=authorization_code";
HttpResponse<String> apiResponse = Unirest.post(url)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Authorization", "Basic MDkxNWNkMDAtNjdmMi00NzY4LTk5YWMtMWIyZmY5ZjFkYTJlOlRVRnEwcGFBQXRzbzBZOEcxMkl2WFZHUmx6WG5zaERiaGt1dzI1YUM=")
.body(parameters)
.asString();
//JSONObject apiJson = apiResponse.getBody().getObject();
//return apiJson.getString("access_token");
JSONObject json = new JSONObject(apiResponse.getBody());
String accessToken = json.getString("access_token");
log.info(accessToken);
return accessToken;
}
But this is not working, I get 400 whenever I hit the second endpoint. What am I doing wrong?
The redirect_uri that you are passing to the OAuth server is https://www.accionmfb.com which does not include the path /redirect-url so the redirect never hits your method.
Either register and pass a callback uri like redirect_uri=https://www.accionmfb.com/redirect-url
Or change #GetMapping("/redirect-url") to #GetMapping("/")
I have 2 microservices, ProductStore and InvoiceStore.
I want ProductStore to provide product information through an API and InvoiceStore to call that API to get product information from ProductStore.
But ProductStore needs Authorization information to check user authentication so I use #RequestHeader("Authorization") String auth as argument variable in my FeignAPI to send it to ProductStore.
But it reported that he did not receive the Authorization data when i test it.
I use #RequestHeader like that because I see it in the examples of feign-reactive all feature
I don't know if I did something wrong somewhere or I misunderstood the usage of #RequestHeader.
Help me please! Here is my code.
My ProductStore provides API to be able to get product information.
#GetMapping("products")
public ResponseEntity<String> test(#RequestHeader("Authorization") String authorization) {
log.debug("Authorization is {}", authorization);
return ResponseEntity.ok().body("all products");
}
And my InvoiceStore call that API with feign-reactive WebReactiveFeign.
I followed the instructions in the readme of Playtika feign-reactive and applied it to my project as follows
First, I write FeignAPI
#Headers({ "Accept: application/json" })
public interface FeignClientAPI {
#RequestLine("GET /products")
Mono<String> getProducts(#RequestHeader("Authorization") String authorization);
}
And then, I build the client in IvoiceService
#Service
#Transactional
public class InvoiceService {
private final FeignClientAPI client = WebReactiveFeign.<FeignClientAPI>builder().target(FeignClientAPI.class, "http://localhost:8082");
public Mono<String> testFeign(String authorization){
log.debug("Call api with authorization: {}", authorization);
return client.getTest(authorization);
}
}
And then, I create an API
#GetMapping("/invoice/test")
public Mono<ResponseEntity<String>> getProducts(#RequestHeader("Authorization") String authorization) {
return invoiceService.testFeign(authorization)
.switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)))
.map(response -> ResponseEntity.ok().body(response));
}
Finally, I shoot an GET request to localhost:8083/invoice/test and I got an error
{
"title": "Internal Server Error",
"status": 500,
"detail": "[400 Bad Request] during [GET] to [http://localhost:8082/products] [FeignClientAPI#getTest(String)]: [{\n \"title\" : \"Bad Request\",\n \"status\" : 400,\n \"detail\" : \"Required request header 'Authorization' for method parameter type String is not present\",\n \"path\" : \"/products\",\n \"message\" : \"error.http.400\"\n}]",
"path": "/invoice/test",
"message": "error.http.500"
}
Tell me where i did wrong, Please!!!
Thank you for everything.
your code is totally wrong and i think you should get compile time error because testFeign(String authorization) need a string input but when you call it ( invoiceService.testFeign().switchIfEmpty ... ) you are not passing any input to it.
i should check the main code ,but i think you are passing null value as authorization in client side ( probably ).
I found the solution to this problem.
I misinterpreted how to use reactive feign before, which resulted in it not working.
I've added #EnableReactiveFeignClients and #EnableFeignClients for my spring boot app
#EnableReactiveFeignClients
#EnableFeignClients
public class AnswerStoreApp {
// main method
}
and then, I create an interface with #ReactiveFeignClient(name = "my-other-service")
#ReactiveFeignClient(name = "my-other-service")
public interface FeignClientService {
#GetMapping("/api/questions/test-feign")
Mono<String> demo(#RequestHeader("Authorization") String authorize);
// More request
}
finally, I can use FeignClientService to get the data that I need
#Autowired
private FeignClientService feignClientService;
// Some method
#GetMapping("/invoice/test")
public Mono<ResponseEntity<String>> getProducts(#RequestHeader("Authorization") String authorization) {
return feignClientService.testFeign(authorization)
.switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)))
.map(response -> ResponseEntity.ok().body(response));
}
I'm trying to get a boolean parameter from an Angular 6 app and Spring can't handle it. I got the following error :
org.springframework.web.server.UnsupportedMediaTypeStatusException: 415 UNSUPPORTED_MEDIA_TYPE "Content type 'text/plain;charset=UTF-8' not supported for bodyType=java.lang.Boolean"
That's my front end code :
updateConfirmationDate(reportInfo: number, isItTrue: boolean): Observable<Boolean> {
const url = this.url;
return this.httpClient.patch(url, isItTrue).pipe(first(), catchError(err => console.log(err)));
}
And that's how i handle it on back :
Router :
public RouterFunction<ServerResponse> router() {
return RouterFunctions.route()
.path(apiPrefix, builder -> builder
.PATCH("/patchBooleanEndpoint", diagnosticHandler::updateBooleanEndpoint)
)
)
.build();
}
Handle :
#NonNull
public Mono<ServerResponse> updateMyBoolean(ServerRequest request) {
final Long id = Long.parseLong(request.pathVariable("id"));
return request.bodyToMono(Boolean.class)
.flatMap(myBoolean -> service.updateBooleanById(id, myBoolean))
.flatMap(savedBoolean ->
status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(savedBoolean), Boolean.class));
}
Thank you very much and have a nice day
You have to set content-Type to 'application/json' when you call the backend server from your Angular app.
something like
const headers = new HttpHeaders()
.set("Content-Type", "application/json");
And then set it to your patch request.
Based on the stack trace, the fronted call the backend server with content-Type = 'text/plain;charset=UTF-8' so it (the backend server) fails to parse the request body.
I am trying to learn Spring Security right now and I have seen many different examples using this. I know what CSRF is and that Spring Security enables it by default.
The thing that I am curious about to know is this kind of customization.
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.authorizeRequests(request -> {
request
.antMatchers("/login").permitAll()
.anyRequest()
....more code
What kind of customization does .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) this line and when it is appropriate to use it.
I would appreciate it if anyone can come with a simple explanation.
CSRF stands for Cross Site Request Forgery
It is one kind of token that is sent with the request to prevent the attacks. In order to use the Spring Security CSRF protection, we'll first need to make sure we use the proper HTTP methods for anything that modifies the state (PATCH, POST, PUT, and DELETE – not GET).
CSRF protection with Spring CookieCsrfTokenRepository works as follows:
The client makes a GET request to Server (Spring Boot Backend), e.g. request for the main page
Spring sends the response for GET request along with Set-cookie header which contains securely generated XSRF Token
The browser sets the cookie with XSRF Token
While sending a state-changing request (e.g. POST) the client (might be angular) copies the cookie value to the HTTP request header
The request is sent with both header and cookie (browser attaches the cookie automatically)
Spring compares the header and the cookie values, if they are the same the request is accepted, otherwise, 403 is returned to the client
The method withHttpOnlyFalse allows angular to read XSRF cookie. Make sure that Angular makes XHR request with withCreddentials flag set to true.
Code from CookieCsrfTokenRepository
#Override
public CsrfToken generateToken(HttpServletRequest request) {
return new DefaultCsrfToken(this.headerName, this.parameterName,
createNewToken());
}
#Override
public void saveToken(CsrfToken token, HttpServletRequest request,
HttpServletResponse response) {
String tokenValue = token == null ? "" : token.getToken();
Cookie cookie = new Cookie(this.cookieName, tokenValue);
cookie.setSecure(request.isSecure());
if (this.cookiePath != null && !this.cookiePath.isEmpty()) {
cookie.setPath(this.cookiePath);
} else {
cookie.setPath(this.getRequestContext(request));
}
if (token == null) {
cookie.setMaxAge(0);
}
else {
cookie.setMaxAge(-1);
}
cookie.setHttpOnly(cookieHttpOnly);
if (this.cookieDomain != null && !this.cookieDomain.isEmpty()) {
cookie.setDomain(this.cookieDomain);
}
response.addCookie(cookie);
}
#Override
public CsrfToken loadToken(HttpServletRequest request) {
Cookie cookie = WebUtils.getCookie(request, this.cookieName);
if (cookie == null) {
return null;
}
String token = cookie.getValue();
if (!StringUtils.hasLength(token)) {
return null;
}
return new DefaultCsrfToken(this.headerName, this.parameterName, token);
}
public static CookieCsrfTokenRepository withHttpOnlyFalse() {
CookieCsrfTokenRepository result = new CookieCsrfTokenRepository();
result.setCookieHttpOnly(false);
return result;
}
You may explore the methods here
You may find additional information about the httpOnly attribute of cookies here: https://www.cookiepro.com/knowledge/httponly-cookie/
I am developing a application with angular 6 as front end and spring boot as back end. In this while implementing user authentication module I want to redirect to student and staff home after login accordingly.
But, I am not able to redirect the page from spring boot. Using Redirect to an external URL from controller action in Spring MVC this solution I am getting the CORS error :
"Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response."
Or, if there is another way to do this task?
AuthenticationController.java
package sgsits.cse.dis.user.controller;
#CrossOrigin(origins = "*") // enables cross origin request
#RestController
#RequestMapping(value = "/dis")
#Api(value = "Authentication Resource")
public class AuthenticationController {
#Autowired
StudentRepository studRepo;
#Autowired
StaffRepository staffRepo;
#Autowired
EmailController email;
String pattern = "MM-dd-yyyy";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
#ApiOperation(value = "login", response = Object.class, httpMethod = "POST", produces = "application/json")
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(#RequestBody Authentication auth, HttpServletResponse httpServletResponse) {
Optional<StaffProfile> staff = staffRepo.findByEmail(auth.getUsername());
if (staff.isPresent()) {
String md5_pass = MD5.getHash(auth.getPassword());
if (staff.get().getPassword().equals(md5_pass)) {
// set session
// update last login
return "forward:/localhost:4200/staff";
} else
return "You have entered incorrect password";
} else {
Optional<StudentProfile> student = studRepo.findByEnrollmentId(auth.getUsername());
if (student.isPresent()) {
String md5_pass = MD5.getHash(auth.getPassword());
if (student.get().getPassword().equals(md5_pass)) {
// set session
// update last login
httpServletResponse.setHeader("Location", "http://localhost:4200/reset-password");
httpServletResponse.setStatus(302);
return "redirect:localhost:4200/student";
} else
return "You have entered incorrect password";
} else {
return "You are not registered with the system. Please signup first";
}
}
}
}
Do not add #CrossOrigin(origins = "*") annotation to controller.
Assume your api runs on 8080 and your angular code on 4200. If true;
create a file called proxy.conf.json
{
"/api/": {
"target": "http://localhost:8080/",
"secure": false,
"changeOrigin": true
}
Start angular app using this command
ng serve --proxy-config proxy.conf.json
With this configuration when you call localhost:4200/api you it will call 8080 and it won't have any CORS error
Why don't you return a proper API Response and code and depending on your response and Code you can redirect on the front end.
It makes your API Completely RESTFUL. I hope this helps.
I observed that when you redirect from Spring Boot to an Angular page the error "request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response" means that, Angular node.js server does not detect field content-type in the header Access-Control-Allow-Headers in your Angular app configuration. The point here is that roles of backend server and and client app are a bit the other way around in this redirection.
To solve this I would add content-type field into the response header Access-Control-Allow-Headers in the following way:
1. create file proxy.config.js just below the package.json file in your Angular project with this code:
module.exports = {
"/": {
"secure": false,
"bypass": (req, res, proxyOptions) => {
res.setHeader('Access-Control-Allow-Headers', "Content-Type");
}
}
};
and modify angular.json file by adding line:
"proxyConfig": "proxy.config.js"
to this location:
projects >> client >> architect >> serve >> options
This solution worked in my case. Note that the capitalization of first letter of the field name matters.