Open Closed

Nginx reverse proxy deployment - login does not return #465


User avatar
1
jason.smith created
  • ABP Framework version: v3.1.2
  • UI type: Angular
  • Tiered (MVC) or Identity Server Seperated (Angular): no (single host)
  • Exception message and stack trace: None
  • Steps to reproduce the issue:

The setup works find on the developers machine using localhost, and local database. Looking to now deploy using the following:

  • Nginx reverse proxy
    • host angular front end
    • host API redirect
  • AWS ec2 instance with ports 80, 443, and 5000 allowed
  • RDS database setup as postgreSQL 12.4 (version used in development)
  • dotnet kestrel service bound to localhost:5000 (Nginx redirects public_ip:5000 to this)

Angular environment file - replace <public_ip> with actual public address <br>

import { Config } from '@abp/ng.core';
const baseUrl = 'http://<public_ip>:80';
export const environment = {
    production: false,
    application: {
        baseUrl,
        name: 'Repros',
        logoUrl: '',
    },
    oAuthConfig: {
        issuer: 'http://<public_ip>:5000',
        redirectUri: baseUrl,
        clientId: 'Repros_App',
        responseType: 'code',
        scope: 'offline_access Repros',
    },
    apis: {
        default: {
            url: 'http://<public_ip>:5000',
            rootNamespace: 'eWater.Repros'
        },
    },
} as Config.Environment;

<br> Http.Host environment file - again replace <public_ip> with actual public address, same for connection string <br>

{ 
    "Urls": "[http://localhost:5000](http://localhost:5000)", 
    "App": { 
        "SelfUrl": "[http://localhost:5000](http://localhost:5000)", 
        "CorsOrigins": "http://<public_ip>,[http://localhost:80,http://localhost:4200](http://localhost:80,http://localhost:4200)" 
    }, 
    "ConnectionStrings": { 
        "Default": "<connection_string>" 
    }, 
    "AuthServer": { 
        "Authority": "[http://localhost:5000](http://localhost:5000)" 
    }
}

<br> Ngnix configuraiton file <br>

worker_processes 1;

events {
    worker_connections 1024;
}

http {
    include mime.types;
    default_type application/octet-stream;
    sendfile        on;
    keepalive_timeout  0;

    server {
        listen       80;
        index index.html;
        root scenario-datastore-debug/dist;
        ignore_invalid_headers off;
        underscores_in_headers on;
        location / {
            try_files $uri %uri/ /index.html;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
        root   html;
    }
}

server {
    listen        5000;
    location / {
        proxy_pass         [http://127.0.0.1:5000](http://127.0.0.1:5000);
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}

<br> The resulting behaviour is the following:

  1. The front end starts up and the Angular front end loads into the index page with user logged out. 
  2. Clicking login results in the progress bar across the top, and only one endpoint being hit. Nothing is logged in the Host service log file. 

<br> Lastly I know the http host is working as expected as I can see it being called in the initial home page load.

I don't see any errors or activity in the dev console, host log, or nginx log. The script just seems to stop.

I am uncertain as to where to even start with an error like this. The dev environment works correctly, even having parts of it (i.e. not ng serve) go through nginx.

I found that the requireHttps for oAuth was set to the default of everything but localhost requires. So I turned this off and started getting the following error in the chrome dev console:

invalid issuer in discovery document expected: http://<public_ip>:5000 current: http://<public_ip>

My nginx configuration should be allowing this through, so confused as to what is occurring here.


27 Answer(s)
  • User Avatar
    0
    armanozak created

    Hi,

    Does environment.prod.ts have the same oAuthConfig?

    Thanks.

  • User Avatar
    0
    jason.smith created

    Hi,

    The environment.prod.ts oAuthConfig has the following:

    oAuthConfig: {
            issuer: 'http://<public_ip>:5000',
            redirectUri: baseUrl,
            requireHttps: false,
            clientId: 'Repros_App',
            responseType: 'code',
            scope: 'offline_access Repros',
        },
    

    Cheers

  • User Avatar
    0
    jason.smith created

    So it appears with the above setup I get the following from http://<public_ip>:5000/.well-known/openid-configuration.

    {"issuer":"http://<public_ip>","jwks_uri":"http://<public_ip>/.well-known/openid-configuration/jwks","authorization_endpoint":"http://<public_ip>/connect/authorize","token_endpoint":"http://<public_ip>/connect/token","userinfo_endpoint":"http://<public_ip>/connect/userinfo","end_session_endpoint":"http://<public_ip>/connect/endsession","check_session_iframe":"http://<public_ip>/connect/checksession","revocation_endpoint":"http://<public_ip>/connect/revocation","introspection_endpoint":"http://<public_ip>/connect/introspect","device_authorization_endpoint":"http://<public_ip>/connect/deviceauthorization","frontchannel_logout_supported":true,"frontchannel_logout_session_supported":true,"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"scopes_supported":["openid","profile","email","address","phone","role","Repros","offline_access"],"claims_supported":["sub","birthdate","family_name","gender","given_name","locale","middle_name","name","nickname","picture","preferred_username","profile","updated_at","website","zoneinfo","email","email_verified","address","phone_number","phone_number_verified","role"],"grant_types_supported":["authorization_code","client_credentials","refresh_token","implicit","password","urn:ietf:params:oauth:grant-type:device_code"],"response_types_supported":["code","token","id_token","id_token token","code id_token","code token","code id_token token"],"response_modes_supported":["form_post","query","fragment"],"token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post"],"id_token_signing_alg_values_supported":["RS256"],"subject_types_supported":["public"],"code_challenge_methods_supported":["plain","S256"],"request_parameter_supported":true}
    

    I am unsure as to what has made the service incorrrectly return the base address without port as the issuer. It also incorrectly sets the jwks url which makes subsequent requests fail even if validation of the configuration is turned off.

    How do I get the authserver to return issue = http://<public_ip>:5000 instead of what its currrently doing?

  • User Avatar
    0
    armanozak created

    Hi,

    I see two things:

    1. On the 2nd screenshot, where abp-ng-account-es2015.js is loaded, there is no hash at the end of the filename. Angular puts a hash on production build output by default to avoid caching of old files. The response on the screenshot shows that the file is loaded from cache. Please check if you are using yarn ng build --prod or yarn build:prod for production. If you are using the prod build command but have disabled hash generation in angular.json on purpose, I recommend turning it back on.
    2. The Nginx configuration should add headers to requests made to index.html, otherwise browser will cache it. The (simplified) configuration for headers usually looks something like this:
    location / {
        gzip_static on;
        try_files $uri @index;
    }
    
    location @index {
        add_header Cache-Control no-cache;
        expires 0;
        try_files /index.html =404;
    }
    

    Please try things above and let me know if the issue is fixed.

    Have a great day.

  • User Avatar
    0
    jason.smith created

    Hi,

    Thanks for the suggestion. I will try it.

    This however does not address the issue that http://<public_ip>:5000/.well-known/openid-configuration is returning the wrong issuer (as its not hitting the Angular page, its the identity server). Do you have a template or suggested based Nginx configuration I could try?

    Angular is configured correctly to specify the issuers as http://<public_ip>:5000 so that calls are directed to the service. The first call to the identity server is correct, however as the identity server returns the incorrect issuer and other url's subsequent calls are incorrect.

    Cheers, Jason

  • User Avatar
    0
    armanozak created

    Hi,

    Unless Nginx delivers the latest files, we cannot verify if the configuration is working or not. So, we first should make sure that an earlier version of the app is not running. Only after then we can address the issues.

    Please try suggested changes and let me know about the result.

    Note: You can disable the cache in the browser and try again instead of implementing the changes. That would be much quicker.

  • User Avatar
    0
    jason.smith created

    Hi,

    Sure. Caching is off, I get the same result.

    Request on localhost correctly reports the port number for auth endpoints

    Public IP does not

    I must be missing something on the proxying inside Nginx and I am unsure as to what it is. At this point I am no longer concerned with the Angular frontend, it can't even get the indentity server to report its configuration correctly.

  • User Avatar
    0
    jason.smith created

    I have now changed ngnix config proxying for 5000 to have the line:

    proxy_set_header   Host $host:5000;

    This has addressed the incorrect /.well-known/openid-configuration and I am able to get the login page now. The next issue is that login is not working. A valid user is simply returned to the same login screen.

  • User Avatar
    0
    jason.smith created

    My only guess at this stage is that the redirect is not working for some reason. Again I am seeing no errors reported in the console, just a reloading of the same login page.

  • User Avatar
    0
    armanozak created

    Hi,

    I am pretty sure the problem is at https://<public_ip>:5000/connect/authorize/callback request, because, although the correct redirect_uri parameter is passed to it, the user is redirected to https://<public_ip>:5000/Account/Login instead of https://<public_ip>. However, I am not sure what is wrong with it.

    I am going to have to ask someone else from the team for further help. Sorry.

  • User Avatar
    0
    jason.smith created

    So figured out what was going on. Turns out I am getting the samesite cookie issue on chrome.

    https://community.abp.io/articles/patch-for-chrome-login-issue-identityserver4-samesite-cookie-problem-weypwp3n

    I implemented the fix above and now the cookie correctly gets set.

    Now we are onto the next issue. The user logs in, however is immediately redirected to the logout screen.

  • User Avatar
    0
    jason.smith created

    OK. Found what is occurring next. The fetchAndProcessToken from angular_oauth2-oidc.js is getting an invalid_grant response from tokenEndpoint. Turns out the token has timed out already. Looking at the auth service logs as to why.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Now we are onto the next issue. The user logs in, however is immediately redirected to the logout screen.

    hi jason.smith

    Can you share the website url and username, password? So we can try it directly. liming.ma@volosoft.com

  • User Avatar
    0
    jason.smith created

    The next thing that we have diagnosed things down to is that the endpoint api/abp/application-configuration comes back with a currentUser object with nulls for each property. This means the current user id is not set causing a auto logout when the auth token is checked. The cookie looks to be correctly sent as well as the token. We are unsure why the response is coming back as it is.

    Using Insomina on the same endpoint after logging in returns a valid currentUser object. Could this be another issue with the same-site cookie and not having https turned on for the service on port 5000?

  • User Avatar
    0
    alper created
    Support Team Director

    yes this might be the issue

  • User Avatar
    0
    jason.smith created

    I am now in the process of switching the site to SSL. I am now getting a cipher mismatch error which looks to be a misconfiguration with cloudflare and nginx. The challenge I was originally attempting to avoid dealing with right now. Something I am going to have to deal with outside of this forum.

    What would really help (because I am now 2 weeks deep into figuring out a deployment configuration) are two things:

    With using the Angular and incorporated app / auth server tutorial, what would be:

    1. The Ngnix configuraiton to use for http access to the site?
    2. The Nginx configuration to use for https access to the site?

    I am not the most versed web technology developer, yet I am finding the deployment process here very susceptible to configuration typos or mistakes without any indication what has caused it.

  • User Avatar
    0
    jason.smith created

    So the site is now fully https yet the user is automatically logged out still. https://test-datastore.ewater.org.au/ - basically the same result as http version.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi

    Can you check the logs of https://test-datastore.ewater.org.au:8443/ application?

    It does not seem to recognize the jwt token it issued.

  • User Avatar
    0
    jason.smith created

    I have the site working now. The last piece of the puzzle was the AuthServer needed to be the public address instead of the internal address in the appsettings.

  • User Avatar
    0
    alper created
    Support Team Director

    closing the issue, you can always reopen if you need help on the same issue.

  • User Avatar
    0
    jason.smith created

    I have recently updated our solution to use the commercial modules. This change the authentication method to the commerical module available which is fine. On testing cookie authentication for POST requests no longer work (GET requests are fine).

    To see if this was an issue with my migration to the commercial modules I decided to create a simple Angular application using the Abp Suite. The same issue is present. For cookie auth POST requests no longer work. Also of interest, only the first POST to Login works. All subsequent calls result in a 400 Bad Request.

    Any hints as to where to look would be greatly appreciated. The very strange behaviour is that GET requests work, yet POST requests fail for the same endpoint. My current next step is to debug AbpAutoValidateAntiforgeryTokenAuthorizationFilter, which I was hoping not to do.

    Abp Suite 3.3.1, Angular App.

    Fresh site with a simple entity that contains a single property.

    Use REST GET command after login = Successful GET (returns zero items)

    Use REST POST command after login = BAD request

    Server reports the following in the log: 2020-11-12 15:51:05.452 +11:00 [ERR] The required antiforgery cookie ".AspNetCore.Antiforgery.Fk0-jtlgxmU" is not present. 2020-11-12 15:51:05.452 +11:00 [INF] Authorization failed for the request at filter 'Volo.Abp.AspNetCore.Mvc.AntiForgery.AbpAutoValidateAntiforgeryTokenAuthorizationFilter'.

    Works fine in non-commercial Angular App.

    In addition, second call to login = BAD request. Same error as above recorded in server.

    Angular UI works fine.

    Tool used for testing Insomnia REST Client with internal cookie management.

  • User Avatar
    0
    jason.smith created

    OK - I can see that the default options does the following:

    AutoValidateIgnoredHttpMethods = new HashSet<string> {"GET", "HEAD", "TRACE", "OPTIONS"};
    

    So that explains why the GET request is working, but the POST is not.

    Yet the following:

    TokenCookie = new CookieBuilder
                {
                    Name = "XSRF-TOKEN",
                    HttpOnly = false,
                    IsEssential = true,
                    Expiration = TimeSpan.FromDays(3650) //10 years!
                };
    

    I never see this cookie returned from the service. Calling https://localhost:44382/api/account/login does not return this token, how is it retrieved using the login REST API call?

  • User Avatar
    0
    jason.smith created

    CSRF-Anti-Forgery.md has the following comments:

    Antiforgery token validation is only enabled for razor pages by default and not enabled for HTTP APIs. You need to enable it yourself for the Controllers. You can use the [ValidateAntiForgeryToken] attribute for a specific API Controller/Action or the [AutoValidateAntiforgeryToken] attribute to prevent attacks globally.
    

    The check is occurring on a controller without this attrbute set. Is this a bug?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi jason.smith

    Can you create a new quesion?

  • User Avatar
    0
    jason.smith created

    OK. This line is confusing. Same documentation.

    All your clients, including non-browser clients, should care about obtaining and sending the antiforgery token in every request. In fact, non-browser clients has no CSRF risk and should not care about this.
    

    Should insomnia care about sending the cookie? Its never given it though.

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11