Goal
Embedding a form on a 3rd party website that can use 1st party authentication (OIDC, keycloak) to post to 1st party service protected via bearer token. (Think of something like a comment form à la disqus.)
It is acceptable that this flow does not allow refreshing the oidc bearer token.
Concerns
Prevent clickjacking
Control 3rd party websites allowed to use the form
Isolate user info and token from 3rd party website
Approach
My current understanding leads me to believe that the concerns are addressed:
To prevent clickjacking (like in iframe-embedded auth), the whole login procedure is performed in a new popup window (see image: open popup).
Checking for window.opener and checking window.opener against a pass-list (see image: login landing) should ensure that login-landing is not embedded in an iframe, is not navigated to directly, and is only accessed in a popup from authorized websites.
Checking the targetOrigin of the window.postmessage command should ensure that only authorized websites can successfully use the form.
Having the web component (see image: 1st party web component) use an internal, sandboxed iframe from srcDoc to perform all user-info or token related actions should shield user-info and token from access by the 3rd party website. Using the internal iframe should also ensure, that the 3rd party website using the web component cannot intercept the postmessage event.
Questions
Is the approach secure?
Is there a more standardized approach to this problem which I do not know of?
Thank you for your input!
Ok, you learn something new each and every day.
I learned, that the approach i followed as shown in the question is not secure.
An iframe which is populated via srcdoc does obviously not count as a cross-origin frame. Well, d'uh. This means it is no viable protection against the 3rd party website. This isn't getting any better by slapping a sandbox attribute on it, as this is intended to provide protection in the other direction (protect 3rd party website from iframe content).
What might work is using a web component which includes a cross-origin iframe. But why bother the embedding 3rd party website with including a) a script and b) a web component tag? I can see no real benefit (in my use case) over simply using a cross-origin iframe.
The popup-remedy for clickjacking on the other side is a must.
This question & answer were sponsored by the 'first try to understand your tools before developing eleborate plans'-committee.
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 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.
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.
I want to show my site inside the Salesforce iFrame, and when someone does any activity on my site inside the iFrame i want to update the Salesforce page as well?
Can i do this, please suggest?
A more recent approach, now GA, is Force.com Canvas - this is iframe-based with additional security and features, including passing context such as Salesforce fields into the iframe. It also enables the iframed web app to call Salesforce APIs based on that authentication, either via JavaScript or by your backend web app (Python, Java, etc) using the credentials from the Canvas framework.
You can also use the new Events feature within Canvas to send messages to the framing Salesforce page.
On authentication options: the Canvas Signed Request authentication is recommended and easy, but note that it doesn't give you a refresh token for OAuth2 - so if you need to do continuing background processing based on the user's authentication, you should use the Canvas OAuth2 authentication instead, which does give you a refresh token.
Overall, Canvas is cleaner than using a raw iframe and should simplify development. It does require some changes to authentication in the iframed web app but they are relatively simple (10 lines or so of Python).
You can use Javascript and page refreshing to send information to the parent window, but this isn't in my recommended books. iFrames are not meant to communicate with it's parent site for security reasons.
You might want to look into using an API to communicate between the two sites.
Scenario:
We provide a hosted site that clients pay to use internally (a tool to support their business workflow). We have a requirement to provide a form that the clients can 'embed' in their outward facing site. This form will permit a member of the public to enter some details to register an interest - this data will be pushed to our remote system.
Question:
I'm currently planning on creating a simple HTML page that the client's web guys can include in on their site with a minimum of technical knowledge required (either using an iframe or an object tag). If I do this, am I going to run into difficulties when the user tries to submit the embedded form (as it will be going to different domain to the one they are currently browsing)?
I had a look at google adsense and I see that they just provide a link to a JS file that renders their ads - I'm not sure I see the advantage in this, but if anybody has any bright ideas...
Whatever technique that gets used, I'll have to authenticate the request as coming from my client's site(s).
If I do this, am I going to run into difficulties when the user tries to submit the embedded form (as it will be going to different domain to the one they are currently browsing)?
No issues, you'll need to hardcode a post url into your form.
Whatever technique that gets used, I'll have to authenticate the request as coming from my client's site(s).
That may be difficult. The request will be coming from the user's browser.
You could make it a requirement for a client to define some JavaScript variable in the containing page, like:
var client_id = '2315213452';
and attach a script to pick up this value and submit to your server along with the form.
The trouble is, any hacker could see this value in plain text and take it thus compromising your security.