Extending the OpenAPI Contract for Security
2023-01-07
The OpenAPI contract standard, also known as Swagger, has been evolving for a long time, but it still seems to be missing something that I think is very important to an API: user attributes for security. Yes, you can describe a schema, so I know if I need to pass Basic Auth or a Bearer Token, but it doesn’t describe what attributes the API is going to need from that token.
This is, in part, because not all security schemas contain attributes, and when they do, they’re delivered in different ways (although OAuth has scopes and OIDC has claims), but I think it’s more because API security has been a separate effort from API design and API Architects tend to think someone else will manage that.
I’d like to propose closing that gap, at least a little.
I’ve been working on a side project where I take an OpenAPI contract and load it into an API gateway. All the rules I need for routing like paths and payloads are described in the contract, so I’m able to automatically create a set of format rules (/v1/cats requires a field “furball” or you don’t get past the gateway).
But the gateway also needs to know how to apply user level security, and, because the contract standards don’t accommodate scopes, claims or other attributes, I solved my problem by creating a separate document that references the operationid in the OpenAPI contract.
The contract identifies the path and the method (e.g. GET /v1/users/) as an operationID
"/v1/pages/{pagesId}": {
"delete": {
"description": "Deletes single object from pages",
"operationId": "pagesdeleteitem",
...
I can use that operationId to describe a rule – that is, everything I want to enforce when someone makes a request. So I make sure you have a role of admin or editor, then I check that you have the right path elements AND I grab any path variables so I can check that you actually, as an editor, own that object:
{
"operationId": "pagesdeleteitem",
"method": "DELETE",
"roles": [
"admin",
"editor"
],
"path": [
"v1",
"pages",
"{pagesId}"
],
"pathvars": [{
"pathvar": "{pagesId}",
"role": "collaborator"
},
{
"pathvar": "",
"role": "admin"
}
]
}]
So, when my API gateway gets an OIDC token, it checks the claims
{
"iss": "b18d65c0-1780-11ed-a7c8-6fed2834e13a",
"sub": "4135659114877368",
"aud": "all",
"ids": {
"usersId": [{
"id": "4135659114877368",
"role": "owner"
}],
"pagesId": [{
"id": "33f497a0-8afd-11ed-bde6-59a264217edc",
"role": "collaborator"
}]
},
"roles": [
"editor"
],
"jti": "5d023614-40ac-45ea-a9da-52e22eeca35d",
"iat": 1673111591,
"exp": 1673197991
}
If I’m trying to delete /v1/pages/33f497a0-8afd-11ed-bde6-59a264217edc I’m okay – the OpenAPI contract says that the path exists and is identified by the operationId pagesdeleteitem, the security document says that I can delete it if I have the ‘editor’ role and if my claims say that I have that pagesId assigned to me with the ‘collaborator’ tag, then I’m good to go.
All of this cascades from the original API design allowing me to build a complete picture of how that consumer is going to interact with my API and then, more importantly, push it out automatically to all the systems that the consumer actually does interact with.
The only problem I have with my solution is that it’s MY solution – I love the OpenAPI contract standard because it’s a standard, and I was disappointed to find there isn’t a standard way to describe security elements, which means I’m not the only person inventing this wheel, and it also means there are a lot of flimsy, broken wheels out there that are responsible for driving our security.
If you like my idea, let me know (social contacts are linked at the top of this page). If you have an alternative, let me know that too!