NULL http headers are passed to the server from Angular2 app
Asked Answered
H

2

1

I have a REST api developed using SpringBoot and it contains the implementation for JWT for authentication and authorization. A FilterRegistrationBean is used for that.

I have a class called JwtFilter which extends GenericFilterBean. In that I look for the content of the request headers in order to authorize the users.

public class JwtFilter extends GenericFilterBean{

    @Override
    public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {

        System.out.println("INSIDE JWT FILTER");

        final HttpServletRequest request = (HttpServletRequest) req;

        final String authHeader = request.getHeader("Authorization");
        String reqHdr = request.getHeader("X-Requested-By");
        String myHdr = request.getHeader("myheader");

        System.out.println("Auth header = "+authHeader);
        System.out.println("requested by header = "+reqHdr);
        System.out.println("my header = "+myHdr);


        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            System.out.println("JwtFilter.doFilter -> auth header is null or token does not start with Bearer");
            throw new ServletException("Missing or invalid Authorization header.");
        }

        final String token = authHeader.substring(7); // The part after "Bearer "

        try {
            final Claims claims = Jwts.parser().setSigningKey("secretkey").parseClaimsJws(token).getBody();
            request.setAttribute("claims", claims);
        }
        catch (final SignatureException e) {
            System.out.println("VIRGLK EXCEPTION : JwtFilter.doFilter -> Signature Exception, invalid token = "+e.getMessage());
            throw new ServletException("Invalid token.");
        }

        chain.doFilter(req, res);
    }

}

I can assure that setting the headers dynamically works fine, since I tested it for some other HTTP requests to the same server which are not url filtered(login request).

But, when it comes to the requests which are filtered by above class, I see that every header of the requests are null.

Given below is how I add headers in to the requests in Angular2. I have extended BaserequestOptions class and overridden the merge method in order to add headers dynamically.

@Injectable()
export class CustomRequestOptions extends BaseRequestOptions {
    constructor(private _globals: Globals) {
        super();
        this.headers.set('Content-Type', 'application/json');
        this.headers.set('X-Requested-By', 'Angular 2');
    }

    merge(options?: RequestOptionsArgs): RequestOptions {
        var newOptions = super.merge(options);
        let hdr = this._globals.getAuthorization();
        newOptions.headers.set("Authorization", hdr);
        newOptions.headers.set("myheader", "my header value");
        return newOptions;
    }

}

But these headers are null when checked in the API for the requests which are filtered. As I mentioned above, there is no problem for the non filtered requests. Both statically appended('X-Requested-By') and dynamically appended('myheader') headers and available in the server.

I cannot understand what is happening here. At least, I have no clue, from which side, the error is coming from.

Following is the request header copied from browser console for the failed request.

OPTIONS /protected/get-roles/USER HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Access-Control-Request-Headers: authorization,content-type,myheader,x-requested-by
Accept: */*

Can someone point me out what could be the issue here. I am clueless.

EDIT

I guess the issue is coming from the server because of the following reason.

I was able to successfully retrieve all the header values of a request which was done after the authorization header is set and that request is not a filtered one. Therefore I guess, there is something wrong with the JWT Filter implementation.

This might be a stupid question, but, Is it due to the reason that the requests I send are not in the type of ServletRequest may be? doFilter method in JwtFilter accepts the parameter of ServletRequest.

Hibbard answered 13/5, 2017 at 17:16 Comment(2)
This request is an OPTIONS request, most probably a pre-flight request sent by the browser before the actual GET/POST/PUT/DELETE request, because you're using CORS. You should just ignore OPTIONS requests in your filter.Ossa
@JBNizet thank you very much for this guidance. It solved my issue. :) Some references -- #18264834 https://mcmap.net/q/88724/-header-in-the-response-must-not-be-the-wildcard-39-39-when-the-request-39-s-credentials-mode-is-39-include-39Hibbard
H
2

I thought of writing the solution I found as an answer here since it might help someone in future. Thanks goes to JB Nizet for his comment. He showed me where the issue is.

It was an issue with CORS. A pre-flight request was there sent by the browser before the actual GET request(in my case) was sent. As @JB Nizet pointed out, I changed my code referring the following linked answers.

Cross-Origin Resource Sharing with Spring Security

Header in the response must not be the wildcard '*' when the request's credentials mode is 'include'

My JwtFilter now looks like this

public class JwtFilter extends GenericFilterBean{

    private final List<String> allowedOrigins = Arrays.asList("http://localhost:3000");

    @Override
    public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {

        System.out.println("INSIDE JWT FILTER");

        final HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;


        // Access-Control-Allow-Origin
        String origin = request.getHeader("Origin");
        response.setHeader("Access-Control-Allow-Origin", allowedOrigins.contains(origin) ? origin : "");
        response.setHeader("Vary", "Origin");

        // Access-Control-Max-Age
        response.setHeader("Access-Control-Max-Age", "3600");

        // Access-Control-Allow-Credentials
        response.setHeader("Access-Control-Allow-Credentials", "true");

        // Access-Control-Allow-Methods
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");

        // Access-Control-Allow-Headers
        response.setHeader("Access-Control-Allow-Headers",
                "Origin, Authorization, myheader, X-Requested-By, X-Requested-With, Content-Type, Accept, " + "X-CSRF-TOKEN");

        if (request.getMethod().equals("OPTIONS")) {
            response.flushBuffer();
        }else{

            final String authHeader = request.getHeader("Authorization");
            String reqHdr = request.getHeader("X-Requested-By");
            String myHdr = request.getHeader("myheader");

            System.out.println("=====================================================");
            System.out.println("Auth header = "+authHeader);
            System.out.println("requested by header = "+reqHdr);
            System.out.println("my header = "+myHdr);
            System.out.println("=====================================================");

            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
                System.out.println("JwtFilter.doFilter -> auth header is null or token does not start with Bearer");
                throw new ServletException("Missing or invalid Authorization header.");
            }

            final String token = authHeader.substring(7); // The part after "Bearer "

            try {
                final Claims claims = Jwts.parser().setSigningKey("secretkey").parseClaimsJws(token).getBody();
                request.setAttribute("claims", claims);
            }
            catch (final SignatureException e) {
                System.out.println("JwtFilter.doFilter -> Signature Exception, invalid token = "+e.getMessage());
                throw new ServletException("Invalid token.");
            }

            chain.doFilter(req, res);

        }


    }

}

Happy Coding !

Hibbard answered 14/5, 2017 at 10:7 Comment(0)
V
1

I've seen this issue many times before. The key here is Access-Control-Allow-Headers http header. So goes CORS, this can bite you if the headers your client and server are trying to exchange are not listed. The network tab in a browser may even show the headers in the response and request entities however it will not be accessible to the application layer.

Are you using Spring Boot for application server? If so, there's some magic there in the way WebSecurity is configured. Specifically extending and configuring HttpSecurity via WebSecurityConfigurerAdapter will help handle the CORS headers on all HTTP verbs like OPTION, POST, GET, etc.

Take a look at https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers for examples on what the headers value should be, basically just a comma-separated header key list.

Voidable answered 14/5, 2017 at 17:42 Comment(1)
yes, thank you for your answer. I somehow figured it out and added the fix by configuringHibbard

© 2022 - 2024 — McMap. All rights reserved.