Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Ceptor supports OAuth2 / OpenID Connect (OIDC) and exchanging JWT tokens with 3rd parties.

Tip

To use these federations, you should add the authentication plugin dk.itp.security.authentication.oauth.jwt.JWTAuthenticationPlugin


This support is an integral part of Ceptor API Management but can also be used outside the API Management scope as a means for federation of identities both from Ceptor to Service Providers and from OpenID or OAuth2 Providers to Ceptor.

...

  • Implicit Flow
  • Authorization Code Flow
  • Authorization Code with PKCE Extension
  • Hybrid Flow
  • Refresh Token 
  • Client Credentials Flow
  • Resource Owner Password Credentials
  • Token Introspection / Revocation
  • Token Exchange

For each scope, you can configure which attributes are added to the various types of tokens, ID Token, Access Token and Userinfo JSON.

...

Code Block
languagejava
titleIOAuthDataStore & ClientData clases
linenumberstrue
collapsetrue
public interface IOAuthDataStore {
	/**
	 * Gives the datastore its configuration
	 * @param config Configuration
	 */
	public void setConfiguration(Properties config) throws ConfigurationException;
	
	/**
	 * Gives the datastore its configuration
	 * @param config Configuration
	 * @param json JSON Configuration
	 */
	public void setConfiguration(Properties config, JSONObject json) throws ConfigurationException, JSONException;
	
	/**
	 * Return a list of known client IDs
	 * @return List of known client IDs
	 */
	public Collection<String> getKnownClientIDs();
	
	/**
	 * Returns known ClientData from a clientID
	 * 
	 * @param clientID Client ID
	 * @return Client Data - includes secret, valid scopes, valid redirect URIs etc.
	 */
	public ClientData getClientData(String clientID);
	
	default void initialize(PTSServer sessionController) {
	}
}

public class ClientData {
	public enum AccessTokenType {
		JWT, UUID, RFC9068, RFC9060UP
	}
	
	/** Client ID */
	public String clientID;
	/** Client secret */
	public String clientSecret;
	/** Valid OAuth2 grant type; implicit, authorization_code */
	public Collection<String> validGrantTypes;
	/** Valid scopes, e.g. email, profile, name, photo */
	public Collection<String> allowedScopes;
	/** Allowed redirect URIs */
	public Collection<String> allowedUris;
	/** Allowed logout URIs */
	public Collection<String> allowedLogoutUris;
	/** Number of seconds the access token is valid for */
	public int accessTokenValiditySeconds;
	/** Number of seconds the refresh token is valid for */
	public int refreshTokenValiditySeconds;
	
	/** The maximum expiration time in minutes allowed */
	public int maximumIdTokenExpirationMinutes;
	/** Name of token configuration to use with this client */
	public String tokenname;
	/** Type of access token to generate */
	public AccessTokenType accessTokenType;
	
	/** A list of roles for this client - could be roles for an API partner */
	public List<String> roles;
	
	public Map<String, Object> properties = new LinkedHashMap<>();
	
	public String toJSON() {
		JSONObject jo = new JSONObject();
		try {
			jo.put("client_id", clientID);
			jo.put("client_secret", clientSecret);
			jo.put("valid_grant_types", validGrantTypes);
			jo.put("allowed_scopes", allowedScopes);
			jo.put("allowed_uris", allowedUris);
			jo.put("allowed_logout_uris", allowedLogoutUris);
			jo.put("accesstoken_valid_seconds", accessTokenValiditySeconds);
			jo.put("refreshtoken_validity_seconds", refreshTokenValiditySeconds);
			jo.put("maximum_idtoken_expiration_minutes", maximumIdTokenExpirationMinutes);
			jo.put("tokenname", tokenname);
			jo.put("accesstoken_type", accessTokenType.toString());
			
			for(String key : properties.keySet()) {
				if (!jo.has(key))
					jo.put(key,  properties.get(key));
			}
			return jo.toString();
		} catch(JSONException e) {
			return "{\"error\": \"" + JSONObject.quote(e.toString())+"\"}";
		}
	}
	
	public static ClientData fromJSON(JSONObject jo) throws JSONException {
		ClientData cd = new ClientData();
		cd.clientID = jo.optString("client_id", null);
		cd.clientSecret = jo.optString("client_secret", null);
		cd.validGrantTypes = fromJA(jo.optJSONArray("valid_grant_types"));
		cd.allowedScopes = fromJA(jo.optJSONArray("allowed_scopes"));
		cd.allowedUris = fromJA(jo.optJSONArray("allowed_uris"));
		cd.allowedLogoutUris = fromJA(jo.optJSONArray("allowed_logout_uris"));
		cd.accessTokenValiditySeconds = jo.optInt("accesstoken_valid_seconds");
		cd.refreshTokenValiditySeconds = jo.optInt("refreshTokenValiditySeconds");
		cd.maximumIdTokenExpirationMinutes = jo.optInt("maximumIdTokenExpirationMinutes");
		cd.tokenname = jo.optString("tokenname", null);
		cd.accessTokenType = AccessTokenType.valueOf(jo.optString("accesstoken_type", "UUID").toUpperCase());
		
		for(String key : JSONObject.getNames(jo)) {
			cd.properties.put(key, jo.get(key));
		}
		return cd;
	}
	
	private static Collection<String> fromJA(JSONArray ja) throws JSONException {
		if (ja == null || ja.length() == 0)
			return Collections.emptyList();
		
		List<String> result = new ArrayList<>(ja.length());
		for(int i = 0; i < ja.length(); i++) {
			result.add(ja.getString(i));
		}
		return result;
	}
}

...

Default: None
JSON key is password

No Proxy For

Hostname matching this pattern will not be proxied

Default: None
JSON key is noproxyfor

Tokens

OAuth2 / OpenID Connect Token issuers and validators

...

For each individual token, you can specify the following setttings:

Image RemovedImage Added

Configuration for each token is stored as a JSON Object within the tokens JSON array within the federations JSON.

...

Tip
titleInput to Scripts

For the 3 script types above, see the code completion in the editor for detailed information about which variables are available to the script.

On high level, this is:

  • context
    Context containing other variables available to the script
  • context.configuredToken
    Contains the token configuration.
    See each field in the code completion help.
  • context.jo
    Parsed JSON object
  • context.json
    JSON (as a string) object containing the input  token
  • context.session
    Contains Ceptor's internal session for the user the token is created for - this contains all the information available about the authenticated user.
    Details about each field available in the code completion.

The script can return a modified token / JSON userinfo as a string.

Example:

Code Block
languagejs
titleScript to modify Acces Token / ID Token or Userinfo
function modifyToken() {
    var token = JSON.parse(context.json);
    
    token.custom = 'Hello There: ' + token.sub;
    token.sid= context.session.sessionID;
    
    return JSON.stringify(token);
}

modifyToken();


Token

...

UserID attribute

Name of the attribute to copy userid from - if not specified, the standard subject (sub) attribute is used instead.

Default: None
JSON key is userid.attribute.name

Username attribute

Name of the attribute to copy username from.

Default: name
JSON key is username.attribute.name

Role/Group attribute

Name of the attribute to copy user groups/roles from.

Default: groups
JSON key is role.attribute.name

Role pattern

When copying user groups/roles from the token into the session, only groups matching the specified pattern will be used.

Default: *
JSON key is role.pattern

Relax key checks

If checked, insecure tokens, such as those using weak keys, or crypto algorithm None will be allowed.

Default: false
JSON key is relax.key.checks

Expires at exact time

If checked, the session will not expire using normal idle timeout, but it will expire at the exact time that the issued token expires. Check this to disable idle-timeout for sessions.

Default: false
JSON key is expires.at.exact.time

Require subject (sub)

Check this if subject (sub) attribute must be present in the token.

Default: false
JSON key is require.subject

Valid audiences

If set, specify one or more valid audience strings that this token must match - if not set, audience (aud attribute) is not checked.

Default: No limits
JSON key is validaudiences as a JSON Array of strings

Clockskew (seconds)

Specify the number of seconds to allow for clock skew / time difference when validating nfb/exp attributes - setting this to a high value will allow tokens x seconds after they have expired or x seconds before they become valid. This can be useful to allow for a certain difference in clocks between machines.

Default: 0
JSON key is clockskew.seconds

Attributes to store

Patter matching list of attributtes to store in session - if attribute in token matches this pattern, it will be stored in the session.

Note

If Custom Attribute Mapping is specified, "Attributes to store" is not used, and t he custom attribute mapping is used instead.

Default: *
JSON key is attributes.to.store.in.session

Custom Attribute Mapping

Allows customized mapping of attribute values.
Each attribute is stored as a JSON object with key and value attributes inside it.

Key is one of:

...

Exchange Script

This script is called whenever a token exchange is requested.

Default: None
JSON key is script.tokenexchange

Tip

Javascript, Groovy or Python code is executed to process the token exchange input, add validation or provide extra information for use when the access token is generated.


If incoming subject_token is not recognizable as earlier issued by Ceptor, you can parse it here and populate the session with relevant values before processing continues.

Variables
Your script is called when a variable called context with the following attributes within it:

  • configuredToken
    JWTHelper.JWTToken object, the configured token
  • session
    Ceptor's internal session object
  • sessionCtrl
    Reference to Ceptor's Session Controller
  • helper
    Reference to Ceptor's JWTHelper instance
  • actorSession
    Ceptor's session object for the actor token
  • resource
    Resource name this token is meant for
  • audience
    Audience from input
  • input
    Properties object containing all parameters from request to token URL
  • validAudiences
    List of valid audiences, or empty

If the subject_token was found and recognized as an earlier issued token, the context.session is populated with the contents of the session.

If not, the token is available in context.session.ticket and you can parse it and populate the session with the results. If context.session.userid is empty when the script returns, it is assumed that the parsing failed and an error will be sent back to the client informing that the token was not valid.

If an actor token was provided and recognized, the session of the actor is populated with information - if not recognized you can find the token (along with any other input parameters from the request in the input java.util.Properties object.

In this script, you can modify the session contents before the final access token is generated. The access token will be generated by default as a copy of the incoming subject_token but with an attribute "act" added with the subject of the actor token in the "sub" field, and the client_id in the "client_id" field of the "act" object.

In the access token generation script, you get a chance to modify the properties after the token is generated before it gets signed - the token exchange script allows you to modify the session context before the resulting access token is generated.


Example:

Code Block
function exchange() {
    if (context.actorSession) {
        context.actorSession.userid = 'Killroy was here';
    }
    if (!context.input.get('actor_token')) {
        context.session.userid = 'On behalf of ' + context.session.userid
    }
}

exchange();



Token validation related configuration

UserID attribute

Name of the attribute to copy userid from - if not specified, the standard subject (sub) attribute is used instead.

Default: None
JSON key is userid.attribute.name

Username attribute

Name of the attribute to copy username from.

Default: name
JSON key is username.attribute.name

Role/Group attribute

Name of the attribute to copy user groups/roles from.

Default: groups
JSON key is role.attribute.name

Role pattern

When copying user groups/roles from the token into the session, only groups matching the specified pattern will be used.

Default: *
JSON key is role.pattern

Relax key checks

If checked, insecure tokens, such as those using weak keys, or crypto algorithm None will be allowed.

Default: false
JSON key is relax.key.checks

Expires at exact time

If checked, the session will not expire using normal idle timeout, but it will expire at the exact time that the issued token expires. Check this to disable idle-timeout for sessions.

Default: false
JSON key is expires.at.exact.time

Require subject (sub)

Check this if subject (sub) attribute must be present in the token.

Default: false
JSON key is require.subject

Valid audiences

If set, specify one or more valid audience strings that this token must match - if not set, audience (aud attribute) is not checked.

Default: No limits
JSON key is validaudiences as a JSON Array of strings

Clockskew (seconds)

Specify the number of seconds to allow for clock skew / time difference when validating nfb/exp attributes - setting this to a high value will allow tokens x seconds after they have expired or x seconds before they become valid. This can be useful to allow for a certain difference in clocks between machines.

Default: 0
JSON key is clockskew.seconds

Attributes to store

Patter matching list of attributtes to store in session - if attribute in token matches this pattern, it will be stored in the session.

Note

If Custom Attribute Mapping is specified, "Attributes to store" is not used, and t he custom attribute mapping is used instead.


Default: *
JSON key is attributes.to.store.in.session

Custom Attribute Mapping

Allows customized mapping of attribute values.
Each attribute is stored as a JSON object with key and value attributes inside it.

Key is one of:

  • groups
    User groups
  • userid
    User ID
  • username
    User Name
  • customerid
    Customer ID
  • isinternal
    True if user is internal, false if not
  • agreementid
    Agreement ID
  • authmethod
    Authentication method ID
  • authlvl
    Authentication Level
  • _state_XXXX where XXXX is the name of a state variable.
  • Any other value XXXX
    The state variable XXXX is set to the value

...

"oauth2.clients
Code Block
languagejs
titleExample configuration
linenumberstrue
collapsetrue
true
"oauth2.clients": [{
  "name": "Sample",
  "client_id": "https://www.example.com/",
  "description": "Example client",
  "client_secret": "secret",
  "accesstoken_type": "UUID",
  "allowed_uris": ["https://www.example.com/oauth2"],
  "allowed_logout_uris": [{],
  "namevalid_grant_types": "Sample" [
    "authorization_code",
    "implicit",
    "hybrid",
    "refresh_token"
  ],
  "clientallowed_idscopes": "https://www.example.com/",[
    "openid",
    "profile",
    "descriptionoffline_access":
 "Example client"],
  "clientrefreshtoken_validity_secretseconds": "secret"86400,
  "accesstoken_type": "UUID",
  "allowed_uris": ["https://www.example.com/oauth2"],
  "allowed_logout_uris": [],
  "valid_grant_types": [
    "authorization_code",
    "implicit",
    "hybrid",
    "refresh_token"
  ],
  "allowed_scopes": [
    "openid",
    "profile",
    "offline_access"
  ],
  "refreshtoken_validity_seconds": 86400,
  "maximum_idtoken_expiration_minutes": 120
}]

Image Removed

OAuth2 / OpenID Connect Client

Name

Name of OAuth 2 / OpenID Connect client or partner.

Default: None
JSON key is name

Description

Optional description

Default: None
JSON key is description

Client ID

Client ID - must be unique across all configured clients.

Default: None
JSON key is client_id

Client Secret

Client secret - may be encrypted/obfuscated.

Default: None
JSON key is client_secret

Accesstoken type

Type of access token to issuer - either UUID or JWT - if UUID, the token itself does not contain any attributes, and the OAuth2 introspection (or userinfo) URL must be called using the access token to get any of the attributes related to it. If the type is JWT, the access token is similar to the ID token a signed JWT token containing the attributes itself.

...

maximum_idtoken_expiration_minutes": 120
}]


Image Added

OAuth2 / OpenID Connect Client

Name

Name of OAuth 2 / OpenID Connect client or partner.

Default: None
JSON key is name

Description

Optional description

Default: None
JSON key is description

Client ID

Client ID - must be unique across all configured clients.

Default: None
JSON key is client_id

Client Secret

Client secret - may be encrypted/obfuscated.

Default: None
JSON key is client_secret

Accesstoken type

Type of access token to issuer - either UUID or JWT/RFC9068/RFC9068UP - if UUID, the token itself does not contain any attributes, and the OAuth2 introspection (or userinfo) URL must be called using the access token to get any of the attributes related to it. If the type is JWT, the access token is similar to the ID token a signed JWT token containing the attributes itself.

For JWT tokens, RFC9068 / RFC9068UP are variants of JWT tokens where they set the "typ" header value to "at-jwt" or "at-JWT" respectively.

Info
titleWhy RFC9068 / RFC9068UP

RFC 9068 (see https://www.rfc-editor.org/rfc/rfc9068.html) mentions in the textual description and registration section that the type must be set to "at-jwt" but the examples within it set it to "at-JWT" with JWT in uppercase. Since some client might care about the case, you can select the type RFC9068 for the lowercase version, but also RFC9068UP for the uppercase version used in the examples.


Default: UUID
JSON key is accesstoken_type

...