angular / universal

Server-side rendering and Prerendering for Angular
MIT License
4.04k stars 484 forks source link

SSR not working with HTTPS API URL #856

Closed ranjitnadar closed 3 years ago

ranjitnadar commented 6 years ago

Note: for support questions, please use one of these channels: https://github.com/angular/universal/blob/master/CONTRIBUTING.md#question. This repository's issues are reserved for feature requests and bug reports. Also, Preboot has moved to https://github.com/angular/preboot - please make preboot-related issues there.

SSR fail when we using https api url for seo meta information. Using http working perfect.

Any solution please help me regards this.

Toxicable commented 6 years ago

How does it fail?

ranjitnadar commented 6 years ago

Using http powered api SSR working perfectly. But can not figure out with SSR Https why its not working

Bellow are the screen shots It render on client side when i inspect in google chrome, But i do the view source code of it Can not see any meta tag rendering there.

Inspect in chrome

8kbtc

view source code in chrome

8pcnu

As you can see the Difference here the last to meta tag render on client side on inspect but are not there when i view source code when i use SSR Https, But using SSR Http every thing work fine.

Using https://angular.io/api/platform-browser/Meta

My code:

wbkze
hiepxanh commented 6 years ago

please, provide the network recording in your network recording (example below), I believe some request are pending at status column. other question, do you use firebase or anything else like socket.io ? I have the same problem when fetching data with firebase.

image

salih0313 commented 6 years ago

@ranjitnadar I am also facing same issues. Did you find any solution?

ranjitnadar commented 6 years ago

Unable to find solution: We tried installing ssl in server side rendering unable to resolve this issue. For now we are using http api url for meta information rendering.

patrykp57 commented 6 years ago

I have the same issue, but it only doesn't work on home page. I'm using https on whole site + on api. When I'm trying to enter home page, AuthGuard from Angular redirect me from https://example.org to https://example.org/en and then I can see Elements in Chrome Dev Tools, but when I'm trying to inspect view source by right mouse click, it's empty (like without Universal). When I'm turning off HTTPS redirect which I did like that

app.use(forceSsl);

  app.use('/',function(req, res, next){
    console.log(req.headers);
    next();
 });

and enter, home page on http, home page view source is working good. Any solution ?

jeyachanthuruj commented 6 years ago

Guys, Any update on this topic. Because I'm also facing this problem.

Chipintoza commented 6 years ago

Any update on this?

Toxicable commented 6 years ago

I havn't been able to reproduce this issue myself. Could someone provide a minimal reproduction for this issue?

Baedonghee commented 6 years ago

i'm same bug

SSR Http every thing work fine

but https not work....

how https seo??

Toxicable commented 6 years ago

@Baedonghee Sorry but there isn't much we can do without a minimal reproduction of the issue. Are you able to put one together for us?

patrykp57 commented 6 years ago

@Toxicable I think that problem is related to mine described below https://github.com/angular/angular/issues/23427 . It's something wrong with SSR and TransferState.

Here https://github.com/patrykp57/angular-sample You can check how it works.

Edit: On HTTPS (or it's external server problem, dunno) on page first load we can see flickering content I think it's related with site source code because after that, home page code is empty. like without Universal Robots are parsing it good, everything is working fine, but I think it's mostly related to that flicker at the beginning which I described in topic above

Baedonghee commented 6 years ago

app.get('/', (req, res) => { res.render('index', { req, res, providers: [ { provide: 'serverUrl', useValue: ${req.protocol}://${req.get('host')} } ] }); }); app.engine('html', ngExpressEngine({ bootstrap: AppServerModuleNgFactory, providers: [ provideModuleMap(LAZY_MODULE_MAP) ] })); app.set('view engine', 'html'); app.set('views', join(DIST_FOLDER));

app.use('/api/user', userRouter); app.get('*', (req, res) => { res.render('index', { req, res, providers: [ { provide: 'serverUrl', useValue: ${req.protocol}://${req.get('host')} } ] }); });

server.ts

this.store.dispatch(new CoreActions.GetUserList(this.queryParams.toString())); this.userListState = this.store.select('userlist');

app.component.ts

http seo ok.... but https seo not working....

inside api server https not seo...

ssatz commented 6 years ago

facing the same issue using docker. The app throwing

message: 'Http failure response for (unknown url): 0 Unknown Error',

This is working perfectly in my local environment using self signed certificate. When the api changed to https am getting above error.

Hosted on digital ocean

Structure of docker

Laravel (Api https://api.fabivo.com) Angular (https://fabivo.com) nginx (Nginx Configuration)

docker-compose.yml

version: '3'
services:

  laravel:
    build: ./laravel/docker/php
    depends_on:
      - database
    expose:
      - 9000  
    volumes:
      - ./laravel:/var/www/laravel
      - $HOME/.composer/:$HOME/.composer/
    environment:
      - "DB_HOST=database"    #mysql service name - database
      - "DB_DATABASE=homestead"
      - "DB_USERNAME=homestead"
      - "DB_PASSWORD=homestead"
      - "REDIS_HOST=cache"
      - "REDIS_PORT=6379"
      - "APP_URL=http://app.lo"

  angular:
    build: ./angular
    expose:
      - 3000
    volumes:
      - ./angular:/var/www/angular 
    depends_on:
      - laravel

  web:
    build: ./nginx
    volumes:
      - ./:/var/www/
    ports:
      - 80:80
      - 443:443
    links:
      - laravel:laravel
      - angular:angular
    depends_on:
      - laravel
      - angular
    environment:
      - VIRTUAL_HOST=app.lo, portal.app.lo , api.app.lo, shop.app.lo  
   networks:
      default:
         aliases:
            - api.app.lo

  database:
    build: ./laravel/docker/mariadb
    environment:
      - "MYSQL_ROOT_PASSWORD=secret"
      - "MYSQL_DATABASE=homestead"
      - "MYSQL_USER=homestead"
      - "MYSQL_PASSWORD=homestead"
    ports:
      -  3306:3306

  mongodb:
   image: mongo:latest
   container_name: mongo
   restart: always
   volumes:
     -  mongo:/data/db
   ports:
      - 27017:27017
   command: --storageEngine wiredTiger

  cache:
    image: redis:4.0-alpine
    command: redis-server --appendonly yes
    ports:
      - 6379:6379
  nodejs:
    build: ./laravel/docker/nodejs
    volumes:
      - ./:/var/www/laravel

volumes:
  mysqldata:
  mongo:

networks:
  default:
    external:
      name: nginx-proxy

any suggestion? Update: Here is the full details of docker setup Docker Compose Setup

ssatz commented 6 years ago

@Toxicable : here is minimal reproduction repo https://github.com/ssatz/Angular-SSR-HTTPS-Error Deployed to heroku HEROKU

Note : This is working perfectly in my local with Self Signed certificate.

ssatz commented 6 years ago

Hello, Issue resolved for me!

After 1 week of trail and error , finally am able to solve the issue.

Incase if any one having the issue, change secp384r1 to prime256v1 ssl_ecdh_curve prime256v1;

And i suggest universal team to provide a error log for ssr.

rkajbaf commented 6 years ago

@ssatz where did you set ssl_ecdh_curve prime256v1;? I'd like to try your solution. Much appreciated!

rkajbaf commented 6 years ago

@Toxicable

In terms of a reproducible case, this is my scenario:

I'm on Angular 6 and everything works well locally, including HTTPS requests. On server, only works if I remove the HTTPS requests. Environments match up, except my openssl version are different as below:

Local is 0.9.8e and Server is OpenSSL 1.0.2k-fips

To reproduce, add something like this to ngOnInit() of your app.component.ts in the start kit.

ngOnInit() {
    this.httpClient
        .get(VALID_HTTPS_URL);
        .subscribe( res => {
            // do something
        });
}

That call never completes, so render never returns.

ssatz commented 6 years ago

@rkajbaf : i am using nginx as reverse proxy, you need the define it in nginx config

        ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;

        ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
        ssl_prefer_server_ciphers on;

        ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

        ssl_ecdh_curve prime256v1;
        ssl_session_tickets off;

        # OCSP stapling
        ssl_stapling on;
        ssl_stapling_verify on;
        resolver 8.8.8.8;`
dottodot commented 6 years ago

@Toxicable I'm suffering from the same issue, and only noticed after launching a new site that it was broken. I've also checked previously working apps to find they are broken too. Not sure at what point this bug was introduced but it's causing major issues.

dottodot commented 6 years ago

@Toxicable I have found that http requests in a resolver do get ssr it's just the requests made in components that don't.

dottodot commented 6 years ago

@Toxicable Look like it's a nodejs issue. https://github.com/nodejs/node/issues/16196 I found that using node 8.5.0 resolves the issue I was using 8.11.3

Gorniv commented 6 years ago

No problem with https: https://ssr.angular.su/back Try look:

piyushpatil027 commented 6 years ago

@Gorniv what is solution for this. I have same issue with https.

Gorniv commented 6 years ago

@piyushpatil027 you need to reproduce exception on a public repository.

bastienlemaitre commented 6 years ago

Same issue..

Gorniv commented 6 years ago

@bastienlemaitre you need to reproduce exception on a public repository.

chen1223 commented 6 years ago

Hi, really late to this issue, but I was struggling for this issue too myself for 2 days and now finally get it working on my side! Below is how I solve this issue:

Since Angular Universal has some trouble requesting to https backend, I make an additional server block in my nginx setting files, so that my backend can also listen from localhost at http. And all request coming in from clients will be redirect to https. Please see my nginx settings below

`upstream recipe_express { server 127.0.0.1:4000; }

server { listen 80; listen [::]:80; server_name example.com www.example.com; return 301 https://$server_name$request_uri; } server { listen 80; server_name localhost;

    root /var/www/html/recipe-backend/public;

    location /api {
            try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~\.php$ {
            include snippets/fastcgi-php.conf;
            fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
    }

} server {

SSL configuration

    #
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;

    server_name example www.example.com localhost;
    ssl_certificate         /etc/ssl/example.crt;
    ssl_certificate_key     /etc/ssl/example.key;

    root /var/www/html/laravel_backend/public;
    # Add index.php to the list if you are using PHP
    index index.html index.htm index.php;

    location /api {
            try_files $uri $uri/ /index.php$is_args$args;
    }

    location / {
            alias /var/www/html/app/dist/browser/;
            try_files $uri $uri @backend;
    }

    location @backend {
            proxy_pass http://recipe_express; # <--- THIS DOES NOT HAVE A TRAILING '/'
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_http_version 1.1;
            proxy_set_header X-NginX-Proxy true;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_cache_bypass $http_upgrade;
            proxy_redirect off;
            proxy_set_header X-Forwarded-Proto $scheme;
    }

    # pass PHP scripts to FastCGI server
    location ~ \.php$ {
            include snippets/fastcgi-php.conf;
            fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
    }

} ` In my nginx setting basically I redirect the incoming request ONLY if it matches my domain name (from client browser) and send it to my backend through http if it's from localhost.

Also at your Angular Application, you need to add conditional check to see if your APP is running in browser or is it running server side rendering by Angular Universal. You can check by these two functions provided by Angular isPlatformBrowser and isPlatformServer.

Hope it helps someone else who's still trying to figure this out.

tedishero commented 6 years ago

It worked for me after I added this line to my server.ts file "process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';"

Toxicable commented 6 years ago

Hey all, the https urls that you're requesting; is it on an internal corprate network or can they be each publicallly? Or do they have self signed/not globally trusted certs?

TylerOrtiz commented 5 years ago

I'm using LetsEncrypt with Nginx -- no internal network and I've done everything I can to make it reachable. I can only think I can't use the HttpClient during app initialization...

EDIT: 1/12/2019 - This partially ended up being that my "server" was being built without environment transformations -- so the browser build was trying to hit localhost:8089 while the server was still using localhost:8088. This is because I did not have environment transformation configuration in my CLI configuration file angular.json.

This fixed me:

         "server": {
           "builder": "@angular-devkit/build-angular:server",
           "options": {
             "outputPath": "dist/server",
             "main": "src/main.server.ts",
             "tsConfig": "src/tsconfig.server.json"
+          },
+          "configurations": {
+            "production": {
+              "fileReplacements": [
+                {
+                  "replace": "src/environments/environment.ts",
+                  "with": "src/environments/environment.prod.ts"
+                }
+              ]
+            }
           }
         }

app.module

providers: [
  ConfigService,
  { provide: APP_INITIALIZER, useFactory: app_init, deps: [ConfigService], multi: true}
  ]

export function app_init(configService: ConfigService) {
  return () => configService.initializeApp();
}

config.service resolve for all cases since it always fails right now

public initializeApp(): Promise<any> {
    if (this.initializePromise) { return this.initializePromise; }

    this.initializePromise = new Promise((resolve, reject) => {
      this.testApiWithoutCreds().then((data) => {
        console.log('testApiWithoutCreds() success);
        resolve();
      })
      .catch(exception => {
        console.error('testApiWithoutCreds() error');
        resolve();
      });

      this.testApiWithCreds().then((data) => {
        console.log('testApiWithCreds() success');
        resolve();
      })
      .catch(exception => {
        console.error('testApiWithCreds() error}');
        resolve();
      });
    });

    return this.initializePromise;
  }
private testApiWithoutCreds(): Promise<string> {
    const promise = new Promise<string>((resolve, reject) => {
      const request = this.http.get(`${environment.serviceUrl}test`, { withCredentials: false })
      .subscribe(
        (res: any) => {
          resolve(res);
        },
        (exception) => {
          console.error(`testApiWithoutCreds() Error doing simple things.`);
          console.error(`Error detail: ${inspect(exception)}`);
          resolve('');
        });
    });

    return promise;
  }
  private testApiWithCreds(): Promise<string> {
    const promise = new Promise<string>((resolve, reject) => {
      const request = this.http.get(`${environment.serviceUrl}test`, { withCredentials: true })
      .subscribe(
        (res: any) => {
          resolve(res);
        },
        (exception) => {
          console.error(`testApiWithCreds() Error doing simple things.`);
          console.error(`Error detail: ${inspect(exception)}`);
          resolve('');
        });
    });

    return promise;
  }
reed-lawrence commented 5 years ago

It worked for me after I added this line to my server.ts file "process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';"

This has worked for me for local development !knock on wood!

julianmartire1 commented 5 years ago

Same problem but in firebase

ERROR HttpErrorResponse { headers: HttpHeaders { normalizedNames: Map {}, lazyUpdate: null, headers: Map {} }, status: 0, statusText: 'Unknown Error', url: 'https://firebaseapp.com/assets/data/product-1.json', ok: false, name: 'HttpErrorResponse', message: 'Http failure response for https://firebaseapp.com/assets/data/product-1.json: 0 Unknown Error', error: ProgressEvent { type: 'error', target: XMLHttpRequest { onloadstart: null, onprogress: null, onabort: null, onerror: null, onload: null, ontimeout: null, onloadend: null, _listeners: [Object], onreadystatechange: null, _anonymous: undefined, readyState: 4, response: null, responseText: '', responseType: 'text', responseURL: '', status: 0, statusText: '', timeout: 0, upload: [Object], _method: 'GET', _url: [Object], _sync: false, _headers: [Object], _loweredHeaders: [Object], _mimeOverride: null, _request: null, _response: null, _responseParts: null, _responseHeaders: null, _aborting: null, _error: null, _loadedBytes: 0, _totalBytes: 0, _lengthComputable: false }, currentTarget: XMLHttpRequest { onloadstart: null, onprogress: null, onabort: null, onerror: null, onload: null, ontimeout: null, onloadend: null, _listeners: [Object], onreadystatechange: null, _anonymous: undefined, readyState: 4, response: null, responseText: '', responseType: 'text', responseURL: '', status: 0, statusText: '', timeout: 0, upload: [Object], _method: 'GET', _url: [Object], _sync: false, _headers: [Object], _loweredHeaders: [Object], _mimeOverride: null, _request: null, _response: null, _responseParts: null, _responseHeaders: null, _aborting: null, _error: null, _loadedBytes: 0, _totalBytes: 0, _lengthComputable: false }, lengthComputable: false, loaded: 0, total: 0 } }

roshanj80 commented 5 years ago

Hey all,

This was not working for me too, and after a week long struggle I have found the problem. The actual problem was with the environment.ts file. For some reason, when Angular runs on the server it refers to the environment.ts file, and on the client it refers to environment.prod.ts.

I had http://localhost:3000 reference in environment.ts file. It worked fine for me after updating this. Fingers crossed, I hope the same was the issue at your end. All the best!

Thanks, Roshan

hiepxanh commented 5 years ago

It worked fine for me after updating this. what did you update?

hiepxanh commented 5 years ago

@roshanj80

danielmarinan commented 5 years ago

It worked for me after I added this line to my server.ts file "process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';"

This resolve my problem! Thanks @tedishero

jaguar7021 commented 5 years ago

Hi all. I found the problem for me. On my server I setup port forwarding by iptables iptables --table nat --append PREROUTING --protocol tcp --dport 443 --jump REDIRECT --to-ports 3000

That is, my web server is running on port 3000. I did not take this into account when copying the UniversalInterceptor code from the official documentation

Now I fixed it and SSR works correctly. My universal-interceptor.ts

import { Request } from 'express';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { Inject, Injectable, Optional } from '@angular/core';
import { HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';

@Injectable()
export class UniversalInterceptor implements HttpInterceptor {

    constructor(
        @Optional() @Inject(REQUEST) protected request: Request,
        @Optional() @Inject('REQUEST') protected origin_request: any
    ) {}

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        let serverReq: HttpRequest<any> = req;

        if (this.request) {
            let host = this.request.get('host');
            /**
             * Fix production SSR error
             */
            if (!host.includes(':')) {
                host += ':3000';
            }

            let newUrl = `${this.request.protocol}://${host}`;
            if (!req.url.startsWith('/')) {
                newUrl += '/';
            }

            serverReq = req.clone({
                url: newUrl + req.url,
                setHeaders: {
                    cookie: this.origin_request ? this.origin_request.headers.cookie : ''
                }
            });
        }

        return next.handle(serverReq);
    }
}
ishak91 commented 4 years ago

It worked for me after I added this line to my server.ts file "process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';"

This worked for me.. My Application Runs in Angualr V9

You will get below warning form node js console: (node:21308) Warning: Setting the NODE_TLS_REJECT_UNAUTHORIZED environment variable to '0' makes TLS connections and HTTPS requests insecure by disabling certificate verification.

ludoviccyril commented 4 years ago

I also faced this problem and struggled with it for a couple of hours. Applying the process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; fix worked for me, but the downside is that you compromise your app's security by exposing it to a man-in-the-middle attack.

Upon investigating further, I realized that the problem was not with Angular. I ran https.get('<my-api-url>') in a Node shell and I got: Uncaught Error: unable to verify the first certificate. Turns out, I had configured my API server with only the leaf certificate, not the full-chain certificate (leaf + CA bundle). Node apparently needs the full-chain certificate for verification, unlike browsers.

Upon installing the full-chain certificate on my server, the problem went away without any changes to the app codebase.

LeoCreer commented 3 years ago

WOW This was deff a show stopper. This needs to be added to Angular Docs @ludoviccyril So I have an Nginx box with SSL in front of my NodeJS cluster. I thought it would be ok to just proxy traffic to servers with out SSL but what a big mistake. This worked but is not a good fix. process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; Please reff @ludoviccyril.

jordankittle commented 3 years ago

Fixed. For us this was caused by Node v 8.17. Upgraded to 12-alpine and it solved the problem.

alan-agius4 commented 3 years ago

This doesn't appear to be a problem in Universal, but rather misconfigurations of the servers.

If you do pin point an issue with Universal, please open a new issue, provide a simple repository reproducing the problem, and describe the difference between the expected and current behavior.

pararell commented 2 years ago

I had similar problem and though that it is in HTTPS configuration, but it was in PWA settings, which is working just in HTTPS. So if someone will have similar problem, solution is here https://github.com/angular/angular/issues/30861

angular-automatic-lock-bot[bot] commented 2 years ago

This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.