Use openid connect as SSO for multiple applications - security

I am trying to use openid connect to create a SSO for my application.
Basically we have one API layer and different Apps (clients) will consume the service of this layer.
To start with we added OAuth2.0 for authorization for each of the different apps; for authentication we are currently using our own database (IDP)
We want to the end users to have a single sign on experience for this flow.
To have that we added openid on top of the OAuth flow we have built.
The web server has the standard oauth + openid implementation and has the following
Explicit Flow
Implicit Flow
Password Grant
On adding the openid connect, the server now has the ability to send the id_token (jwt) as well, depending on the scope and request type
There are two clients registered (C1 & C2)
Step 1: C1 follows explicit flow and uses response type as code, so when a user (U1) access C1 it is redirected to the authentication server where U1 enters the credentials.
Step 2: The authorization server validates the credentials and prompts for the user consent, confirming which sends out the code to the redirect_uri of C1
Step 3: C1 then requests for a token and the server gives out an access_token and an id_token; the access token being persisted in the database
Step 4: U1 now needs to access C2
Questions:
What would be the best way/practice for C2 to get the access token from the api sever without having the user to login again.
If C1 passes the jwt id_token to C2 through local-storage or any other means, one possible way would be to exchange the id_token for an access_token following this.
If we go with the above approach, would it be sufficient to just verify the id_token and issue the access_token or should we add any other check
Any other approach.
Thanks

Choice of flow will depend on type of client. For example, client could be a native application, which can use Authorization code flow (I guess you refer Explicit Flow to this flow). Or JavaScript application which solely run on browser.
Solution - Session cookie based SSO
In OAuth 2.0 (including OpenID Connect), end user (ex:- U1 as in your example) authentication occur through browser interaction. In this process, usually authorization server establish establish a session. What this session does is keeping a reference to previously authenticated user. For example, this allows user to access IDP and update user contented there (if your identity provider support such).
Now, if both applications use the same browser (ex:- Microsoft Edge) to show the login screen in authorization request of OpenID Connect, your authorization server can check session cookie existing with the browser.If this is the case, authorization server can skip login screen and response with relevant content. Following is sample scenario with a fresh login
C1 initiates authorization request, and redirects to the authorization endpoint
Authorization server receives the request and check for a session cookie.
Since this is a fresh start, no cookie exist, so login is present
User U1 login
Authorization server send response and C1 complete token obtaining
C2 initiates authorization request, and redirects to the authorization endpoint
Authorization server receives the request and check for a session cookie.
Since we are using the same browser, session cookie exists and correlated user is identified as U1
Authorization server send response and C2 complete token obtaining
Now both C1 and C2 have U1 user logged in to them. This is browser based SSO.

if those clients are web applications the best and recommended flow to use is the Implicit flow for some reasons:
it is secured in that case if you compare it with the hybrid flow, because the hybrid flow share a secret key with the clinet, and the client have to keep it, this key is used to generate acces_tokens, on the other side Implicit flow provide an access-token directly to the client with a short life time, so the best is to provide an access_token with short life time than an authorization key with long lifetime that can be used to generate tokens.
SSO (single sign on) will work perfectly between client that share the same STS server, so C2 can have an access_token without authentication, here is the steps:
C1 authenticate and get and id-token and an access_token
a session cookie is created for this active session
C2 click login and will get directly an access_token without authentication because the browser will send a valid cookie created from C1 authentication

Related

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.

How does OpenID Connect deal with service chains?

Background
Say I have an application to create shipments. A user sits down in front of that application and loads the page. They are then redirected to the IDP to login. The flow I use is the Authorization Code flow. This involves a Client ID and Client Secret. The IDP can take those values along with the User's Credentials and do the login.
After the login, the application gets an id_token that lets the application know who the user is (authentication).
The application then needs to call a service (we can call it Service 1). The application can pass the id_token to Service 1 as a JWT bearer token.
Service 1 gets the JWT and can use the signature on it (with the IDP's public key) to verify that the JWT in fact came from an IDP that it trusts.
Problem
This is all great and works just fine. But now Service 1 needs to call Service 2 to fulfill the Shipment Application's request.
This is where things get confusing for me. Service 1 has its own Client ID and Client Secret. And it can get a "Client Credentials" token. But Service 2 needs to know the user that is making the request and a Client Credentials token does not have any user information in it.
The Authorization: Bearer header only allows for one token. But I need space for two:
If I only pass the User's JWT, my JWT looks like the call came directly from the Shipment Application to service 2. (It may be that Service 2 should not even be called directly from the Shipment Application.)
But if I pass only the Client Credentials token of Service 1, then Service 2 is not going to get the user's information.
Either way Service 2 is not going to be happy.
Question
Does OpenID Connect have a way to merge two tokens? Or some other way to allow for chains of service calls to work?
NOTE: I currently am passing both. One in the Authorization: Bearer header and one in a custom header. But because this is not part of the OpenID Connect protocol, it is causing issues when working with 3rd party tools (like OpenAPI (aka Swagger)).
You can look at delegated tokens pattern to issue a new token for Service1 to Service communication.
See:
https://auth0.com/docs/tokens/delegation-tokens
https://www.scottbrady91.com/OAuth/Delegation-Patterns-for-OAuth-20

Link another account with oauth on passport

Context:
1 spa
1 api
1 existing passport strategy with jwt
Feature:
Connect additional account with oauth to the existing account
I already have an authentification using passport on nodejs.
This generates a jwt allowing the users to authenticate them with a bearer token in the header of each request to the api.
I would like to add an additional oauth connection to the existing account (actually it is docusign but I think the problem could be the same with other connections).
When there is the redirection with the authorization code, I cannot identify the user which has initiated the connection because I don't have the bearer token in the request header anymore and the data between the two accounts can be different (different emails for example).
Is there a workaround for this or did I miss something?
Thank you for your help
Passport has an Authorize method which can be used to authenticate with a secondary IdP. For example:
Use Passport#Login to enable the user to login to your application's primary IdP
Use Passport#Authorize to enable the user to authenticate with a secondary IdP (such as DocuSign) as needed.
Regarding state for your redirect method (determining which IdP should be used to exchange the authorization code for an access token):
You can use the state parameter that is sent as part of the OAuth authorization code grant flow. But be sure to also use the parameter as a random nonce to guard against a CSRF attack. Eg, send idp1|random_nonce so you can both check the nonce and know that you should communicate with idp1, not idp2.
You can use your application's session machinery to maintain your app's state knowledge of which IdP you're working with at the time.
The only one I can think of is having 2 apps. Each can run on a different port or vd. Each would have their own passport strategy. The two apps can redirect to eachother and/or communicate using API endpoints to pass data between them.

openid connect 2.0 and setting authentication JWT in http only cookie

Since local storage and session storage are both accessible via JavaScript it is best not to store the authentication JWT in either of them to avoid XSS attacks.
Since OpenID connect 2.0 is performed on a separate domain how do we set a server-side HTTP only cookie that contains the authenticated JWT?
My guess is this:
The user goes to your website then clicks sign-in.
The user gets redirected to the 3rd party OpenID connect 2.0 provider.
The user signs in and is now redirected to the route of your choice www.example.com/myredirectlogin.
The user's browser then makes a get request when the redirect lands on my route and it passes in the JWT token in the URI.
The server then validates the JWT via Asymmetric algorithm with the public key given by the provider.
The server then returns a server-side HTTP only cookie with the JWT as the value and the client-side doesn't have any recollection of the JWT since it was only in the URI and isn't stored anywhere else.
My question is: Is the above the correct process to securely handle OpenIDConnect 2.0 flow?
I'm assuming you mean the "Id Token" when you say "authentication JWT", since that's the only JWT required by OpenID connect.
All the flows that OpenID connect supports are listed in the spec. If you want to log in to the authorization server and authenticate to a separate site, then you will often use the "authorization code" flow which doesn't send the ID token to the browser at all. There are other flows defined by OpenID connect, but none of them mention storing the ID token in a cookie - how the session is maintained between the client (the site you're authenticating to) and the browser is a separate issue from authenticating the user.
I found the answer within authorization code flow: https://connect2id.com/learn/openid-connect
List of steps
OAuth 2.0 and OIDC Authorization code flow
The user hits your website's login route
The user is redirected to an identity provider with a proper tenant id
The user is authenticated and is redirected to your callback route with an access token in a query parameter i.e. &access_code=234234sdfkljsak.
a get request is executed on your web server at the callback route with the access token in the query parameters.
this callback get route should then make a post-call to retrieve an actual JWTidentity token from the provider i.e. azure b2c and it will add the access token as part of the request either as a query parameter or post of body.
the provider (Azure B2C) then will respond with an identity JWT token that we will send back to the user's browser as an HTTP-only session cookie that way the user is now SSOed among all browser tabs and the cookie will be sent with every request automatically and is protected from xss.

Should my app issue it's own access tokens, when using external oauth2 provider (facebook)?

I would like to give the users a possibility to login with some external oauth2 provider (facebook) in my app. The client's part is running on mobile device in a native app.
I am not sure which of the approaches below should I prefer ?
Should the client send the user's access token by facebook with each request ? At each request backend asks facebook to validate the access token. Based on the validation's result, backend performs authorization and return corresponding result to the client.
Should the backend ask facebook to validate the access token only at user logon, then issue its own access token, return the access token back to the client and client will use this access token at making requests to the server to avoid contacting facebook at each request ?
I have read some questions about how to implement the auth with facebook and most of the devs are using B, but I haven't seen any explanation why is it good/bad to use A ?
What I see as benefits of the solutions:
backend doesn't need to care about issuing, refreshing, validating access tokens since this is done only by facebook's authorization servers.
this solution seems to be more effective, since it does not require to connect to facebook at each request.
Security tokens issued by Facebook are signed with a digital signature. The API server only needs access to the public key to validate the signature. There's no need at all to contact Facebook after the user authenticates.
A reason to issue your own tokens after the user signed in with Facebook could be to add claims to the token. But obviously having your own authorization server comes at a cost. It's up to you to weigh the pros and cons.
If you do decide to have your own authorization server, make sure not to write your own! There are open source options like Thinktecture IdentityServer.
I will vote for option B and here is my explanation,
Your API must authorise the request every time with some auth token , which cannot be external provider token, in such case anyone with an access token (eg: other developers) of other provider can access your api, basically there is no auth here.
When your sever issue access token, it's easy to validate and when needed could be revoked easily (eg: on password reset)
While authenticating , your server has fully control over issuing access token , so the validation is made only once and doesn't have to do every time while calling the API.

Resources