microsoft / dev-tunnels

Dev Tunnels SDK
MIT License
286 stars 20 forks source link

HTTP POST fails with 401 Unauthorized #446

Closed MrCSharp22 closed 1 month ago

MrCSharp22 commented 4 months ago

Hi, I have setup a dev tunnel to a locally running ASP.NET Core app which uses Azure AD for SSO. The tunnel was setup using these commands:

devtunnel create my-test-dev-tunnel

devtunnel port create my-test-dev-tunnel --port-number 5000 --protocol https
devtunnel port create my-test-dev-tunnel --port-number 5001 --protocol https
devtunnel port create my-test-dev-tunnel --port-number 5173 --protocol https

devtunnel access create my-test-dev-tunnel --port-number 5000 --tenant --expiration 5h
devtunnel access create my-test-dev-tunnel --port-number 5001 --tenant --expiration 5h
devtunnel access create my-test-dev-tunnel --port-number 5173 --anonymous --expiration 5h

When I navigate to a URL that requires authentication using through the tunnel, I get redirected properly to the Azure AD login page. Upon a successful login, Azure AD responds back with a simple HTML page containing a form that is submitted automatically in the browser to my-app-url/signin-oidc and sends some auth parameters back to my app. The form is submitting using a HTTP POST.

However, the HTTP POST request always fails with 401 and never actually hits my app. I thought maybe /signin-oidc is conflicting with something in the tunnel itself so I changed that callback url to /hello-world which did not fix the issue.

I then noticed that HTTP POST requests are all failing.

I tried adding X-Tunnel-Skip-AntiPhishing-Page header and setting its value to True thinking it was because of the anti-phishing measures (shouldn't be since the request is POST) but that did not help either.

I'd appreciate any help and guidance you can offer. This is the last thing for me to solve before we can adopt dev tunnels into our internal processes.

Thank you.

derekbekoe commented 4 months ago

When I navigate to a URL that requires authentication using through the tunnel, I get redirected properly to the Azure AD login page.

Is the URL used here the port that you have anonymous access enabled on, i.e. 5173?

However, the HTTP POST request always fails with 401 and never actually hits my app. I then noticed that HTTP POST requests are all failing.

Can you share a tunnelId and request/correlation id for a sample of the POST requests returning 401?

MrCSharp22 commented 4 months ago

Hi @derekbekoe, thanks for the reply. Let me explain the setup a bit more. There are 3 ports that we need open when running the website locally:

5173 has anonymous access because the front-end of the website (either 5000 or 5001) will need to open a WSS connection and it will fail if it gets redirected to a login page.

The issue I described above happens when I navigate to my-website-host/login(port 5000 or 5001). My app redirects with a 302 to https://login.microsoft..... Once the login is successful, a 200 response containing a HTML form with some JS to submit it on page load. The HTTP POST from that form always returns a 401 error.

I observed my app logs at debug level and the request doesn't make it to my app at all. Inspecting the headers and comparing them with a request that made it to my app's server, confirms that the response is not generated by my app.

Here's a POST request sent to my website's home page that is also failing:

* Preparing request to https://0lw58g42.aue.devtunnels.ms:5000/
* Current time is 2024-05-08T23:24:20.209Z
* Enable automatic URL encoding
* Using default HTTP version
* Disable SSL validation
* Found bundle for host 0lw58g42.aue.devtunnels.ms: 0x23f8b439cb0 [can multiplex]
* Re-using existing connection! (#18) with host 0lw58g42.aue.devtunnels.ms
* Connected to 0lw58g42.aue.devtunnels.ms (20.213.0.59) port 5000 (#18)
* Using Stream ID: 3 (easy handle 0x23f8b84ec70)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):

> POST / HTTP/2
> Host: 0lw58g42.aue.devtunnels.ms:5000
> user-agent: insomnia/2023.5.8
> accept: */*
> content-length: 0

* TLSv1.2 (IN), TLS header, Supplemental data (23):

< HTTP/2 401 
< date: Wed, 08 May 2024 23:24:20 GMT
< content-length: 0
< www-authenticate: tunnel
< x-content-type-options: nosniff
< ratelimit-limit: HttpRequestRatePerPort:1500/m
< ratelimit-remaining: HttpRequestRatePerPort:1499
< ratelimit-reset: HttpRequestRatePerPort:49s
< vssaas-request-id: c7b1954b-ae2f-41c4-a40f-4cae66e63683
< strict-transport-security: max-age=31536000; includeSubDomains
< x-served-by: tunnels-prod-rel-aue-v3-cluster

* Connection #18 to host 0lw58g42.aue.devtunnels.ms left intact

The above is an empty POST request (no json or form body).

My tunnel ID is: carlisle-homes-dev-tunnel. I am not sure where I can find the correlation ID but the above log has vssaas-request-id.

Some more info:

> devtunnel show carlisle-homes-dev-tunnel

Tunnel ID             : carlisle-homes-dev-tunnel.aue
Description           :
Labels                :
Access control        : {}
Host connections      : 1
Client connections    : 0
Current upload rate   : 0 MB/s (limit: 20 MB/s)
Current download rate : 0 MB/s (limit: 20 MB/s)
Upload total          : 1799 KB
Download total        : 26 MB
Ports                 : 3
  5000  https  https://0lw58g42-5000.aue.devtunnels.ms/    (Host:unchanged, Origin:unchanged)
  5001  https  https://0lw58g42-5001.aue.devtunnels.ms/
  5173  https  https://0lw58g42-5173.aue.devtunnels.ms/
Tunnel Expiration     : 30 days

Looking forward to your reply.

Cheers

derekbekoe commented 4 months ago

I am not sure where I can find the correlation ID but the above log has vssaas-request-id. Yes I meant vssaas-request-id, thanks.

The issue I described above happens when I navigate to my-website-host/login(port 5000 or 5001). My app redirects with a 302 to https://login.microsoft..... Once the login is successful, a 200 response containing a HTML form with some JS to submit it on page load. The HTTP POST from that form always returns a 401 error. I observed my app logs at debug level and the request doesn't make it to my app at all. Inspecting the headers and comparing them with a request that made it to my app's server, confirms that the response is not generated by my app.

I believe the initial redirection you see to https://login.microsoft..... is not from your app but from the Dev Tunnels service. Once that login is successful, it's Dev Tunnels that redirects to your HTML.   I'll try to explain:

devtunnel access create my-test-dev-tunnel --port-number 5000 --tenant --expiration 5h
devtunnel access create my-test-dev-tunnel --port-number 5001 --tenant --expiration 5h

When you run the commands above, you're telling Dev Tunnels to authenticate requests on your behalf. Specifically in this case, to only allow access for identities in your Entra ID tenant. When the initial GET request to my-website-host/login is made, Dev Tunnels redirects you to log in to your tenant via Microsoft Entra and after login, you see your content. But when the POST request is made, you get the 401 response and that response is coming from the Dev Tunnels service (not your app) and it's because that request is not authenticated.

I have setup a dev tunnel to a locally running ASP.NET Core app which uses Azure AD for SSO.

Given you initially mentioned this, it sounds like your app (running on port 5000 / 5001) is already doing its own auth. Therefore, instead of --tenant, you could specify --anonymous. That way, Dev Tunnels will not auth and it'd be up to your own app to authenticate the requests. This YouTube video from our team covers that topic - https://www.youtube.com/watch?v=yCYLurylgj8.

On the other hand, if your app does not authenticate the POST endpoint or there's another reason you do not want to enable --anonymous on those ports, you can specify a Tunnel Access Token as a request header on the POST request - https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/security#tunnel-access. This is also mentioned in the YouTube video.

MrCSharp22 commented 4 months ago

Thank you @derekbekoe that was super helpful. Using --anonymous worked perfectly.

I watched the video but I am missing one piece of info here and would appreciate a quick explanation from you if you can:

When I have --tenant enabled and navigate to the tunnel URL, I will see and go past the anti-phishing screen then login to the tenant. At this point, my browser will hold cookies that ensure the next time I navigate to that domain, I won't see the anti-phishing screen nor would have to login again.

If I then navigate to my /login URL, I get taken to the app's own AzureAD login page. When I login successfully, the HTTP post from AzureAD back to my tunnel's domain fails. Are you suggesting that this failure (error 401) is happening because the POST is going to MS servers? Why would it do that if my browser is still carrying the cookies that log me into the tunnel?

Essentially, since that POST request is happening from the same browser, shouldn't it also be authenticated?

Again, thank you for taking the time to answer.

Cheers

derekbekoe commented 4 months ago

Glad to hear --anonymous on all ports works.

Dev Tunnel authentication is per-port, not per-tunnel. For the cookies, you should see one for each port. So, if you go through the login flow for port 5000 and then try to submit a POST request to port 5001, that request will not be authenticated and so the Dev Tunnel service would return a 401.

If all the requests are happening on the same port and you're still getting a 401, we may need some more details on each request of the flow you are describing.

MrCSharp22 commented 4 months ago

If all the requests are happening on the same port and you're still getting a 401, we may need some more details on each request of the flow you are describing.

Yes, this is what happened. The flow I described was on one port the entire time. What kind of details would you like me to provide so you can investigate that?

derekbekoe commented 4 months ago

Please share recent vssaas-request-ids for each of the requests including the timestamp it occurred. This should include the first GET request and then the POST request as well. Also, please check that in your browser, the dev tunnels auth cookie is sent along with the POST request.

derekbekoe commented 1 month ago

Closing this as stale but please comment to clarify your use case if you continue to see an issue.