IdentityServer4: should GetLogoutContextAsync return multiple SignOutIFrameUrls - asp.net-core

While implementing a federated single logout (SLO) in our application using IdentityServer4, I was surprised to see only a single SignOutIFrameUrl property in a LogoutRequest object returned by GetLogoutContextAsync method.
Shouldn't it be a collection of iframe URLs for every registered client? We want to trigger a logout for all clients obviously, so we need to generate an iframe per client resulting in multiple iframes rendered on "logged out" page.
I may be missing something here, as documentation mentions "clients" but then describes only a single SignOutIframeUrl property.

IdentityServer keeps track of the clients that you have actually logged into and returns a list of SignOutIFrameUrl for those clients. If you haven't visited a particular client after single log-on, then it won't be contacted for logout by design during single log out.
Just happened to be looking at this again in a project today and I forgot one detail. The logged out page renders 1 iframe and the URL is the endsessioncallback endpoint, then the page in that iframe renders many iframes, one for each logged in client. So you would not see the list version until you looked at the code to render the inner iframes.

Related

Get auth from topdesk site being opened within iframe

I have a topdesk board, and connected external site via url, so my site is opened within iframe from topdesk.
My task is to get logged in user (at least its email) from topdesk.
I have found so far this api: https://developers.topdesk.com/explorer/?page=supporting-files#/Persons/getLoggedInPerson
But it requires to get auth key from login/password, but I do not user login/password from UI side.
So I do not see any straightforward solution, but maybe I could somehow to get cookie from parent topdesk view or something like that.
What you're trying to do is not possible, as it would be an enormous security vulnerability. If you would be able to go into an iFrame and interact with the embedded document, or go from an embedded page to its parent and interact with its document (e.g. retrieve the session cookies, read the contents of the page, etc.) it would become really easy to take over someones session and do malicious things.
For instance, as a malicious actor, I could place a small iFrame on my site in which I load TOPdesk. If you were already logged in into TOPdesk, you would also be logged in in the iFrame on my site. If I would be able to interact with the cookies within that iFrame, I could retrieve your session cookies and impersonate you.
Therefore, you can only interact on this level with iFrames that are on the same origin, also known as the Same origin policy.
However, even if you would be on the same origin, you would still not be able to read TOPdesk's session cookies, because they are set to HttpOnly. This means the cookies can only be used by the browser itself, and can't be interacted with from JavaScript.
So, to me, it sounds like the only option you have is to interact with the API in the official way, through an application password. However, that might not be possible with what you are trying to achieve. Can you tell a little bit more about your use case?

Keycloak - Multi/2FA Factor - OTP - QR Code - Custom Login Screen - Rest API

I have my own Login page where user enters username/password.
This username/password are used to login through Keycloak Rest API.
http://localhost:8080/auth/realms/Demo/protocol/openid-connect/token
input - {username,password,grant_type,client_secret,client_id}
And in response i get access token.
Now i wish to enable Authenticator (Google Authenticator). I have enabled it from backend. Now if user wishes to login thorugh my application, my login page i need to get below details.
1.) Somehow i need to include QR Code that appears on keycloak login page post username/password validation to show on my login screen for the first time login once user enter username/password. So do we have any API which return Keycloak QR code image in response.
2.) Subsequent login i will have OTP field, so need a REST api to pass OTP along with username/password.
Please help with REST API if keycloak has any. Integrating through Javascript.
Similar flow as described in use case 1 here
Just want to use keycloak as a database, doing all operation for me, input will be my screen. I do want redirection of URL's while login in and yet should be standalone deployed.
I've managed to implement this through the rest API of Keycloak. To realize this, you need to extend Keycloak yourself with a SPI. To do this create your own Java project and extend org.keycloak.services.resource.RealmResourceProvider and org.keycloak.services.resource.RealmResourceProviderFactory. You can find more information in the official docs (https://www.keycloak.org/docs/latest/server_development/#_extensions), github examples and other stack overflow posts how to do this.
Once you got this up and running you can implement it like this:
#GET
#Path("your-end-point-to-fetch-the-qr")
#Produces({MediaType.APPLICATION_JSON})
public YourDtoWithSecretAndQr get2FASetup(#PathParam("username") final String username) {
final RealmModel realm = this.session.getContext().getRealm();
final UserModel user = this.session.users().getUserByUsername(username, realm);
final String totpSecret = HmacOTP.generateSecret(20);
final String totpSecretQrCode = TotpUtils.qrCode(totpSecret, realm, user);
return new YourDtoWithSecretAndQr(totpSecret, totpSecretQrCode);
}
#POST
#Path("your-end-point-to-setup-2fa")
#Consumes("application/json")
public void setup2FA(#PathParam("username") final String username, final YourDtoWithData dto) {
final RealmModel realm = this.session.getContext().getRealm();
final UserModel user = this.session.users().getUserByUsername(username, realm);
final OTPCredentialModel otpCredentialModel = OTPCredentialModel.createFromPolicy(realm, dto.getSecret(), dto.getDeviceName());
CredentialHelper.createOTPCredential(this.session, realm, user, dto.getInitialCode(), otpCredentialModel);
}
The secret received with the GET must be send back with the POST. The initial code is the one from your 2FA app (e.g. Google Authenticator). The QR code is a string which can be displayed in an img with src 'data:image/png;base64,' + qrCodeString;
I know this is an old question, but I've recently been looking at something similar, and so thought it would be potentially valuable to share what I have found for others who may be looking into this and wondered what the possibilities are.
TL;DR
You can only really use the existing Keycloak actions to do this or embed the user account management page found at https://{keycloak server URL}/auth/realms/{realm name}/account in an iframe. That's it, I'm afraid. In my opinion it is currently best to just assign actions directly to accounts or use the Credential Reset emails to assign actions; both of these can be done via the Admin API if desired:
Send Credential Reset email containing assigned actions:
https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_executeactionsemail
Set actions directly on the account (include the actions in the requiredActions portion of the user JSON that you send in the body to the endpoint):
https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_updateuser
Background is that as part of a project that I have been working on we wanted to see if we could have an integrated way for users to set up their initial password and OTP device when a new account has been created for them, since the default method of sending them an email from Keycloak using the "Credential Reset" functionality has the limitations that a) it doesn't provide a link to the application itself unless you override the theme, and if you have multiple instances of the application for different users you have no way of knowing which instance to provide the link for, so may have to end up including a list of them, and b) it often doesn't feel truly native to the application, even with changes to the theme. If you're sensible though, I'd suggest you stop and just use this functionality - please see the TL;DR section above for details.
So, in short there is NO API endpoint for receiving a QR code to set up an OTP device. There are two places, however, where the QR code can be retrieved from - the OTP device setup screen when you log in as a user who has had a "Configure OTP" action assigned to their account, and the user's own account management screen.
The first option of the Configure OTP action screen is a non-starter. It only shows up when you log in, and so by definition the user has to log in to Keycloak via the Keycloak login page in order to trigger the page to be displayed. At this point you're already on a Keycloak page instead of one of your app's pages, and so unless you can get very creative with changes to these Keycloak pages via a custom theme, tapping into this page isn't really an option.
The second option is more interesting, but far from ideal. Every user who has logged in has access to an account management page that can be found at https://{keycloak server URL}/auth/realms/{realm name}/account. This page allows you to do things like change your name, password, etc. and it also allows you to add an OTP device if you don't already have one, or delete any existing OTP devices associated with your account. This OTP device tab of the account management page can be reached directly via https://{keycloak server URL}/auth/realms/{realm name}/account/totp.
As I mentioned, there isn't an API that you can access to view the QR code that shows up on this page. The only way it is accessible is via the GET request to https://{keycloak server URL}/auth/realms/{realm name}/account/totp, which returns the HTML for the page I've already mentioned. Okay great, so can we scrape the QR code programmatically and then put it in our own page on our application? Err, no, not really. You see, whilst a lot of the Keycloak API endpoints rightly allow you to send a bearer token (e.g. access token) in the authorization header to access and endpoint, this page will not accept a bearer token as a means of authentication/authorization. Instead it uses a session cookie that is locked down to the Keycloak URL. This cookie is set when you log in to your application via the Keycloak login page, and so is available to this account management page when you navigate to it, having already logged in, and since the account management page uses the same server and domain name as the original Keycloak login page, it has access to the cookie and can let you in. This cookie cannot be sent by your application to e.g. your own REST API to then programmatically call the account management page and scrape the QR code, because your application doesn't have access to it for good security reasons. This might be something you can change in Keycloak somewhere, but if there is I would strongly recommend against changing it.
So if we can't scrape the page from our own server, can we do something on the front-end? Well, as mentioned, your application doesn't have access to the session cookie but if you make a request (e.g. using fetch or axios) in your front-end JavaScript to the account management page then that request will send the cookie along with it, so that could work right? Umm, well actually you will get hit with an error message in this scenario due to CORS. CORS is Cross-Origin-Resource-Sharing and in order to allow the Keycloak page to be accessed then you would have to open up the settings on the server to allow it to be accessed from your website's address. I've seen some articles that look at how you can open up your CORS settings on Keycloak if you wish but I'd be very nervous about doing this. I don't know enough about the internals of Keycloak and how it operates to comment on how much of a security risk this is, but I certainly wouldn't recommend it. There some information here (Keycloak angular No 'Access-Control-Allow-Origin' header is present) on changing the "Web Origins" setting of your application's Keycloak client, but this opens up your application to some serious potential abuse. There is also the MAJOR issue that even if you scraped the QR code, the device isn't actually added to the user's account (even though it appears in the authenticator app) until you enter a code into the page that the QR code is on and click Save. Since there isn't an API endpoint that you can use to complete this operation, I therefore don't think that this option is viable either. I've tried out whether or not you can use the token retrieval endpoint at https://{keycloak server URL}/auth/realms/{realm name}/protocol/openid-connect/token to see if making a request with your username/password/otp code will somehow "register" your device and complete the process, but although you can get a token this way, and it doesn't complain about the otp code, it doesn't actually take any notice of the code because as far as it's concerned the user's account doesn't have a device registered with it. So we have to use the form on the account management page in order to complete this registration process.
So the final way of possibly doing this is.... an iframe. Sorry, yeah it's rubbish but that's all your left with. You can have the iframe point at your account management page, and because the user is logged in then they will be able to see the contents from your application's page. You can use relative positioning, fixed width and height and remove scroll bars to ensure that you ONLY show the QR code and the fields for the one time code, device name, and the Save/Cancel buttons. This, sadly, seems to be the only option at the moment, and due to how nasty and unreliable iframes can be in general - they certainly don't feel native to your application, and you'll need to override your Keycloak theme to get the page in question to look more like your app - I'd recommend steering clear of this and using the standard approach of using Keycloak actions and the Admin API instead.
If you've made it this far, congratulations, you win at Stack Overflow :-)

OpenGraph API User Object Sometimes Returns Link that 404s

In my application I allow users to connect their Facebook accounts via oauth for the purpose of posting via our interface. We support both page accounts and regular accounts that simply manage pages.
We also inspect the result of the opengraph API call to get a valid URL to their profile, or page. The primary endpoint we use is https://graph.facebook.com/me (with oauth credentials). For some page-only accounts, the returned object has a 'link' value that, when entered into a web browser, 404s.
The bad URLs I have seen fall into two distinct cases:
The URL can be of the form 'www.facebook.com/{page_id}' which 404s on some pages, but not others.
The URL can be of the form 'www.facebook.com/profile.php?id={user_id}' which more often than not 404s.
The only URL format I have seen that works for all accounts is www.facebook.com/profile.php?id={page_id}. In the first case, we detect that the 'link' field isn't of the proper form (using profile.php?id=...), and construct a URL with the proper structure, and it works.
My next heuristic I'm considering adding is to see if the URL is of the proper form....but uses the {user_id} as the id argument to profile.php, and just construct the URL using the {page_id}. Obviously, this is getting ridiculous.
So, is there a good way to know if an account will give back a link that is invalid? Is this a bug in the API? What is the most reliable way to, given a User on the open graph API, to get a working link to their profile/page?
Using 'www.facebook.com/{page_id}' or 'www.facebook.com/profile.php?id={user_id}' will always work - they are both the same. The only reason you'll see a 404 is if the Page has been unpublished / deleted or if the user has deactivated their account.

Omniture sends double metrics on Safari browser

I use omniture analytics for tracking when is product is viewed when shopping on the site.
These reports are send normally in FF, Chrome, IE browsers, but somehow when using Safari the reports are send twice.
Safari isn't making two calls, what is probably happening is that it is making one call and that call is responding with a redirect that is then making the second call. It could be that for some reason Omniture is logging the call as two entries, but that would be stupid.
Any suggestions?
Make sure you have Safari set to accept cookies, it will come by default blocking third party cookies.
If you are using a third party cookie and they are blocked, then you could see two calls on each page view. The first one will have a 302 status and the second one a 200 status.
When SiteCatalyst loads, it checks for a cookie with a unique visitor ID in it. If the visitor has never been to the site before, or they have cleared their cookies, then the visitor ID cookie will not be found. In that case the call to SiteCatalyst gets redirected back, this time with a visitor ID value that was generated by the Omniture servers.
If the browser accepts cookies, then that vistor ID value is written to one (named s_vi), and referenced from there on each additional page view or other custom action that SiteCatalyst will record.
If that cookie could not be set, then its possible to see the multiple calls (one 302 and one 200) on each page view. But don't worry, when that happens you are not getting hit for multiple page views. Your analytics data will not be inflated because of this.

Redirecting back to a page after authentication through OpenID, Oauth, or Facebook Connect

I'm allowing users to login to my site with either OpenID, Twitter OAuth or FBConnect. If the user attempts to go to a page that requires them to be logged in, after that user logs in I want to send them BACK to that page. Is there an easy way to accomplish this with all of these or should I simply just write the redirect page to a cookie and upon a successful login send them to that page? I'm using Django so if there are any nice tips or tricks involving that specifically that would be great.
Thanks for the input in advance!
You could thread that parameter (the page they were at) through as a parameter to your return_to. As noted in the spec:
Note: The return_to URL MAY be used as a mechanism for the Relying Party to attach context about the authentication request to the authentication response. This document does not define a mechanism by which the RP can ensure that query parameters are not modified by outside parties; such a mechanism can be defined by the RP itself.
For example:
def sendOpenIDCheck(...):
# after getting an AuthRequest from Consumer.begin
return_to = oidutil.appendArgs(return_to,
{'destination_url': that_place_they_tried_to_go})
return redirect(auth_request.redirectURL, realm, return_to))
def handleReturnTo(request):
# after doing Consumer.complete and receiving a SuccessResponse:
return redirect(request.GET['destination_url'])
If there's some other state you need to track (like POST data), or you have an extraordinarily long URL that you can't fit in as a query parameter, or you need to have the destination_url tampered with by the user, you store that information server-side, send the key as a query parameter instead of a URL, and look it up when they get back.
Not very different from storing it in the session, unless the user's got several simultaneous tabs in one session that run in to this, and then having it in the query helps.
Sadly OAuth and OpenID aren't really aware of your app states (while OAuth WRAP can be). So you have to take the following assumption:
The user will complete the sign-in WITHOUT switching tabs/windows or doing other requests on your site.
Then you can do the following:
Once you detect the access of a protected site, store the full query in the session. This won't work at all if it's a POST request, you have to prepare for this problem (show them a warning site with a lik that they must login first).
Store a timestamp of when this request happend.
On your OpenID callback check whether the session variables are set and redirect the user to the stored query. Check the timestamp (don't redirect if the timestamp is older than 5 minutes or so). After that clear both variables from the session.
This will lead to odd behaviour if the user violates against the assumption, but I don't think there is any way you can circumvent that.