Skip to content

Commit

Permalink
docs: corrections and additional notes (#2467)
Browse files Browse the repository at this point in the history
  • Loading branch information
maduvena authored Sep 26, 2022
1 parent c22ce1a commit 1c93168
Showing 1 changed file with 109 additions and 42 deletions.
151 changes: 109 additions & 42 deletions docs/script-catalog/update_token/sample-script/README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
By overriding the interface methods in [`UpdateTokenType`](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/token/UpdateTokenType.java) inside a custom script you can
## Overview
By overriding the interface methods in [`UpdateTokenType`](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/token/UpdateTokenType.java) inside a custom script you can

1. Add a custom claim to an Access Token, Refresh Token or ID token
<br/>Example use-case: As per the open banking standard, the access token should contain claim `openbanking_intent_id` and the same value should also reflect in the `sub` claim.

2. Modify values of Registered (header claims), public or private claims.
<br/>Example use-case: An expectation required to be fulfilled by the FAPI-RW standard where the `sub` claim should have the user id.

3. Tweak the functionality of the AS to return a token only if a condition (check) is fulfilled.
<br/>Example use-case: Return an Access Token only if organization's account balance is greater than x.
1. Enable transformation of claims and values in id_token and Access token e.g. add a custom claim to an `id_token`, change a token expiry, change the `sub` value, or remove the `nonce`.
<br/>Example use-case:
* As per the open banking standard, the id_token should contain claim `openbanking_intent_id` and the same value should also reflect in the `sub` claim.
* As specified in the [FAPI Baseline Specification](https://openid.net/specs/openid-financial-api-part-1-1_0.html) the `sub` claim should have the user id.
2. Set a specific token lifetime
3. Perform extra business logic like adding or removing scopes.
4. Add an extra audit log for each token response.

## Flow
```mermaid
sequenceDiagram
title UpdateToken script
autonumber 1
RP->Jans AS:Request token (Access Token, Refresh Token or ID token)
Jans AS->Jans AS:check if UpdateToken script is enabled
Jans AS->Jans AS: modifyIdToken(), modifyRefreshToken(), \n modifyAccessToken() called from the custom script
Jans AS->RP: return token reflecting relevant claims
```

## Pre-requisites
- A Janssen Authorization Server installation
- [Update Token script](https://github.com/JanssenProject/jans-setup/blob/openbank/static/extension/update_token/UpdateToken.py) - this is just an example that is present in Janssen's OpenBanking distribution
RP->>Jans AS: Request token
Jans AS->>Jans AS: Is UpdateToken script is associated with client? <br/>(or is there a script that applies to all clients? )
Jans AS->>Jans AS: do stuff
note right of Jans AS: do stuff<br/>1. Enable transformation of claims and values in id_token <br/> OR 2. set token expiry <br/> or 3. add / remove scopes. <br/> or 4. perform audit logs
Jans AS->>RP: return token(s) (Access token, ID token or Refresh Token) reflecting step 3
```

## Adding the custom script to Jans server

Expand All @@ -37,7 +35,7 @@ Jans AS->RP: return token reflecting relevant claims
* Configure any parameters that the script may use.
* `name` field should reflect the use case
* `script_type` should be `UPDATE_TOKEN`
* `script.py` can have contents similar to [Sample Script](https://github.com/JanssenProject/jans-setup/blob/openbank/static/extension/update_token/UpdateToken.py)
* `script.py` can have contents similar to [Sample Script](https://github.com/JanssenProject/jans/blob/main/docs/script-catalog/update_token/sample-script/sample_script.py)) and is present in jans-cli's host machine.

```
Expand Down Expand Up @@ -83,12 +81,30 @@ Jans AS->RP: return token reflecting relevant claims
"internal": false
}
```
3. Add the custom script. Save the response, it will contain the inum of the newly added script.
- Add the custom script. Save the response, it will contain the inum of the newly added script.
```
/opt/jans/jans-cli/config-cli.py --operation-id post-config-scripts --data /tmp/cs.json
```
## Associate an Update Token script to a client (RP) [optional step]
📝 Note: If the Update token script is not associated with a client, then it will be applicable to all clients registered in the Jans Server. Which implies that all tokens obtained using the Jans server will reflect modifications as per the script.
<br/>
To Associate an Update Token script to a client (RP), execute the command below with appropriate values for:
- inum of the client
- inum of the update_token script

```
/opt/jans/jans-cli/config-cli.py --operation-id patch-oauth-openid-clients-by-inum --url-suffix inum:inum_of_client
--data '[
{
"op": "add",
"path": "updateTokenScriptDns",
"value": ["inum={SCRIPT_ID},ou=scripts,o=jans"]
}
]'
```


## How to write the custom script
## Writing an Update token script (Pseudo code for potential usecases)

### 1. Mandatory methods:
```
Expand Down Expand Up @@ -127,16 +143,26 @@ Pseudocode and example :
return True
```
### 3. modifyRefreshToken() : Used to modify claims in a Refresh Token
![Inspecting a modified ID token](https://github.com/JanssenProject/jans/blob/main/docs/assets/update-id-token.png)

### 4. modifyAccessToken(): Used to modify claims in a Access Token
### 3. modifyAccessToken():
#### a. Granularity of access control:
An UpdateTokenType script is great for adding scopes or removing scopes to/from the Access token. By doing so you can tailor build the granularity of access control according to business need.

[`context.overwriteAccessTokenScopes`](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/context/ExternalUpdateTokenContext.java) is ready to use method of the `context` variable

```
def modifyAccessToken(self, accessToken, context):
context.overwriteAccessTokenScopes(accessToken, Sets.newHashSet("openid", "mynewscope"))
```
#### b. Perform business check before returning AT

Pseudo code and example - Issue Access token only if account balance is greater than 0
```
# Returns boolean, true - indicates that script applied changes
# jsonWebResponse - is JwtHeader, you can use any method to manipulate JWT
# accessToken - is JwtHeader, you can use any method to manipulate JWT
# context is reference of io.jans.oxauth.service.external.context.ExternalUpdateTokenContext
def modifyAccessToken(self, jsonWebResponse, context):
def modifyAccessToken(self, accessToken, context):
#read from session
sessionIdService = CdiUtil.bean(SessionIdService)
Expand All @@ -148,27 +174,68 @@ Pseudo code and example - Issue Access token only if account balance is greater
if balance > 0 :
return True
else:
return False # in this case an AT will not be issued
return False # forbid the creation of AT
```
#### c. Modify claims in an access token:
```
# Returns boolean, true - indicates that script applied changes. If false is returned token will not be created.
# accessToken is reference of io.jans.as.server.model.common.AccessToken (note authorization grant can be taken as context.getGrant())
# context is reference of io.jans.oxauth.service.external.context.ExternalUpdateTokenContext
def modifyAccessToken(self, accessToken, context):
# header claims
accessToken.getHeader().setClaim("header_name", "header_value")
#custom claims
accessToken.getClaims().setClaim("claim_name", "claimValue")
#regular claims
accessToken.getClaims().setClaim("sub", claimValue)
## Associate an Update Token script to a client
Until this section of the document, we have going by the assumption that the UpdateToken script will apply to requests from all RPs (clients) registered with Janssen's Authentication server.
However, in a real scenario, you may want to modify claims, or perform checks only when specific RPs (clients ) request for tokens.
return True
```

### 5. Modify a specific token lifetime based on the context:
1. Refresh token lifetime:
```
def getRefreshTokenLifetimeInSeconds(self, context):
return 24 * 60 * 60 # one day
```
2. ID token lifetime:
```
def getIdTokenLifetimeInSeconds(self, context):
return 10 * 60 * 60 # 10 hours
```
3. Access token lifetime:
```
def getAccessTokenLifetimeInSeconds(self, context):
return 10 * 60 * 60 # 10 hours
```

To associate the script with a client execute the command below with appropriate values for
1. inum of the client
2. inum of the update_token script
### 6. modifyRefreshToken() :
Used to modify claims in a Refresh Token
```
# Returns boolean, true - indicates that script applied changes. If false is returned token will not be created.
# refreshToken is reference of io.jans.as.server.model.common.RefreshToken (note authorization grant can be taken as context.getGrant())
# context is reference of io.jans.as.server.service.external.context.ExternalUpdateTokenContext (in https://github.com/JanssenProject/jans-auth-server project, )
def modifyRefreshToken(self, refreshToken, context):
return True
```
/opt/jans/jans-cli/config-cli.py --operation-id patch-oauth-openid-clients-by-inum --url-suffix inum:inum_of_client
--data '[
{
"op": "add",
"path": "updateTokenScriptDns",
"value": ["inum={SCRIPT_ID},ou=scripts,o=jans"]
}
]'
```
## IntrospectionType script vs UpdateTokenType script

| | [`IntrospectionType`](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/introspection/IntrospectionType.java)| [`UpdateTokenType`](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/token/UpdateTokenType.java) |
|---|---|---|
| Client configuration parameter |Introspection script is invoked only when **`accessTokenAsJwt`=`true`** | Update token script is invoked irrespective of whether `accessTokenAsJwt` is `true` or `false` |
| Core Purpose | Used to return access token meta information like current validity, approved scopes, and information about the context in which the token was issued when a Resource Server which queries the [Introspection endpoint](https://datatracker.ietf.org/doc/html/rfc7662) | used to enable transformation of claims and values in id_token and Access token, set a specific token lifetime, change granularity of access control (up-scoping, down-scoping), audit logging for each token response, forbid the creation of AT based on a criteria. |
| Functionality | 1. Can be used to modify claims of an Access token as JWT, however this it is recommended to use UpdateToken script instead. |1. Used to modify id_token, refresh token and access token |
| |2. Introspection script cannot change scope of AT | 2. UpdateToken can change scope of AT and modify AT object in persistence irrespective of the value of `accessTokenAsJwt` as `true` or `false` |
| Script Invocation sequence |2. **After** an Access token is generated | 2. **Before** the creation of AT, id_token and refresh_token |

## Testing

1. Use this: [Reference for testing](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/client/src/test/java/io/jans/as/client/ws/rs/AuthorizationCodeFlowHttpTest.java)
2. Inspect the tokens. Use [jwt.io](jwt.io) to inspect the contents of a JWT.


0 comments on commit 1c93168

Please sign in to comment.