@tessin/authorize v1.5.0
Authorization Data Library
This library gets authorization data from an Azure storage account rather than Azure AD role assignments by simply fetching and collating a bunch of JSON documents.
Based on a token issued by Azure AD the library fetches the following JSON documents.
{accountName}.blob.core.windows.net/groups/{oid}/{appId}/{scope}.json
{accountName}.blob.core.windows.net/users/{oid}/{appId}/{scope}.json
All documents are fetched in parallel but the documents that apply to users take precedence and get after applied after any inherited data from security groups.
Example
import { createContext } from "@tessin/authorize"
const { validationContext, authorizationContext } = createContext(
"524f5b50-2d73-4767-8d88-b2855c65350d", // tenant
["97851bc4-f874-4015-ac92-e509b319526a"], // allowed audience
"AuthZ" // appId
)
// it is not possible to pass a token directly to authorization functions
// without first validating the token, only tokens issued by Azure AD for
// the intended audience pass this step, if validation fails it will return an error
// you can use `isValidToken` to test if the validation was successful
const validToken = await validationContext.validateToken(token)
// based on claims in the token the authorization data is read from the following URLs
// {accountName}.blob.core.windows.net/groups/{oid}/Authz/roles.json
// {accountName}.blob.core.windows.net/users/{oid}/Authz/roles.json
if (await authorizationContext.authorize(validToken, ["User.Read"])) {
// ok
} else {
// not ok
}
accountName
is derived from tid
tenant ID claim (see implementation of function accountName
for details). For users oid
corresponds to the oid
object ID claim. For groups oid
corresponds to the groups
claim (one for each group membership). Also here is each group is an object ID.
The blob containers users
and groups
should be public for blob access. Authorization data is not sensitive and it's meant to be freely available for anyone to inspect.
Easy Auth
Easy Auth is a name given to the Azure AD app service authentication service. It is a nice feature that makes sure you are authenticated. This package has a subset of those features implemented so that you can have the same experience locally without being tied to the specifics of Azure App Service (we've had lots of issues with this when it comes to deployment slots and debugging). We've implemented the protocols ourself for increased transparency and understanding.
import http = require("http")
import { easyAuth, TokenValidationError } from "@tessin/authorize"
// to use easy auth you need a token validation context
const easyAuth_ = easyAuth(
"524f5b50-2d73-4767-8d88-b2855c65350d", // tenant
"c4cea48a-a8e9-4faa-9bb1-f60821c70b9f", // client ID
tokenValidationContext
)
async function requestHandler(
req: http.IncomingMessage,
res: http.ServerResponse
) {
const identity = await easyAuth_.login(req, res)
if (identity === "pending") {
return // the easy auth handler is processing the request
}
if (identity instanceof TokenValidationError) {
// there was an error with the login process
// details can be found on the identity object
// which is actually a sub class of Error
//
// the error code will originate from the
// Microsoft Identity Platform (aka Azure AD)
// or the token validation code from this package
// if the token doesn't pass validation
//
// you can handle this error anyway you like
res.setHeader("content-type", "application/json")
res.end(
JSON.stringify({
ok: false,
...identity.toJSON()
})
)
return
}
console.log("ID token claims", identity.payload)
// ...rest of request processing goes here
}
Application Manifest
Application registration should be done with accessTokenAcceptedVersion: 2
and need to be configured to use groupMembershipClaims: "SecurityGroup"
as well as optionalClaims
where accessToken
include ipaddr
for proper auditing.
{
"accessTokenAcceptedVersion": 2,
"groupMembershipClaims": "SecurityGroup",
"optionalClaims": {
"accessToken": [
{
"name": "ipaddr",
"source": null,
"essential": false,
"additionalProperties": []
}
]
}
}
API details
accountName
Let's say your Azure AD Directory ID is 2f055a66-75dd-4eeb-bdad-69eb4f5a409b
. Then the storage account name would be 69hf4b3cspe1iysssdm3sql7
.
This mapping is done by parsing the version 4 random UUID as a hex string, omitting the constant version nibble 4
at index 12
(excluding hyphens) and then encoding the hex string in base 36
.
users/groups
The blob containers users
and groups
are public (for read only). The reason for this is that the authorization data is not sensitive per se. And while we can use access tokens to protect the storage account the extra hassle is not worth it.
appId
Here appId
, does not refer to a Azure AD application registration. appId
can be any user friendly name assigned to your application.
For example Web-dev
or Web-prod
. The getAuthorization
API doesn't mandate any rules what so ever. It's just a name identifying your application context.
scope
Scopes is just the name given to the type of authorization data. For example
GET /users/05585cb3-1307-48de-aee5-ac876f0b2424/Web-prod/roles.json
Host: 69hf4b3cspe1iysssdm3sql7.blob.core.windows.net
OK 200 HTTP/1.1
["User.Read"]
The well known roles
scope is a JSON document containing an array of role memberships.
Limitations
- No support for claims mapping feature