I have a app which is setup to use Owin and Azure AD. It works well, but I have now a requirement to run inside an iframe as part of a third party solution.
This is OK apart from the security. https://login.microsoftonline.com does not allow running inside an iframe. My controller will check if the user is authenticated and if not, call 'HttpContext.GetOwinContext().Authentication.Challenge(..............'
On research it looks unlikely that I can do anything inside the frame. Is my only option to create a sign-in page, which redirects to the client app which includes the iframe? If this expires, the iframe then currently displays an error stating the content cannot be displayed in a frame, this is not very elegant.
AAD does not permit framing of pages where credentials are entered. This requirements stems from a need to prevent click jacking style attacks. See: https://www.owasp.org/index.php/Clickjacking. Instead, you can do a full frame authentication initially and then do I frame authentication with prompt=none to refresh the ticket. Prompt=none tells AAD that it is not permitted to stop and ask for credentials, so this flow will always work in an iframe.
As an addendum to the accepted answer, I was able to work around the issue by ensuring that the iframe URL's casing matched exactly what I'd provided in my app registration in Azure AD. E.g. if Azure AD has https://mydomain/My-App/ as one of my reply URLs then ensure that the iframe URL is spelled exactly the same way. I initially had my iframe URL as https://mydomain/my-app/my-page and was encountering the same issue described above till I changed the casing to https://mydomain/My-App/my-page.
Related
I've read that the silent authentication is typically made in a 1px iFrame. What I've been wondering is how the response to the authentication request is passed back from the iFrame to the parent application. Only option i can think of is that the Auth-Server returns some javascript code that runs e.g.
window.top.postMessage('auth', 'thisisthetoken')
But that approach seems a little sloppy to me. So how does it work?
That is the traditional flow for token renewal in Single Page Apps. The initial authentication should be done on a main browser window via a redirect, eg as for Google Sign In or Office 365.
TOKEN RENEWAL LIBRARY USAGE
The oidc client library is commonly used to implement this, enabling the iframe post to be done with very little code.
IFRAME MECHANICS
The main window triggers an OpenID Connect redirect on a hidden iframe. When a response is received, the iframe uses the postMessage API to return an OpenID Connect response to the main window, containing code and state parameters. The main window then exchanges the code for tokens, using a PKCE code verifier that it saved to session storage before triggering the iframe redirect.
BROWSER SUPPORT FOR THIRD PARTY COOKIES
The above flow relies on the Authorization Server's SSO Cookie being sent in the iframe request, but browsers are starting to drop third party cookies to limit tracking - Safari already does this.
Therefore it is now standard to instead manage renewal via a secure cookie issued for the site of the web origin, and to avoid iframe post solutions.
Projects that rely on third party cookies these days will struggle - see this recent answer of mine.
HOSTING PREREQUISITES
In 2021 you are best to use secure SameSite cookies in the browser, since posting tokens between frames is vulnerable to Cross Site Scripting. Ensuring that the web origin of each frame can share a secure cookie via a child / sibling domain is therefore a prerequisite - you cannot really develop a secure web solution these days without it.
Security in the browser is a tricky topic and needs an architectural design - for more info on 2021 web security recommendations, take a look at recent Curity Web Guidance.
WITH TOKENS ONLY
This will work buy is considered very poor security in 2021:
Redirect the whole window to authenticate the user (good)
Save tokens to local storage (bad) - to deal with page reloads - easily exploited by malicious code
Then post tokens between iframes (bad) - can be intercepted by malicious code that adds a listener
I have an Intranet authenticating by Azure AD - located at https://intranet.example.com/ (In details, its Sharepoint Online);
In some pages, we need dynamic content generated by an rest api - located at https://api.example.com/ (In details, .NET WebAPI, Owin middleware using OpenId);
api.example auth by AAD too;
Both api.example and intranet.example have Windows Azure AD permission granted through admin consent;
api.example has X-Frame-Options, Access-Control-Allow-Credentials and Access-Control-Allow-Origin enabled to https://intranet.example.com/;
What I need:
Some pages have dynamic content generated by JavaScript logic requesting data from api.example;
Users will authenticate in intranet.example and api.example must have Single Sign On behaviour.
Avoiding any type of prompt or authorize request is a MUST, since all of that must be transparent to the final user;
What I tried:
I tried to simply put an IFrame inside intranet.example pointing to app.example and it works both on Chrome/Firefox. But, IE11 doesnt allow it, since my app redirects to https://login.microsoftonline.com/ that responds with X-FRAME-OPTIONS set to DENY.
Example:
User log in Google Account, access https://mail.google.com/ and have hangouts messages up and running with no prompt or authorize request or something else, totally transparent.
User log in Microsoft Personal Account, access https://onedrive.live.com/ and have Skype messages up and running with no prompt or authorize request or something else, totally transparent.
Note1: It must works outside our domain. So, setting Intranet/Trusted Site Zone is not a option.
Note2: The more decoupled from Sharepoint, the better.
Note3: I tried this aproach PnP Webcast - Calling external APIs securely from SharePoint Framework.
I would recommend re-posting your question in SharePoint Stack
Exchange.
AadHttpClient might be a better option, but it's in preview right now and 'not supported in production tenants'. It also requires the new SharePoint Admin Center which is only available for first release tenants.
Also, I found these to be better AadHttpClient tutorials than the existing MS documentation:
https://www.spdavid.com/consume-a-secured-azurefunction-using/
https://github.com/SharePoint/sp-dev-docs/issues/1378
First of all, for the lack of words, I used "normal web page" on the title. Please let me explain that:
Recently, I have seen many websites such as shopify.com making the most out of the modern browsers support for html5 push-state and Ajax. Instead of submitting forms, requesting new pages I see them doing all that via their REST APIs. It seems to me like a very neat way to do things because it's faster (less page reload), and also allow greatly allows us to re-use the API code.
In these scenarios, the users access the service via the websites as they would normally do, however their interaction with the resources are powered by the REST APIs.
As I dive more into the documents, it seems like these API requests should be stateless yet should always have a mechanism to authorize/authenticate each request so I looked into OAuth2 for that purpose (Since I will need OAuth2 anyhow, to grant accesses to 3rd parties). Now, since in this particular circumstance the user's browser will act as the client to request the resources via REST, I want to know what is the recommended flow to do it.
Right now I plan to implement it as followed: (I'm using Symfony 2 with FOSRestBundle and FOSAuthServerBundle)
User should login via the web form as normal (Since we need to authenticate/authorize both for the normal web page as well as for the API Requests)
When the user logged in, immediately check if an OAuth client is already created for this user? If not then create it with GRANT_TYPE_IMPLICIT. If the client is already there, just retrieve it.
Proceed to normal OAuth authorize for Rest requests?
Edit 1:
Further research makes me think that I should not send back the refresh token to the JS app as this would be too dangerous if the browser is compromised. Perhaps I could store the refresh token for the user somewhere in the server backend once he/she is logged in, then can reserve a special link for the JS app to request for new access token when old one expires? This seems a bit messy to me tho.
Our ASP.NET application is hosted in IIS 7.5 and has the following setup:
main site is hosted under root IIS folder accessible with http://siteurl (1)
we have a separate app in the same AppPool hosted under http://siteurl/Intranet (2)
Main app (1) has Anonymous Authentication enabled along side Forms Authentication (url: siteurl/loginform).
Second app (2) has Integrated Authentication (NTLM).
The login procedure works as following:
User goes to siteurl first
User gets redirected to /Intranet to check integrated auth
If integrated is accepted user gets redirected back with proper auth cookies to siteurl and gets access to the site
If integrated fails user gets redirected to siteurl/loginForm to manually fill in credentials
We have some issues with Internet Explorer (8, 9, 10) that refuses to submit the form data at step 4. It appears to be a known behavior that IE will not POST content to an unauthenticated site once the NTLM negotiation started for that session. I have considered some workarounds for this:
store credentials in a cookie (with JS) and on the server if the POST content has 0 length try to check the cookie values. delete the cookie afterwards
send credentials using GET instead of POST (ugly as we need to make sure the user does not see his just posted password in the browser address bar)
Provide a link to the user to open a new tab and continue the auth process in a separate browser session (this seems to work as IE will happily send POST data from a second tab)
Are there any other options we might have to get around this issue?
From the above 3 which one would be preferable and what unconsidered pitfalls we might encounter?
I wrote about this issue here: http://blogs.msdn.com/b/ieinternals/archive/2010/11/22/internet-explorer-post-bodies-are-zero-bytes-in-length-when-authentication-challenges-are-expected.aspx
Your question omits important information which makes it hard to troubleshoot. You should never see the problem described with the literal URLs you've used, because IE uses protection spaces to decide whether a site is going to demand credentials via a HTTP/401 and example.com/ and example.com/foo/ are different protection spaces.
It would be very helpful if you could share a Fiddler log of this scenario for better troubleshooting.
I'm just curious if you can bypass the login into a asp.net website, which to let yall know, I have no control of using a unique url?
I have login credentials to the site, and tried using those to do this but to no avail.
So is this possible? Only thing I could think of was
http://username:password.awesomesauce.com/login/login.aspx
FYI I can log in fine, I just need this to login to the site via a 3rd party app. It is a major pain to login everytime with the app and sometimes many of the functions fail because its screwy with keeping an authenticated login
No, this is not normally possible.
This would be a very specific custom case - there is no standard for this as all logins are different, and authentication methods with databases etc. are all different.
FTP can work like this, but that is because the authentication is part of the protocol whereas in a web form it is not.
I used a firefox add on called Live HTTP Headers 0.17 to follow the url actions as it logins into the site. After turning on the capture option I logged in and it gave me this
Then I took the contents from the Content-Length catagory and appended it to the url like so
https://www.TROLLFACE.com/login/login.aspx?__LASTFOCUS=&__EVENTTARGET=&__EVENTARGUMENT=&__VIEWSTATE=%2FwEPDwUJMTI2OTI3NTE1D2QWAmYPZBYCAgMPZBYMAiAPDxYCHgdWaXNpYmxlaGRkAiEPDxYCHwBoZGQCJg8WAh4LXyFJdGVtQ291bnQCGhYCAhkPZBYCAgEPFgIeBXN0eWxlBQtib3JkZXI6MHB4O2QCKA8WAh8AaGQCKg8PFgIeC05hdmlnYXRlVXJsBRZqYXZhc2NyaXB0Om9wZW5DaGF0KCk7ZGQCOQ8PFgIeBFRleHQFKUNvcHlyaWdodCAmY29weTsgMjAxMiBCIE8gWCBQYXJ0bmVycywgTExDZGQYAQUeX19Db250cm9sc1JlcXVpcmVQb3N0QmFja0tleV9fFgEFGGN0bDAwJGJ0bkNoZWNrSW52ZW50b3J5MdYH4EkMQWCgv%2FEOSMLJPNZ21rMa&ctl00%24cphMain%24txtUsername=USERNAME-GOES-HERE&ctl00%24cphMain%24txtPassword=PASSWORD-GOES-HERE8&ctl00%24cphMain%24btnLogin=Log+In
Please note the USERNAME-GOES-HERE and the PASSWORD-GOES-HERE
So far it works every single time, effectively skipping the login.