Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement OpenID Connect authentication with 3rd party IDPs in a microservices architecture

For the past 10+ days I've read an watched ALL the content I could find on understanding OAuth2 and OpenID Connect, only to find that many people disagree on the implementation, which really confuses me.

To my understanding, all the articles and examples I found assume you want access to eg. google calendar, profile info or emails if you eg. login with google, but I do NOT need to access other than my own API's - I only want to use Google, Facebook etc for logging in, and getting an id which I can link to my user in my own database - nothing more than that.

I'll try illustrate my use case and use that as an example.

enter image description here

A note on the diagram: the Authentication service could probably be built into the API Gateway - not that i matters for this example, since this is not about "where to do it", but "how to do it the best way" possible, for an architecture such as mine, where it's used for my own API's / Microservices, and not accessing Google, Facebook etc. external API's

If you can understand what I'm trying to illustrate with this diagram above, please tell me if I've misunderstood this.

The most basic requirements for this architecture you see here are:

  • Users can login with Google, Facebook, etc.
  • The same login will be used for all micro-services
  • OpenId user will have a linked account in the database
  • User access is defined in my own db, based on groups, roles and permissions

I do not intend to use external API's after the user is authenticated and logged in. No need for ever accessing a users calendar, email etc. so I really just need the authentication part and nothing else (proof of successful login). All user access is defined in my own database.

So a few fundamental questions comes to mind.

  • First of all, is OpenID Connect even the right tool for the job for authentication only (I'll have no use for authorization, since I will not need read/write access to google / facebook API's other than getting the ID from authenticating)?
  • People generally do not agree on whether to use the ID or Access token for accessing your own API's. As far as I understand the ID token is for the client (user-agent) only, and the access token is for eg. accessing google calendar, emails etc.... External API's of the OpenID Provider... but since I'll only be accessing my own API's, do I event need the access token or the ID token - what is the correct way to protect your own API's?

If the ID token is really just for the client, so it can show eg. currently logged in user, without going to the DB, I have 0 use for it, since I'll probably query the user from from the db and store it in redux for my react frontend app.

Dilemma: To store user details, groups, roles and permission inside JWT or not for API authorization?

  • By only storing the user identifier in the token, it means that I always allow authenticated users that has a valid token, to call endpoints BEFORE authorization and first then determine access based on the db query result and the permissions in my own database.
  • By storing more data about the user inside the JWT, it means that in some cases, I'd be able to do the authorization / access (group, role, permission) check before hitting the API - only possible with user info, groups, roles and permission stored inside a JWT issued upon login. In some cases it would not be possible due to eg. the CMS content access permissions being on a per-node level. But still it would mean a little better performance.

As you can see on the diagram I'm sending all API requests through the gateway, which will (in itself or with an authentication service) translate the opaque access token into some JWT with an identifier, so I can identify the user in the graph database - and then verify if the user has the required groups, roles and permissions - not from an external API, but from my own database like you see on the diagram.

This seems like a lot of work on every request, even if the services can share the JWT in case multiple services should need to cross call each other.

The advantage of always looking up the user, and his permissions in the db, is naturally that the moment the user access levels change, he is denied/granted access immediately and it will always be in sync. If I store the user details, groups, roles and permission inside a JWT and persist that in the client localstorage, I guess it could pose a security issue right, and it would be pretty hard to update the user info, groups, roles and permissions inside that JWT?

One big advantage of storing user access levels and info inside the JWT is of course that in many cases I'd be able to block the user from calling certain API's, instead of having to determine access after a db lookup.

So the whole token translation thing means increased security at the cost of performance, but is is generally recommended and worth it? Or is it safe enough to store user info and groups, roles, permissions inside the JWT?

If yes, do I store all that information from my own DB in the ID Token, Access token or a 3rd token - what token is sent to the API and determines if the user should be granted access to a given resource based on his permissions in the db? Do I really need an access token if I don't need to interact with the ID providers API? Or do I store and append all my groups, roles, permissions inside the ID token (that doesn't seem clean to me) issued by OpenID connect, and call the API and authorize my own API endpoints using that, even if some say you should never use the ID token to access an API? Or do I create a new JWT to store all the info fetched from my database, which is to be used for deciding if the user can access a given resource / API endpoint?

Please do not just link to general specs or general info, since I've already read it all - I just failed to understand how to apply all that info to my actual use case (the diagram above). Try to please be as concrete as possible.

Made another attempt to try and simply the flow:

enter image description here

like image 847
Dac0d3r Avatar asked Jul 07 '18 23:07

Dac0d3r


1 Answers

The following answer does only apply for a OpenID Connect authentication flow with a 3rd party IDP (like Google). It does not apply for an architecture where you host your own IDP.

(There are some API gateways (e.g Tyk or Kong) which support OpenID Connect out of the box.)

You can use JWTs (ID token) to secure your APIs. However, this has one disadvantage. JWTs cannot be revoked easily.

I would not recommend this. Instead you should implement an OAuth2 authorization server which issues access tokens for your API. (In this case, you have two OAuth2 flows. One for authentication and one for authorization. The ID and access token from the IDP are used only for authentication.)

The following picture shows a setup where the API gateway and authentication/authorization server are two separate services. (As mentioned above, the authentication/authorization can also be done by the API gateway.)

The authentication flow (Authorization Code Grant) calls are marked blue. The authorization flow (Implicit Grant) calls are marked green.

OpenID Connect Authentication Flow

1: Your web app is loaded from the app server.

2a: The user clicks on your login button, your web app builds the authorization URL and opens it. (See: Authorization Request)

2b: Because the user hasn't authenticated and has no valid session with your authorization server, the URL he wanted to access is stored and your authorization server responds with a redirect to its login page.

3: The login page is loaded from your authorization server.

4a: The user clicks on "Login with ...".

4b: Your authorization server builds the IDP authorization URL and responds with a redirect to it. (See: Authentication Request)

5a: The IDP authorization URL is opend.

5b: Because the user hasn't authenticated and has no valid session with the IDP, the URL he wanted to access is stored and the IDP responds with a redirect to its login page.

6: The login page is loaded from the IDP.

7a: The user fills in his credentials and clicks on the login button.

7b: The IDP checks the credentials, creates a new session and responds with a redirect to the stored URL.

8a: The IDP authorization URL is opend again.

(The approval steps are ignored here for simplicity.)

8b: The IDP creates an authorization and responds with a redirect to the callback URL of your authorization server. (See: Authentication Response)

9a: The callback URL is opened.

9b: Your authorization server extracts the authorization code from the callback URL.

10a: Your authorization server calls the IDP's token endpoint, gets an ID and access token and validates the data in the ID token. (See: Token Request)

(10b: Your authorization server calls the IDP's user info endpoint if some needed claims aren't available in the ID token.)

11a/b: Your authorization server queries/creates the user in your service/DB, creates a new session and responds with a redirect to the stored URL.

12a: The authorization URL is opend again.

(The approval steps are ignored here for simplicity.)

12b/+13a/b: Your authorization server creates/gets the authorization (creates access token) and responds with a redirect to the callback URL of your web app. (See: Access Token Response)

14a: The callback URL is opened.

14b: Your web app extracts the access token from the callback URL.

15: Your web app makes an API call.

16/17/18: The API gateway checks the access token, exchanges the access token with an JWT (which contains user infos, ...) and forwards the call.

A setup where the authorization server calls the API gateway is also possible. In this case, after the authorization is done, the authorization server passes the access token and JWT to the API gateway. Here, however, everytime the user infos change the authorization server has to "inform" the API gateway.

like image 92
Matt Ke Avatar answered Oct 19 '22 05:10

Matt Ke