Single Sign On (SSO) using JWT - cross-domain

I have read several articles about sso but could not find an answer in my mind.
I have a scenario like below:
Scenario:
My company wants to have sso mechanism using jwt.
Company has 2 different domains like abc.com as abc and xyz.com as xyz.
Also there is a masterdomain that manages clients authentication.
User X wants to log in abc at first.
abc sends credentials to masterdomain and masterdomain authenticates user then create a signed jwt in order to send back to abc.
abc keeps this jwt in a cookie.
After a while if a login to abc is attempted at the same computer, system does not ask for credentials and automatically login the user.
Question:
If user tries to open a page in xyz domain, how does the system understand that the user loggedin before? I mean xyz domain cannot reach the cookie of abc which has the jwt. What information should be sent to xyz that indicates the user X is trying to login?
Thanks in advance

You can store the JWT authentication token in a cookie / localStorage of a intermediate domain connected to the home page using an iframe
Scenario
abc sends credentials to masterdomain and masterdomain authenticates user then create a signed jwt in order to send back to abc.
abc masterdomain keeps this jwt in a cookie.
After a while if a login to abc is attempted at the same computer, system does not ask for credentials and automatically login the user.
Finally when the user enters in the second domain xyz, the jwt is recovered from masterdomain storage using the iframe, and automatically login the user
CORS is not a problem because masterdomain.com have access to its storage and communication between iframes is allowed if origin and destination are recognized (see http://blog.teamtreehouse.com/cross-domain-messaging-with-postmessage)
To simplify development, we have released recently an opensource project cross domain SSO with JWT at https://github.com/Aralink/ssojwt

Related

Unique identifier in SSO authentication

Good afternoon. I'm creating an authentication system via SSO.
Let's imagine the following scenario:
Site A: forms.nso.imm.pt (Backend: backend.forms.nso.imm.pt)
Site B: auth.aettr.pt (Backend: backend.auth.aettr.pt)
Now I want a user, when clicking on the login button on Site A, to be redirected to the Authentication portal of Site B, and with that the user can authenticate himself with the credentials of Site B. For this, when the user clicks on login , currently the frontend of Site A, communicates with its own backend, which in turn communicates with the backend of Site B, to create an authentication session token, then this token is returned to the backend of site A, and in turn returned to the front-end of site A, which in turn forwards the user to the frontend of site B, passing the previously created token via query string. But it just creates a vulnerability. If a malicious user creates an authentication session, copies the URL and sends it to the victim, the victim sees that the site is real, because it is indeed an authentication, and logs in, then the malicious person just needs to refresh and he will be redirected to site A with the victim's session.
For that I've already tried on the backend of site B along with the authentication session token, create a uuid, record it in the database along with the token, and automatically create a cookie with that uuid. So when the user opened the front-end of site B and it was going to validate the authentication session token, if the cookie did not exist or was not the same as what is in the database, he would not let the login, because a victim would never have the cookie created at the time of the authentication session token creation process. But unfortunately this is not possible because I cannot create the cookie, because the user "does not open" the backend domain of site B.
Does anyone know any other ways to resolve this vulnerability via cookies, or otherwise? (Except browser fingerprint)

Sign in with Apple Security for Linked Account

Assuming you have an existing user management/database on a web platform. Sign in with Apple should be integrated for a quicker login and registration process – although it will always create a regular account linked to an email address (just without a regular password). Is it safe to use the (validated) JWT provided by Apple for authenticating?
Signing in (existing account) would be the following steps:
User taps on "Sign in with Apple" in an app
the generated JWT from Apple is sent to the authentication server
the server validates the JWT using the public keys provided by Apple's API endpoint
the server extracts the email from the (validated) JWT and if a user with that email exists, this user is signed in (API returns internal access/refresh token for the session)
I try to craft an answer for iOS apps. But first clarify the question:
"Is it safe to use the (validated) JWT provided by Apple for authenticating?"
The only known JWT we receive from an Authorization task is the "id_token". Other parameters may be JWTs as well, but these are opaque for the client.
The question is now, if we send the id_token to the app server, is it sufficient to just validate the id_token to hand out the client an access token for the app server's domain? Answer: NO!
When using Apple's Authentication Framework for iOS for Sign in With Apple, the Authorization task returns an ASAuthorization value in the completion handler. This contains basically the following parameters:
user: an identifier
identityToken: JWT the "id_token" (see OIDC)
authorizationCode: A short-lived, one-time valid token that provides proof of authorization to the server component of the app. The authorization code is bound to the specific transaction using the state attribute passed in the authorization request. The server component of the app can validate the code using Apple’s identity service endpoint provided for this purpose. *)
*) If that value does correspond to the OIDC "code" value which will be obtained by a client via the "front channel" aka user agent aka browser, then we should also ensure that an additional mechanism is in place which actually provides a secure "proof of authorization" (Universal Links, PKCE), see Authorization Code Interception Attack.
If these attacks are technically impossible, because the authentication system provides secure communication channels with the app, we don't need PKCE, though.
The id_token contains information about the user that has been authenticated which is stored on the Provider. It's a signed JWT. Even if the JWT can be successfully validated, with the JWT alone the app server cannot be sure that the sender is the one who it believes it is. We don't want to give anyone an access token who is not authenticated!
The app server needs more prove and this will be accomplished with the authorizationCode parameter. This check has to be done on the Provider though.
So, we have to perform two steps:
Verify the Identity Token (id_token)
This will be performed on the app server.
Validate Authorization Code
The second step will be accomplished by your app server obtaining a refresh token form the Providers special endpoints.
With Step 2 we receive a TokenResponse.
If this was successful, we receive an access token and a refresh token. The access token is of no use, but we need the refresh token:
"You may verify the refresh token up to once a day to confirm that the user’s Apple ID on that device is still in good standing with Apple’s servers."
Store this on your app server.
Once after this is all done on your app server you proceed with:
Manage the User Session
After verifying the identity token, your app is responsible for managing the user session. You may tie the session’s lifetime to successful getCredentialState(forUserID:completion:) calls on Apple devices. This is a local, inexpensive, nonnetwork call and is enabled by the Apple ID system that keeps the Apple ID state on a device in sync with Apple servers.
A "User Session" will likely require a domain specific access token and refresh token. You will likely verify again Apple's refresh token when the client requires a new access token on your token endpoint.
So, the last step is your app sending the domain specific access token and refresh token to your client.

Many users in account and app integration

When performing authorization code grant authentication, Docusign asks for consent. Far as I understand documentation, it's user's consent implied. When first asked for consent, the user is required to enter his credentials. When asked next time, the same user is implied.
However consider this: I have 3 users listed in my demo account wowproj.dev#gmail.com: the wowproj.dev#gmail.com himself, and Mary#inbox.ru, and Mike#inbox.ru.
I need to know two things:
1) whether I can statically specify the user when doing code authorization (say, by his "api username"), so that the user only has to press some "OK" button (maybe enter his password as well, but not username) - is that possible? Otherwise it may be possible that I have users "Mike" and "Mary" in my app, and I have those users in my Docusign account as well; then "Mike" user of my application starts some action, but when asked for consent, "Mary" enters her Docusign credentials and gives her consent. I want the consent to be Mike's! For example, I could store Mike's api username in his account in my app, and use it in grant authorization. But, far as I can see, neither user's login nor his api username are sent to Docusign in authorization code request, so I don't see how to achieve what I want.
2) When asked next time, the same user is implied - that may be a problem. What if my "Mike" from former paragraph, when asked by Docusign, enters his credentials and gives his consent, then he logs out from MY application and Mary logs in instead and starts some action involving Docusign; will Docusign assume it's still Mike's session? How do I make sure this does not happen?
I don't understand your question's use of "implied." Here's how it works. If you have further questions, please update (edit) your original question.
When using authorization code grant, the user first authenticates himself to DocuSign.
Then, if he hasn't previously done so, he is asked to grant consent to the integration. The permissions requested by the integration are the integration's scopes. The usual scope is signature, there can be others.
After he grants consent, the DocuSign authorization service will redirect the user's browser back to the integration, and and an authorization code will be included as a query parameter.
Your integration then makes an oauth call to DocuSign to exchange the authorization code for an access token.
Next (typically), your integration uses the OAuth::getUserInfo method to obtain the user's name, email, authorized DocuSign accounts, and more from DocuSign.
Ensuring that your app's user is the DocuSign user
You can't force who will be authenticating with DocuSign. But you can check that the right person authenticated. For example:
Mike logs into your app. You know Mike's email.
Your app wants its user to authenticate with DocuSign. Your app initiates the OAuth Authorization Code Grant flow with DocuSign.
Your app's user now sees the DocuSign login screen.
(The problem) is that Mike asks Mary to authenticate with DocuSign for him. Mary does so.
But when your app learns the email address of the DocuSign authenticated user, it will be Mary's email address, not Mike's. So your app can reject the DocuSign authentication by posting a message to the user saying that Mike must re-authenticate with DocuSign.
By implementing the above, your app can guarantee that when Mike is logged into your app, the matching authentication with DocuSign will be Mike's DocuSign user account, never someone else's.
Instead of comparing email addresses, you can also use the DocuSign user id. But doing so requires that you go through a step of loading your app with a table that associates Mike's account and his DocuSign User ID. Email addresses are probably easier.
Re: other people logging in after a prior session
There are two cases:
Same browser on the same machine
This is the "public computer problem."
Mike uses browser "G" to login into your app and also into DocuSign. Later Mary slides into his seat and uses the same browser and same application.
By default, DocuSign's OAuth Auth Code Grant enables Silent Authentication. This means that the DocuSign auth flow will silently enable Mary to use Mike's DocuSign session (if it is still active). For a public machine scenario, this is obviously not good. Solutions:
Always require DocuSign to actively authenticate the person (no Silent Authentication allowed). To do so, include prompt=login in the initial URL sent to DocuSign. See docs.
Clear the browser's cookies between users. The standard methods for handling public computers will include this.
Different users on the same app
Your app should use sessions. Each user of the app (in parallel or sequentially) will get their own session. Each session should maintain its own authentication information for DocuSign including the current user's access token, account id, and base url.
All of that information is determined as part of the authentication-with-DocuSign process.
These days, all modern web app frameworks provide easy to use session interfaces.
We also have code examples you can use. See this repository list. (With more on the way.)

Is this password-less auth flow secure?

I'd like to implement a passwordless auth flow for my mobile app that only requires a user clicking a link in their email to log in. Similar to how Slack handles auth. I'll be using node and jwt for this implementation.
I think I've come up with a secure design, but I'm sure I'm missing something. I would love some critique from the community 🙏.
Here we go:
User opens the mobile app.
We check to see if user has a token in their local storage.
If they do, we add that token to their headers and send to the home page of the app.
Else, we prompt them to enter their email to get started
When they click "Submit", we POST that email address to the requestMagicLink endpoint on our server.
The server checks the database for a user with that email address
If we find a user with that email, we take the id from that user
If the user does not exist, we create a new user, and get that id
We use JWT to generate a token with the id, and our secret that expires after 1 hour
We send that token to the user via a link in an email.
Upon being clicked, that link sends a GET request to our server at the magicLogin endpoint with the token in a query param
We verify that the token is correct using JWT and our secret.
If it fails verification, we redirect the user to the screen where we prompt them with their email to get started.
If it's successful, we generate a new JWT token using their id, and our secret that doesn't have an expiration, then pass that back to the user in the params of a URL that redirects them to a success page in our app.
The app takes the token from the param and stores it in local storage until the user chooses to logout, and the user is redirected to the home page.
The requests to the api all now contain the token in the headers, and the user is good to go.

Refresh token not returned for Office365 accounts purchased through GoDaddy

Background
We have a feature that syncs calendar entries and contacts between our application and Office365, using the Office365 REST apis outlined here. We are using Version 1 of the API. For authorization we are performing authorization via Azure AD as outline here.
Problem
In the normal case (when using Office365 accounts purchased directly from Microsoft), our system works as expected: we are able to refresh the user's tokens when they expire and are returned a new access and refresh token in exchange.
In the second case, when testing with Office365 accounts purchased via GoDaddy, we encounter a blocking issue that can be outlined in this series of steps:
1. User is sent from our app -> Office365 Login page.
2. User enters email address
3. User is redirected to GoDaddy Office365 login page.
4. User completes authorization, and is redirected back to our app with an access code in the response.
5. App exchanges access code for an access_token and refresh_token from Office365.
6. Some time goes by, and access_token expires
7. App refreshes the user's access_token using the refresh_token
Expected Behaviour
At this point we are expecting to receive a new access_token as well as a new refresh_token, as we do when using a regular Office365 account
Actual Behaviour
Only for accounts purchased via GoDaddy, we do not receive a new refresh token in the response after refreshing for the first time.
Obviously when intending to have a long-running sync, this is a breaking case as the user will no longer be able to have their tokens refreshed beyond this point.
Postman traces (can save as .json and import to Postman for debugging
https://gist.github.com/drunkel/7ec66ed33f66d0070148694651699d03 (IDs and secrets have been removed)
Question:
Is this a known issue?
Is there a workaround?
I am a Software Engineer at GoDaddy and can confirm that this issue has been resolved. The reason for more frequent login requests under Modern Authentication is that as these are federated users and as you mentioned in your question, the refresh token was not being returned. This was caused by the StsRefreshTokensValidFrom attribute on the AAD user not being updated properly.
Every provider can decide how to implement its own oAuth server with certain policies on how to act with certain grant type and policies about granting/revoking refresh tokens/id tokens/access token and their lifetime properties.
This is a known issue with go daddy when purchasing office 365 accounts. see here and also here and here.
So it seems like GoDaddy decided to implement their OAuth server with a restricted security policy about refresh tokens by not enabling and not sending back a refresh token to the API calling the OAuth authentication and authorization when you purchase office 365 accounts through GoDaddy.
This is security enhancement/block to disable your application not to hold a lifetime refresh token that can be lived forever (if refreshed) to these office 365 accounts purchased on Godaddy
Usually, OAuth servers implemented with integration with Azure Active directory have the following token lifetime (but you can change and decide to override configure them differently 3rd party implement their own server with their own policies about tokens)
Another important featurw which Go Daddy does not support multi-factor authentication(mfa) for office 365 accounts found here.
Azure lifetime policies:
Azure Active Directory Configurable token lifetime properties
Another important issue is that if you want to be able to continue to refresh the token while the user is offline you must ask the user for access_type="offline", so during a time of inactivity from the user, you can continue to refresh the token and to hold long lifetime token for the account.
If the user decides to revoke the token for any reason - the token immediately expires.
Another issue in the steps you described is:
User is sent from our app -> Office365 Login page.
User enters email address
The user is redirected to GoDaddy Office365 login page.
so now the refresh token for office 365 flow from server to the hands of Godday servers.
User completes authorization and is redirected back to our app with an access code in the response. (but without the refresh token obtained the in the last server to server step. Godaddy to keep security on behalf of 365 accounts keeps it to itself and not returning it to the end user.
The app exchanges access code for an access_token and refresh_token from Office365. 6. Some time goes by, and access_token expires 7. App refreshes the user's access_token using the refresh_token

Resources