 Hello developers, my name is Matt Rable. You might recognize me from Octane 22, where I wore this cape and I was Octaman. Pretty cool, huh? So as part of the developer track, I and many others helped pick out the speakers that spoke at our developer track. And one of those speakers unfortunately couldn't make it, Ashwini Metallic Desai. But she was kind enough to record her presentation, send it to us, and now we'd like to share that with you today. So behold, building an identity platform using Octa for healthcare applications. I hope you enjoy it. In this video, we are going to see how to build an identity platform using Octa for healthcare applications. Although the contest and the examples shared in this video are those of healthcare applications, the knowledge shared is generic enough to be extrapolated to any generic web application. Before we dig deeper a little about me, my name is Ashwini Metallic Desai. I'm an engineering manager with Medli Pharmacy and currently leading the platform identity tool. You can find my coordinates on the screen to reach me out on Twitter and LinkedIn and also an email ID. So before we get started, I just like to give an outline of how this video is set up. So we would be looking at the design of the identity platform in three steps. The first step would be to see how we evolved the design of the platform through various requirements that came in. The second part would then be to see how did we standardize and enforce these designs so that all applications do end up following the enforced design. And the third part would then see how we could build reporting and operationalize the design. So let's get started with the first step. Let's see how we evolved the design of the identity platform with the requirements that came in. So Medli Pharmacy is a pharmacy, digital pharmacy in the US and has different types of applications, internal apps, B2B apps, B2C apps and so on. So we first started, the business first started with the requirements of internal applications. These internal applications are React-based single-page applications. Let's see how the authentication and authorization was set up with these. When it came to authentication, since these were single-page applications, we used this single-page app type and we used PKCE, which is the more secure front-end-based authentication mechanism. When it came to authorization, the requirements were that there are two types of users in the applications, admins and non-edmins. And as naturally we would think about it, we created an admins group, assigned all the admin users of these applications to the admin group. And that's how made it also part of the access token, as you can see, via a claim called groups claim, which would have the value of all the groups assigned to that user. Hence, this would allow us to on the back-end be able to validate the presence of the admins group and hence decide if certain operations are permitted or not. An example of that. So this is a Node.js middleware, as you can see, and it validates if the claims has groups and if it does include the admins group. This is how the authentication and authorization is set up for the internal applications and the setup went well and served the purpose. As we moved on and as the business expanded, we started developing B2B applications. The user of these B2B applications were of course the B2B consumers, but also the internal users on the medley side. Again, these were web applications, single-page apps based on React. Let's see how the authentication and authorization was set up for this. So continuing with previous design of the internal applications, we still use PKCE and these are single-page apps. But as you can see, instead of creating one app, since medley has four SDLC accounts, DevTestUATProd, or four environments per application, we created one app in October environment. Now to do this, we used OctaPreview to create Dev and Test environment and we used OctaProd to create the UATN prod. Moving on, to be able to have different values of claims and custom attributes per user, per environment, we moved away from using the base profile. Since the base profile can only be one in OctaPreview and one in OctaProd, we moved from there to an application profile. Octa by default creates an application profile against each application that gets created. Hence, with the use of this, each user can now have one profile per app per environment. When it came to authorization, we again used the scopes and claims, but instead of restricting ourselves to admin groups, because the requirements were so extensible such that, say for example, two doctors would not have the same permissions. One could be able to read prescriptions while the other could read and write both. Hence, the role-based authorization based on groups wouldn't fly and extend, which is why we moved away from that and moved to claim-based authorization. Inside of claim-based authorization, we created the scopes per entity, as you can see on the screen. Now these entities could be understood as the HTTP resources managed by your backend services and the claims had the same name as that of the scope to ease the purpose of understanding and the value as you can see is read from the application profile of the user and not the base profile. Instead of doing user.patient, you would be doing app user.patient. Now how these scopes and claims are checked on the backend? Before that, let's see how the application policy was set up. Till now, we had been using for the internal applications the default policy, which lets applications request for any scope and has a default access token lifetime setup, which is common across all these applications. As we moved to the B2B applications, we thought that we should be giving a smaller access token lifetime because B2B consumers are also there on the platform and it increases the security, which is why we created an application-specific policy and to this policy, we attached a default rule. Now this rule lets us define the scopes that are allowed for this application and these scopes can be the scopes that the application needs. One could also set up the access token lifetime. Hence, we could restrict which application needs with scopes and what is the access token lifetime. If any application needs more scopes or wants to change the access token lifetime, it can be done but would not be allowed to access any and all the scopes. It would be more controlled. Now this is how the setup for the B2B applications continued. Moving on, given that there were so many applications being built, it meant a lot of services were created and given Medli Pharmacy is a microservices-based architecture, there was a lot of need of inter-service communication, all of those based on HTTP. To be able to authorize these requests, we ended up setting up Okta apps based on client credentials. As you can see, we said the client authentication needs to happen based on a client ID and a client secret. So continuing with our learning from the B2B application setup, we ended up creating one app per environment, one Okta Client Service app per environment so that the client ID and client secret combination is unique per environment and is not compromised at all. This is how the authentication and authorization is set up. But to add to the authorization, because now we are reading the claim values from the application profile of the user and since the Okta Service apps or the client credential based apps do not have profile attributes, we set up because the user is not in context, we set up the custom profile attributes with the same name on the application itself. So the claim value would either read from the application user if the user is in context, if the user is not in context, it would read from the profile of the app itself. And that is how this makes it easy for us to, without having to know if it's a call coming from a service or from a user, still be able to just check for the presence of a claim. Moving on, we continue with the same scopes and claims, but we add an extra layer of authorization because we did not want any service to be able to do any set of operations on the other service, meaning a client should have restricted permissions on the server based on whatever access it has been granted. To be able to do that, we continued with the entity-based scopes and the claim name corresponding to the same scope, but we added values of the claims to be read, write, or delete. Now, these were mutually exclusive claim values to reduce the cognitive load and understanding and handling on the backend, which means if somebody has the value for facility as read, they'll be able to read facility. If somebody has write, they'll be able to update and create facility. And if somebody has delete, they'll be able to delete. Now, these were the common operations. Most of these operations on the services were restricted to get, put, portion, delete, and hence served a purpose well, but these could be extended further as per requirements and customized further. Now, how would this look on the backend? If you see, for example, for an endpoint where we need to fetch a prescription by an ID, we would be checking if there's a claim called prescription and has a value read. This is a custom annotation created by us, and hence reads well. Another example for the same. For a put endpoint, we would see if the user has prescription write. So this is how the authorization was set for service to service communication. Moving on, we started developing the B2C applications. So far, we have understood that we've created one app per environment. We have used application profile over default profile. We've moved on from using admins but specific scopes and claims and also with granular read, write, and delete. Let's see how it went ahead for the B2C applications. For B2C applications, we went ahead by creating one app per environment, but difference being, now this app is common across all the B2C platforms. The reason for doing so is, across all the B2C platforms, the user's details are the same. To avoid asynchronously managing the syncing between these attributes, like for example, say phone number, address, email ID, or an identifier, like say in our case, the patient ID, it makes sense to have one profile for the user, one application profile for the user per environment across these B2C platforms. But then how could we be able to manage the granular customizations? To be able to do those, we created one group corresponding to each of these B2C platforms. Now, let's see how does this group allow us to do customizations. So similar to how we have seen the application level policy for other apps, we created a similar policy for the B2C app, but we created one policy rule per B2C platform, and these policy rules would hence have the scopes restricted for that B2C platform and the group assigned to them. Also, as we had seen in the past, we'll be able to set up the access token lifetime per different platform. For example, an iOS app would have a longer session time, longer access token lifetime, in comparison to the other B2C platforms. In addition to the scope and claim-based and read-write delete authorization, when it comes to B2C apps, it is very important for us to make sure and especially given healthcare applications that we do not violate any HIPAA compliance and we are exposing just enough data that is applicable for the user in context or allowing the user to update just enough or operate on the resource that is specific to the user or has access to it. Now, how did we do that? We added resource level authorization. Resource level authorization can be added using any of the identifiers. It could also be based on an email idea that can be fixed for a certain type of application. In our case for the B2C applications, for example, we used patient ID. Now, how is this patient ID used in the backend service for our authorization? Let's see an example. So if somebody calls a function, get patient profile, and passes a patient profile ID, we would call a function called isauthorized and pass in the authentication object, which is nothing but the content of the JWT and the patient profile ID, which came in the request. We would then be verifying if the ID in the token matches the one that is the user requesting for. This increases the overall security and makes sure that the user is only able to see as much as the user should be allowed to. Also, in addition to resource level authorization, we now segregated the authorization server as well. So far, we have not spoken about the authorization server at all, which means we just used one authorization server, which is the default custom authorization server that comes with Okta. But since we have these many different types of applications, to increase the security, we segregated the authorization server into multiple, and to suit our needs, we segregated them based on application types, one for internal B2B, one for B2C, one for partnership and so on. Now, this helped in making sure that the access token from one app type cannot be used in other backend services for the other types of fetching data for other application types as well. Two, it also helps in avoiding rate limit violations and warnings because Okta has rate limit warnings and violations per endpoint per minute on the authorization server. We can also go to an extent of creating an authorization server per application, but I think we did not have to go that far. But if that is the purpose for you, then you can give that a thought. So that is the end of the first section where we just saw how did we evolve through the design of the platform team of the identity platform and how we have made it as secure as possible just to summarize. So we created one application in Okta per application, per environment, except for the B2C, where there's one application per environment across the B2C platforms. We added application level scopes and access token customizations and restrictions. We added scopes and claims checks, granular checks based on entity and read-write-delete-level access. We added resource-level authorization and desegregated the authorization server. Now the second very important part is after we have tried and tested this environment, this platform to be working fine across all different types of applications, we now have to standardize and enforce this for any upcoming applications and also for the existing ones. How did we do that? Let's see. So the first step was to build that library is for build and run time to be able to reduce repetitive efforts, to ease out operations and to also enforce them. How do we do that? Let's see some examples. So the first thing that we created was a React library. React library because all of our internet applications are React-based single-page applications. In this React library, we export hooks and components. All the operations on the front-end are for the login of identity-related things, either authentication, login, which means login, and you would either do user management, creating a user, removing a user, updating a user, or you would do life cycle changes for God password, change password, et cetera, et cetera. To serve all these purposes, we created the hooks and components. So if you see, we created hooks for create user, remove user, update user. Using the hooks, let's people choose their own UI and with the right UX in mind, the hooks could be used and would take care of all the things on the backend. On the components, we extracted an auth component, which is a boilerplate on top of Octa React SDK's security component that initiates authentication. And we also created default login page, login widget, and user management as some examples of the components. Let's see how these were used. One example of this, so you would encapsulate all your routes within this auth component and pass in the scopes, or URL and other props as required. And one could also use the private route component, which would let you restrict the access of that route based on a presence of a claim and its value. This is how it helps. Moving on, you could also have additional features where you would be able to restrict based on having multiple sessions. Skipping to the next example. Now all of these actions on the front end by the React library were done on Octa with the help of a proxy service, a Node.js service. It acted as a proxy between Octa and the React library. The reason for doing so is the React library cannot securely hold the API key that is needed to make the API call to Octa. Now, although we are calling it a proxy service, it handles a lot of things. Let's see what does it do. So it lets you add security elements like hiding x powered by enabling HSTS, adding a level of x-ray monitoring, so that you can see the traces for all your calls between this service and Octa. With that, it also exposes all the end points. Although all these end points look very similar to how the end points of Octa are, it adds a lot of extra authorization checks custom to how we have designed our system to be. It checks whether a user who's trying to grant somebody else access has himself or herself has enough permissions to be able to give that to the other user. It checks the presence of the right body and all the other things. With this, how do we make use of the API key between this proxy service and Octa? Instead of using a user's API key, we created a custom user type in Octa called API key user and created as many API keys, as many environments for the service and use them in the service. This made sure that nobody's API key is being compromised. We also created token verification library on the backend. This token verification library would work seamlessly with multiple authorization servers. You could just list the name of the authorization servers and it would know the URL and figure out details. It would mandate you to specify a claim and the value that is required. It would specifically also check if it's a service-to-service call, what are the trusted clients that you say is the call coming in from one of the trusted clients that you have passed in and so on, so forth. So this is how we would try to enforce as many restrictions as we have designed on paper and have tried to enforce manually. But these libraries, the React library, the Node.js proxy service and this token verification library would enforce them on the backend and on the front end. Now, these are the libraries for bedtime and runtime. What are the benefits? As we saw, it helps abstract all authentication and authorization related concerns in some areas that we own. It helps provide generic design without compromising on security, which is a very important aspect when it comes to identity. It enables threat modeling and penetration testing to be done on few components, central components, without having the teams to test them at all individual levels. Although some level of sanity needs to be done because this is a central piece of work, the threat modeling and penetration testing once done hurriedly on these components would give enough confidence. With this, these libraries, if they are made a part of skeleton and made auto-available to individual repositories via skeletons like progen-based skeleton, which is a library that lets you create a project template, it helps you to bootstrap and rollout changes soon. It abstracts out upgrades and also lets you do security patch fixes independently. So we saw that for the build time and runtime we created these libraries. How did we automate provisioning and deployment phases? Let's see that. So we built the Terraform modules for single-page apps provision. This means we created an octa app using Terraform modules to match with one app, one environment to set up that we had seen. This module would ask for certain variables and set up those scopes, claims, a custom IDP for you, allow wildcard and all those things. In return, return an SSM parameter which has the AppLiant ID, IDPU, the type ID, et cetera. Likewise, for a client credentials-based app, we exposed another Terraform module, which would, in addition to the environment and service team, also take default claims. Now these default claims are nothing but the profile claims for the application, meaning for the client, which would set the claim values on this profile so that the claim validation on the backend can go through based on the values. The outcome of this Terraform module, as we can see, is an AW secret which has a client ID and client secret that could be made available to the service. Now how does all of this come together? Let's see. So local and build time, we have the library, the React library, this is for the front-end app architecture. We have the React library that is plugged in to all the repositories by a project-based created code template. Now, this library needs a JSON file to kickstart the authentication. How does that come into picture? During your runtime, let's see that. So as we saw for the provisioning, there's a Terraform module that creates an octa-app and outputs the values in an SSM parameter. We used AWS Amplify that merged these values with the authorization server-specific attributes and created an app config file. Now, this app config file, as I said, is needed by the React library and it kicks out the login flow with octa and gives us an access token. Similarly, for the service architecture, we again created a template, a project-based template with a verification library that would create a Git repository with these. For the provisioning and runtime, as we saw, we use a Terraform module. This Terraform module creates a client ID client secret in the AWS secret. This AWS secret, we used serverless as a framework to deploy our backend services on lambdas. This serverless framework let the lambda have access to the AWS secret for client ID and client secret and during the runtime, the lambda would then make a call to octa to fetch the access token. Hope this helps understand the overall system architecture. So that ends the phase two of our overall talk where we spoke about how do we standardize and enforce the platform design via various libraries in different phases of the application, like build time, runtime, provisioning and deployment. Now that all of this is done, it is equally important for us to operationalize the platform. How do we do that? So the first step towards that is having the right alerts and monitoring in place. So octa does allow email integration where you could enable email-based alerts for events like the one we see on screen where we see a rate limit warning by octa. There is equally one for rate limit violation. These could be integrated with ZenDuty or PagerDuty which are support-based tools and integrate with all these different products well enough for the person on support to receive an alarm when such an event occurs. Likewise, there's also email integration for things like ActiveDirect, reconnecting and reconnecting. These could also be integrated with ZenDuty so that the person on call receives the alarm. Next, we also enable CloudWatch-based alarms and integrated them with ZenDuty. Like I mentioned, we used AWS Lambda for our services. We used API Gateway, the HTTP request to the lambdas and we used the HTTP metrics of five xx errors, latency, et cetera of the API Gateway and integrated those metrics as alarms on ZenDuty. But there are certain alarms which are still not available directly via octa using their standard email integrations which could be made available to the person on call and hence everyone by the use of event hooks. So there are a lot of alarms which one would want to have like users being removed, application being deactivated and suspicious user activity on all of those. These cannot be received as email integrations or notification and helps. Having an event hook integrated with Lambda that would trigger whenever the event occurs will help. The way we set it up is we integrated the event hook with a Lambda function. This Lambda function on receiving those event would publish the message on SNS topic and this SNS topic is the one that is connected to ZenDuty and hence would notify the person on call. This is a very extensible solution and can be used for all types of events in octa. Now that we have seen how alerts and monitoring can be set up, it is equally important for us to do reporting and analysis over the period of time to see how are we functioning. Alerts and monitoring would always alert us and tell us how we're doing at that point in time. But it is equally important for us to see are we seeing a good trend with time. How do we do that? Let's see. So one we did directory integration. We did active directory integration in octa and this helped us activate and deactivate users as and when they joined the organization and left the organization without having to do any manual intervention which is a very important security aspect. Moving on, because octa's logs are only retained, the system logs are only retained for a duration of three months, we used a log retention tool which was the same as the one we used for our data lake and we used that tool to retain all the logs and build analytics on top of it. The different kind of analytics we built, things like count of successful user logins for a given day, across different app, count of failed user logins for a given app across different days, for example. Other such analytics were how many users got locked out in a day? How many suspicious IP and security threat detections were done? This would help us identify of what is the trend and for us to be able to identify what are the loopholes in the platform design, how could we fix things up and see if that is helping in reducing the number or increasing based on the parameter we are tracking. So that's all that I had to share through this video. Thank you and hope this was helpful for you. Thank you for watching Ashwini's video. I hope you enjoyed it. If you'd like more videos like this, please subscribe to our YouTube channel and follow us on Twitter, we're Octadev. Have a great day.