├── .abapgit.xml ├── .gitignore ├── LICENSE ├── README.md ├── abaplint.json ├── doc └── jwt_profile.png └── src ├── package.devc.xml ├── zcl_jwt_generator.clas.abap ├── zcl_jwt_generator.clas.testclasses.abap ├── zcl_jwt_generator.clas.xml ├── zcx_jwt_generator.clas.abap ├── zcx_jwt_generator.clas.xml └── zjwt_profile.tabl.xml /.abapgit.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | E 6 | /src/ 7 | PREFIX 8 | 9 | /.gitignore 10 | /LICENSE 11 | /README.md 12 | /package.json 13 | /.travis.yml 14 | /.gitlab-ci.yml 15 | /abaplint.json 16 | /azure-pipelines.yml 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | src/zcl_jwt_generator.clas.abap.bak 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 abapChaoLiu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![abapLint](https://github.com/abapChaoLiu/abap_jwt_generator/workflows/abapLint/badge.svg?branch=master) 2 | 3 | # ABAP JWT Generator 4 | Generate Json Web Token (JWT) in ABAP. 5 | 6 | ## Demo 1 - method get_jwt_by_profile(). 7 | 8 | If JWT profile is maintained in table ZJWT_PROFILE, use method get_jwt_by_profile() to derive JWT token by profile name. 9 | 10 | ![JWT profile screenshot](/doc/jwt_profile.png) 11 | 12 | ```abap 13 | DATA: jwt_generator TYPE REF TO zcl_jwt_generator, 14 | jwt TYPE string. 15 | 16 | CREATE OBJECT jwt_client. 17 | jwt = jwt_client->get_jwt_by_profile( 'JWT_PROFILE_NAME' ). 18 | ``` 19 | 20 | ## Demo 2 - method generate_jwt(). 21 | If JWT profile is not maintained, use method generate_jwt() to derive JWT token. 22 | 23 | ```abap 24 | REPORT zrpt_jwt_generator_demo. 25 | 26 | DATA: jwt_generator TYPE REF TO zcl_jwt_generator. 27 | DATA: jwt_header TYPE zcl_jwt_generator=>ty_jwt_header, 28 | jwt_claim TYPE zcl_jwt_generator=>ty_jwt_claim. 29 | DATA: exp_second TYPE int8, 30 | ssf_info TYPE ssfinfo. 31 | DATA: start_timestamp TYPE timestamp VALUE '19700101000000', 32 | ssf_id TYPE ssfid VALUE '', 33 | "PSE profile with private key 34 | ssf_profile TYPE ssfprof VALUE 'SAPZJWTSF001.pse'. 35 | 36 | 37 | jwt_header-alg = 'RS256'. 38 | jwt_claim = VALUE #( iss = 'UserID' 39 | sub = 'example@gmail.com' 40 | aud = 'https://login.example.com' 41 | exp = exp_second ). 42 | 43 | ssf_info = VALUE #( id = ssf_id profile = ssf_profile ). 44 | 45 | CREATE OBJECT jwt_generator. 46 | TRY. 47 | jwt_generator->generate_jwt( 48 | EXPORTING 49 | jwt_header = jwt_header 50 | jwt_claim = jwt_claim 51 | ssf_info = ssfinfo 52 | RECEIVING 53 | jwt = DATA(jwt) ). 54 | CATCH zcx_jwt_generator INTO DATA(lo_exp). 55 | WRITE /: 'ERROR when generate JWT token.'. 56 | WRITE /: lo_exp->get_text( ). 57 | ENDTRY. 58 | ``` 59 | 60 | ## Demo 3 - method get_access_token_by_profile(). 61 | 62 | If JWT profile is maintained in table ZJWT_PROFILE, use method get_access_token_by_profile() to get JWT Access Token. 63 | 64 | ![JWT profile screenshot](/doc/jwt_profile.png) 65 | 66 | ```abap 67 | DATA: jwt_generator TYPE REF TO zcl_jwt_generator, 68 | jwt_access_token TYPE string. 69 | 70 | CREATE OBJECT jwt_client. 71 | jwt_access_token = jwt_client->get_access_token_by_profile( 'JWT_PROFILE_NAME' ). 72 | ``` 73 | 74 | ## Demo 4 - method generate_jwt_with_secret(). 75 | You can use method generate_jwt_with_secret() to generate a JWT with the secret key, like [JWT Debugger](https://jwt.io/#debugger-io). 76 | ```abap 77 | GET TIME STAMP FIELD DATA(timestamp). 78 | 79 | DATA(jwt_generator) = NEW zcl_jwt_generator( ). 80 | 81 | TRY. 82 | DATA(jwt_result) = 83 | jwt_generator->generate_jwt_with_secret( 84 | jwt_header = VALUE #( alg = 'HS256' typ = 'JWT' ) 85 | jwt_claim = VALUE #( sub = '1234567890' 86 | name = 'John Doe' 87 | iat = jwt_generator->convert_abap_timestamp_to_unix( timestamp ) ) 88 | secret = '1WRAv0Usf90-jr2W7UQBQBLvIBBFq8vumq-VzrR3h7E' 89 | algorithm = 'SHA256' ). 90 | CATCH zcx_jwt_generator INTO DATA(lx_jwt). 91 | ENDTRY. 92 | ``` 93 | 94 | ## Credits and references 95 | 96 | Class ZCL_JWT_Generator is modified from Dimitri Seifmann's post [Connect from AS ABAP to Google Cloud Platform App-Engine resource secured with Google Identity-Aware Proxy](https://blogs.sap.com/2019/11/10/connect-from-as-abap-to-google-cloud-platform-app-engine-resource-secured-with-google-identity-aware-proxy/). 97 | -------------------------------------------------------------------------------- /abaplint.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "files": "/src/**/*.*", 4 | "skipGeneratedGatewayClasses": true, 5 | "skipGeneratedPersistentClasses": true, 6 | "skipGeneratedFunctionGroups": true 7 | }, 8 | "dependencies": [ 9 | { 10 | "url": "https://github.com/abaplint/deps", 11 | "folder": "/deps", 12 | "files": "/src/**/*.*" 13 | } 14 | ], 15 | "syntax": { 16 | "version": "v740sp08", 17 | "errorNamespace": "^(Z|Y)", 18 | "globalConstants": [ 19 | "abap_func_exporting", 20 | "abap_func_tables", 21 | "cssf_formtype_text", 22 | "seoc_category_exception", 23 | "seoc_category_webdynpro_class", 24 | "seoc_exposure_private", 25 | "seoc_exposure_protected", 26 | "seoc_exposure_public", 27 | "seoc_version_active", 28 | "seoc_version_inactive", 29 | "seok_access_free", 30 | "seok_access_modify", 31 | "seok_pgmid_r3tr", 32 | "seop_ext_class_locals_def", 33 | "seop_ext_class_locals_imp", 34 | "seop_ext_class_macros", 35 | "seop_ext_class_testclasses", 36 | "sews_c_vif_version", 37 | "skwfc_obtype_folder", 38 | "skwfc_obtype_loio", 39 | "so2_controller", 40 | "ststc_c_type_dialog", 41 | "ststc_c_type_object", 42 | "ststc_c_type_parameters", 43 | "ststc_c_type_report", 44 | "swbm_c_op_delete_no_dialog", 45 | "swbm_c_type_ddic_db_tabxinx", 46 | "swbm_c_type_wdy_application", 47 | "swbm_version_active", 48 | "swbm_version_inactive", 49 | "wbmr_c_skwf_folder_class", 50 | "wdyn_limu_component_controller", 51 | "wdyn_limu_component_definition", 52 | "wdyn_limu_component_view" 53 | ], 54 | "globalMacros": [] 55 | }, 56 | "rules": { 57 | "prefix_is_current_class": { 58 | "omitMeInstanceCalls": false 59 | }, 60 | "allowed_object_naming": true, 61 | "check_comments": false, 62 | "fully_type_constants": true, 63 | "keep_single_parameter_on_one_line": false, 64 | "prefer_returning_to_exporting": false, 65 | "selection_screen_naming": true, 66 | "sicf_consistency": true, 67 | "sql_escape_host_variables": false, 68 | "xml_consistency": true, 69 | "check_no_handler_pragma": true, 70 | "newline_between_methods": false, 71 | "chain_mainly_declarations": true, 72 | "check_abstract": true, 73 | "check_text_elements": true, 74 | "type_begin_single_include": true, 75 | "types_naming": false, 76 | "7bit_ascii": { 77 | "exclude": ["zcl_abapgit_utils.clas.testclasses.abap"] 78 | }, 79 | "abapdoc": false, 80 | "check_ddic": true, 81 | "check_include": true, 82 | "form_no_dash": false, 83 | "allowed_object_types": { 84 | "allowed": [ 85 | "PROG", 86 | "CLAS", 87 | "INTF", 88 | "FUGR", 89 | "DEVC", 90 | "TRAN", 91 | "W3MI", 92 | "TABL" 93 | ] 94 | }, 95 | "ambiguous_statement": true, 96 | "avoid_use": { 97 | "define": false, 98 | "endselect": true, 99 | "execSQL": true, 100 | "kernelCall": true, 101 | "communication": true, 102 | "systemCall": true, 103 | "defaultKey": false, 104 | "break": true, 105 | "statics": true 106 | }, 107 | "begin_end_names": true, 108 | "check_transformation_exists": true, 109 | "check_syntax": false, 110 | "class_attribute_names": { 111 | "ignoreExceptions": true, 112 | "statics": ".", 113 | "instance": "." 114 | }, 115 | "cloud_types": true, 116 | "colon_missing_space": true, 117 | "commented_code": false, 118 | "constructor_visibility_public": true, 119 | "contains_tab": true, 120 | "definitions_top": true, 121 | "description_empty": true, 122 | "double_space": false, 123 | "empty_line_in_statement": { 124 | "allowChained": true 125 | }, 126 | "empty_statement": true, 127 | "empty_structure": { 128 | "loop": true, 129 | "if": false, 130 | "while": true, 131 | "case": true, 132 | "select": true, 133 | "do": true, 134 | "at": true 135 | }, 136 | "exit_or_check": true, 137 | "exporting": true, 138 | "form_tables_obsolete": false, 139 | "functional_writing": { 140 | "ignoreExceptions": true 141 | }, 142 | "global_class": true, 143 | "identical_form_names": true, 144 | "if_in_if": true, 145 | "implement_methods": false, 146 | "in_statement_indentation": false, 147 | "indentation": { 148 | "ignoreExceptions": true, 149 | "alignTryCatch": false, 150 | "globalClassSkipFirst": false, 151 | "ignoreGlobalClassDefinition": false, 152 | "ignoreGlobalInterface": false 153 | }, 154 | "inline_data_old_versions": true, 155 | "line_length": { 156 | "length": 240 157 | }, 158 | "line_only_punc": { 159 | "ignoreExceptions": true 160 | }, 161 | "local_class_naming": { 162 | "exception": ".", 163 | "local": ".", 164 | "test": "." 165 | }, 166 | "local_testclass_location": true, 167 | "local_variable_names": { 168 | "expectedData": ".", 169 | "expectedConstant": ".", 170 | "expectedFS": "." 171 | }, 172 | "main_file_contents": true, 173 | "max_one_statement": true, 174 | "message_exists": false, 175 | "method_length": { 176 | "statements": 100, 177 | "ignoreTestClasses": false, 178 | "errorWhenEmpty": false 179 | }, 180 | "method_parameter_names": { 181 | "ignoreExceptions": true, 182 | "importing": ".", 183 | "returning": ".", 184 | "changing": ".", 185 | "exporting": ".", 186 | "ignoreNames": [] 187 | }, 188 | "mix_returning": true, 189 | "msag_consistency": true, 190 | "nesting": { 191 | "depth": 5 192 | }, 193 | "no_public_attributes": false, 194 | "object_naming": { 195 | "clas": "^ZC(L|X)\\_", 196 | "intf": "^ZIF\\_", 197 | "prog": "^Z", 198 | "fugr": "^Z", 199 | "tabl": "^Z", 200 | "ttyp": "^Z", 201 | "dtel": "^Z", 202 | "doma": "^Z", 203 | "msag": "^Z", 204 | "tran": "^Z", 205 | "enqu": "^EZ", 206 | "auth": "^Z", 207 | "pinf": "^Z", 208 | "idoc": "^Z", 209 | "xslt": "^Z" 210 | }, 211 | "obsolete_statement": { 212 | "refresh": true, 213 | "compute": true, 214 | "add": true, 215 | "subtract": true, 216 | "multiply": true, 217 | "move": true, 218 | "divide": true, 219 | "requested": true, 220 | "occurs": true 221 | }, 222 | "parser_error": {}, 223 | "preferred_compare_operator": { 224 | "badOperators": [ 225 | "EQ", 226 | "><", 227 | "NE", 228 | "GE", 229 | "GT", 230 | "LT", 231 | "LE" 232 | ] 233 | }, 234 | "release_idoc": true, 235 | "remove_descriptions": { 236 | "ignoreExceptions": true 237 | }, 238 | "rfc_error_handling": false, 239 | "sequential_blank": { 240 | "lines": 4 241 | }, 242 | "short_case": { 243 | "length": 0, 244 | "allow": [ 245 | "iv_action", 246 | "sy" 247 | ] 248 | }, 249 | "space_before_colon": true, 250 | "space_before_dot": { 251 | "ignoreGlobalDefinition": true, 252 | "ignoreExceptions": true 253 | }, 254 | "start_at_tab": true, 255 | "superclass_final": true, 256 | "tabl_enhancement_category": true, 257 | "type_form_parameters": false, 258 | "unreachable_code": true, 259 | "use_new": true, 260 | "when_others_last": true, 261 | "whitespace_end": false 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /doc/jwt_profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abapChaoLiu/abap_jwt_generator/ac8abf9ffdd62c90ebfa5fb9cfe33995c028d6c7/doc/jwt_profile.png -------------------------------------------------------------------------------- /src/package.devc.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | JWT Generator 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/zcl_jwt_generator.clas.abap: -------------------------------------------------------------------------------- 1 | CLASS zcl_jwt_generator DEFINITION 2 | PUBLIC 3 | CREATE PUBLIC . 4 | 5 | PUBLIC SECTION. 6 | TYPES: 7 | BEGIN OF ty_jwt_header, 8 | alg TYPE string, 9 | typ TYPE string, 10 | END OF ty_jwt_header . 11 | TYPES: 12 | BEGIN OF ty_jwt_claim, 13 | name TYPE string, " Name 14 | iss TYPE string, "Issuer 15 | sub TYPE string, "Subject 16 | aud TYPE string, "Audience 17 | exp TYPE string, "Expiration Time 18 | nbf TYPE string, "Not Before 19 | iat TYPE string, "Issued At 20 | jti TYPE string, "JWT ID 21 | END OF ty_jwt_claim . 22 | 23 | METHODS generate_jwt 24 | IMPORTING 25 | !jwt_header TYPE ty_jwt_header 26 | !jwt_claim TYPE ty_jwt_claim 27 | !ssf_info TYPE ssfinfo 28 | !ssf_format TYPE ssfform DEFAULT 'PKCS1-V1.5' 29 | !ssf_hash_agrithm TYPE ssfhash DEFAULT 'SHA256' 30 | RETURNING 31 | VALUE(jwt) TYPE string 32 | RAISING 33 | zcx_jwt_generator . 34 | 35 | METHODS generate_jwt_with_secret 36 | IMPORTING 37 | !jwt_header TYPE ty_jwt_header 38 | !jwt_claim TYPE ty_jwt_claim 39 | !secret TYPE string 40 | !algorithm TYPE string DEFAULT 'SHA256' 41 | RETURNING 42 | VALUE(result) TYPE string 43 | RAISING 44 | zcx_jwt_generator . 45 | 46 | METHODS get_jwt_by_profile 47 | IMPORTING 48 | !profile TYPE zjwt_profile-profile_name 49 | RETURNING 50 | VALUE(jwt) TYPE string 51 | RAISING 52 | zcx_jwt_generator . 53 | 54 | METHODS get_access_token_by_profile 55 | IMPORTING 56 | !profile TYPE zjwt_profile-profile_name 57 | RETURNING 58 | VALUE(access_token) TYPE string 59 | RAISING 60 | zcx_jwt_generator . 61 | 62 | METHODS base64url_encode 63 | IMPORTING 64 | !unencoded TYPE string 65 | RETURNING 66 | VALUE(base64url) TYPE string . 67 | 68 | METHODS convert_abap_timestamp_to_unix 69 | IMPORTING 70 | !timestamp TYPE timestamp 71 | RETURNING 72 | VALUE(result) TYPE i . 73 | 74 | PROTECTED SECTION. 75 | 76 | PRIVATE SECTION. 77 | TYPES: 78 | ty_tssfbin TYPE STANDARD TABLE OF ssfbin WITH KEY table_line WITHOUT FURTHER SECONDARY KEYS. 79 | 80 | DATA: 81 | jwt_profile TYPE zjwt_profile. 82 | 83 | METHODS format_jwt_claim 84 | IMPORTING jwt_claim TYPE ty_jwt_claim 85 | RETURNING VALUE(result) TYPE ty_jwt_claim. 86 | 87 | METHODS string_to_binary_tab 88 | IMPORTING input_string TYPE string 89 | RETURNING VALUE(output_bins) TYPE ty_tssfbin 90 | RAISING zcx_jwt_generator. 91 | 92 | METHODS binary_tab_to_string 93 | IMPORTING input_bins TYPE ty_tssfbin 94 | length TYPE ssflen 95 | RETURNING VALUE(output_string) TYPE string 96 | RAISING zcx_jwt_generator. 97 | 98 | METHODS format_base64 99 | IMPORTING input TYPE string 100 | RETURNING VALUE(result) TYPE string. 101 | 102 | ENDCLASS. 103 | 104 | 105 | CLASS zcl_jwt_generator IMPLEMENTATION. 106 | 107 | 108 | METHOD base64url_encode. 109 | base64url = format_base64( cl_http_utility=>encode_base64( unencoded = unencoded ) ). 110 | ENDMETHOD. 111 | 112 | 113 | METHOD binary_tab_to_string. 114 | CALL FUNCTION 'SCMS_BINARY_TO_STRING' 115 | EXPORTING 116 | input_length = length 117 | encoding = '4110' 118 | IMPORTING 119 | text_buffer = output_string 120 | TABLES 121 | binary_tab = input_bins 122 | EXCEPTIONS 123 | failed = 1 124 | OTHERS = 2. 125 | IF sy-subrc <> 0. 126 | zcx_jwt_generator=>raise_system( ). 127 | ENDIF. 128 | ENDMETHOD. 129 | 130 | 131 | METHOD convert_abap_timestamp_to_unix. 132 | 133 | CONVERT TIME STAMP timestamp TIME ZONE 'UTC' INTO DATE DATA(date) TIME DATA(time). 134 | 135 | cl_pco_utility=>convert_abap_timestamp_to_java( 136 | EXPORTING 137 | iv_date = date 138 | iv_time = time 139 | IMPORTING 140 | ev_timestamp = DATA(lv_unix_timestamp) ). 141 | 142 | result = 143 | COND #( 144 | WHEN strlen( lv_unix_timestamp ) > 10 145 | THEN lv_unix_timestamp(10) 146 | ELSE lv_unix_timestamp ). 147 | 148 | ENDMETHOD. 149 | 150 | 151 | METHOD format_base64. 152 | 153 | result = input. 154 | 155 | REPLACE ALL OCCURRENCES OF '=' IN result WITH ''. 156 | REPLACE ALL OCCURRENCES OF '+' IN result WITH '-'. 157 | REPLACE ALL OCCURRENCES OF '/' IN result WITH '_'. 158 | 159 | ENDMETHOD. 160 | 161 | 162 | METHOD generate_jwt. 163 | 164 | DATA input_bins TYPE STANDARD TABLE OF ssfbin. 165 | DATA output_bins TYPE STANDARD TABLE OF ssfbin. 166 | DATA input_length TYPE ssflen. 167 | DATA output_length TYPE ssflen. 168 | DATA output_crc TYPE ssfreturn. 169 | DATA signers TYPE STANDARD TABLE OF ssfinfo. 170 | DATA: jwt_claim_json TYPE string, 171 | jwt_header_json TYPE string, 172 | jwt_header_base64url TYPE string, 173 | jwt_claim_base64url TYPE string. 174 | DATA input_base64url TYPE string. 175 | DATA: signature TYPE string, 176 | signature_base64url TYPE string. 177 | 178 | 179 | jwt_header_json = /ui2/cl_json=>serialize( 180 | compress = abap_true 181 | data = jwt_header 182 | pretty_name = /ui2/cl_json=>pretty_mode-low_case ). 183 | 184 | jwt_claim_json = /ui2/cl_json=>serialize( 185 | compress = abap_true 186 | data = jwt_claim 187 | pretty_name = /ui2/cl_json=>pretty_mode-low_case ). 188 | 189 | 190 | jwt_header_base64url = base64url_encode( jwt_header_json ). 191 | jwt_claim_base64url = base64url_encode( jwt_claim_json ). 192 | 193 | 194 | input_base64url = |{ jwt_header_base64url }.{ jwt_claim_base64url }|. 195 | input_length = strlen( input_base64url ). 196 | 197 | input_bins = string_to_binary_tab( input_string = input_base64url ). 198 | 199 | 200 | APPEND ssf_info TO signers. 201 | 202 | CALL FUNCTION 'SSF_KRN_SIGN' 203 | EXPORTING 204 | str_format = ssf_format 205 | b_inc_certs = abap_false 206 | b_detached = abap_false 207 | b_inenc = abap_false 208 | ostr_input_data_l = input_length 209 | str_hashalg = ssf_hash_agrithm 210 | IMPORTING 211 | ostr_signed_data_l = output_length 212 | crc = output_crc " SSF Return code 213 | TABLES 214 | ostr_input_data = input_bins 215 | signer = signers 216 | ostr_signed_data = output_bins 217 | EXCEPTIONS 218 | ssf_krn_error = 1 219 | ssf_krn_noop = 2 220 | ssf_krn_nomemory = 3 221 | ssf_krn_opinv = 4 222 | ssf_krn_nossflib = 5 223 | ssf_krn_signer_list_error = 6 224 | ssf_krn_input_data_error = 7 225 | ssf_krn_invalid_par = 8 226 | ssf_krn_invalid_parlen = 9 227 | ssf_fb_input_parameter_error = 10. 228 | IF sy-subrc <> 0. 229 | zcx_jwt_generator=>raise_system( ). 230 | ENDIF. 231 | 232 | signature = binary_tab_to_string( input_bins = output_bins 233 | length = output_length ). 234 | 235 | signature_base64url = base64url_encode( signature ). 236 | 237 | jwt = |{ input_base64url }.{ signature_base64url }|. 238 | 239 | ENDMETHOD. 240 | 241 | 242 | METHOD generate_jwt_with_secret. 243 | 244 | DATA(header_json) = 245 | /ui2/cl_json=>serialize( 246 | data = jwt_header 247 | compress = abap_true 248 | pretty_name = /ui2/cl_json=>pretty_mode-low_case ). 249 | 250 | DATA(header_base64) = base64url_encode( header_json ). 251 | 252 | DATA(payload_json) = 253 | /ui2/cl_json=>serialize( 254 | data = format_jwt_claim( jwt_claim ) 255 | compress = abap_true 256 | pretty_name = /ui2/cl_json=>pretty_mode-low_case ). 257 | 258 | DATA(payload_base64) = base64url_encode( payload_json ). 259 | 260 | DATA(data_to_sign) = 261 | |{ header_base64 }.{ payload_base64 }|. 262 | 263 | TRY. 264 | cl_abap_hmac=>calculate_hmac_for_char( 265 | EXPORTING 266 | if_algorithm = algorithm 267 | if_key = cl_abap_hmac=>string_to_xstring( secret ) 268 | if_data = data_to_sign 269 | IMPORTING 270 | ef_hmacb64string = DATA(hmacb64string) ). 271 | CATCH cx_abap_message_digest INTO DATA(lx_message_digest). 272 | RAISE EXCEPTION TYPE zcx_jwt_generator 273 | EXPORTING 274 | previous = lx_message_digest. 275 | ENDTRY. 276 | 277 | DATA(lv_signature) = format_base64( hmacb64string ). 278 | 279 | result = 280 | |{ header_base64 }.{ payload_base64 }.{ lv_signature }|. 281 | 282 | ENDMETHOD. 283 | 284 | 285 | METHOD get_access_token_by_profile. 286 | 287 | DATA: 288 | BEGIN OF ls_token, 289 | access_token TYPE string, 290 | END OF ls_token. 291 | DATA: 292 | lo_client TYPE REF TO if_http_client, 293 | jwt TYPE string. 294 | FIELD-SYMBOLS: 295 | TYPE data, 296 | TYPE data. 297 | 298 | jwt = get_jwt_by_profile( profile = profile ). 299 | 300 | cl_http_client=>create_by_url( 301 | EXPORTING 302 | url = jwt_profile-aud " URL 303 | ssl_id = 'ANONYM' " SSL Identity 304 | IMPORTING 305 | client = lo_client " HTTP Client Abstraction 306 | EXCEPTIONS 307 | argument_not_found = 1 308 | plugin_not_active = 2 309 | internal_error = 3 310 | OTHERS = 4 ). 311 | IF sy-subrc <> 0. 312 | zcx_jwt_generator=>raise_system( ). 313 | ENDIF. 314 | 315 | 316 | IF lo_client IS NOT BOUND. 317 | zcx_jwt_generator=>raise_system( ). 318 | ENDIF. 319 | 320 | lo_client->request->set_method( if_http_request=>co_request_method_post ). 321 | lo_client->request->set_formfield_encoding( formfield_encoding = if_http_entity=>co_formfield_encoding_encoded ). 322 | 323 | lo_client->request->set_header_field( 324 | name = 'Content-type' " Name of the header field 325 | value = 'application/x-www-form-urlencoded' ). " HTTP header field value 326 | 327 | lo_client->request->set_form_field( 328 | name = 'grant_type' 329 | value = 'urn:ietf:params:oauth:grant-type:jwt-bearer' ). 330 | 331 | lo_client->request->set_form_field( 332 | name = 'assertion' 333 | value = jwt ). 334 | 335 | lo_client->send( ). 336 | lo_client->receive( 337 | EXCEPTIONS 338 | http_communication_failure = 1 339 | http_invalid_state = 2 340 | http_processing_failed = 3 ). 341 | IF sy-subrc <> 0. 342 | zcx_jwt_generator=>raise_system( ). 343 | ENDIF. 344 | 345 | DATA(response_json) = lo_client->response->get_cdata( ). 346 | 347 | lo_client->close( ). 348 | IF sy-subrc <> 0. 349 | zcx_jwt_generator=>raise_system( ). 350 | ENDIF. 351 | 352 | /ui2/cl_json=>deserialize( 353 | EXPORTING 354 | json = response_json 355 | CHANGING 356 | data = ls_token ). 357 | 358 | access_token = ls_token-access_token. 359 | 360 | ENDMETHOD. 361 | 362 | 363 | METHOD get_jwt_by_profile. 364 | 365 | DATA: jwt_header TYPE ty_jwt_header, 366 | jwt_claim TYPE ty_jwt_claim. 367 | 368 | DATA: current_timestamp TYPE timestamp, 369 | exp_timestamp TYPE tzntstmpl, 370 | diff_second TYPE tzntstmpl, 371 | exp_second TYPE int8, 372 | ssfinfo TYPE ssfinfo. 373 | CONSTANTS: start_timestamp TYPE timestamp VALUE '19700101000000'. 374 | 375 | SELECT SINGLE * FROM zjwt_profile INTO jwt_profile 376 | WHERE profile_name = profile. 377 | IF sy-subrc = 0. 378 | GET TIME STAMP FIELD current_timestamp. 379 | cl_abap_tstmp=>add( 380 | EXPORTING 381 | tstmp = current_timestamp " UTC Time Stamp 382 | secs = jwt_profile-time_interval " Time Interval in Seconds 383 | RECEIVING 384 | r_tstmp = exp_timestamp ). " UTC Time Stamp 385 | cl_abap_tstmp=>subtract( 386 | EXPORTING 387 | tstmp1 = exp_timestamp " UTC Time Stamp 388 | tstmp2 = start_timestamp " UTC Time Stamp 389 | RECEIVING 390 | r_secs = diff_second ). " Time Interval in Seconds 391 | 392 | 393 | MOVE-CORRESPONDING jwt_profile TO jwt_claim. 394 | exp_second = diff_second. 395 | jwt_claim-exp = exp_second. 396 | 397 | ssfinfo-id = jwt_profile-ssf_id. 398 | ssfinfo-profile = jwt_profile-ssf_profile. 399 | jwt_header-alg = jwt_profile-alg. 400 | 401 | generate_jwt( 402 | EXPORTING 403 | jwt_header = jwt_header 404 | jwt_claim = jwt_claim 405 | ssf_info = ssfinfo 406 | RECEIVING 407 | jwt = jwt ). 408 | 409 | ELSE. 410 | "TO DO 411 | zcx_jwt_generator=>raise_system( ). 412 | ENDIF. 413 | 414 | ENDMETHOD. 415 | 416 | 417 | METHOD format_jwt_claim. 418 | 419 | result = jwt_claim. 420 | 421 | result-iat = condense( result-iat ). 422 | result-exp = condense( result-exp ). 423 | 424 | ENDMETHOD. 425 | 426 | 427 | METHOD string_to_binary_tab. 428 | DATA lv_xstring TYPE xstring. 429 | 430 | CALL FUNCTION 'SCMS_STRING_TO_XSTRING' 431 | EXPORTING 432 | text = input_string 433 | encoding = '4110' 434 | IMPORTING 435 | buffer = lv_xstring 436 | EXCEPTIONS 437 | failed = 1 438 | OTHERS = 2. 439 | IF sy-subrc <> 0. 440 | zcx_jwt_generator=>raise_system( ). 441 | ENDIF. 442 | 443 | CALL FUNCTION 'SCMS_XSTRING_TO_BINARY' 444 | EXPORTING 445 | buffer = lv_xstring 446 | TABLES 447 | binary_tab = output_bins. 448 | IF sy-subrc <> 0. 449 | zcx_jwt_generator=>raise_system( ). 450 | ENDIF. 451 | ENDMETHOD. 452 | ENDCLASS. 453 | -------------------------------------------------------------------------------- /src/zcl_jwt_generator.clas.testclasses.abap: -------------------------------------------------------------------------------- 1 | CLASS ltcl_test DEFINITION FINAL 2 | FOR TESTING 3 | RISK LEVEL HARMLESS 4 | DURATION SHORT. 5 | 6 | PUBLIC SECTION. 7 | 8 | PRIVATE SECTION. 9 | METHODS generate_jwt_with_secret FOR TESTING. 10 | 11 | ENDCLASS. 12 | 13 | CLASS ltcl_test IMPLEMENTATION. 14 | 15 | 16 | METHOD generate_jwt_with_secret. 17 | 18 | DATA: 19 | BEGIN OF ls_jwt, 20 | header TYPE string, 21 | payload TYPE string, 22 | secret TYPE string, 23 | END OF ls_jwt. 24 | 25 | DATA(lo_jwt_generator) = NEW zcl_jwt_generator( ). 26 | 27 | TRY. 28 | DATA(lv_jwt) = 29 | lo_jwt_generator->generate_jwt_with_secret( 30 | jwt_header = VALUE #( alg = 'HS256' typ = 'JWT' ) 31 | jwt_claim = VALUE #( sub = '1234567890' name = 'John Doe' iat = lo_jwt_generator->convert_abap_timestamp_to_unix( '20180118013022' ) ) 32 | secret = '1WRAv0Usf90-jr2W7UQBQBLvIBBFq8vumq-VzrR3h7E' 33 | algorithm = 'SHA256' ). 34 | CATCH zcx_jwt_generator INTO DATA(lx_jwt). 35 | cl_abap_unit_assert=>fail( msg = lx_jwt->get_text( ) detail = lx_jwt->get_longtext( ) ). 36 | ENDTRY. 37 | 38 | SPLIT lv_jwt AT '.' INTO ls_jwt-header ls_jwt-payload ls_jwt-secret. 39 | 40 | cl_abap_unit_assert=>assert_equals( 41 | act = ls_jwt-header 42 | exp = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' 43 | msg = 'Header has invalid content' ). 44 | 45 | cl_abap_unit_assert=>assert_equals( 46 | act = ls_jwt-payload 47 | exp = 'eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoiMTUxNjIzOTAyMiJ9' 48 | msg = 'Payload has invalid content' ). 49 | 50 | cl_abap_unit_assert=>assert_equals( 51 | act = ls_jwt-secret 52 | exp = '_GNK9PCjJnoeHZsNx9F-7TnZF8m_jS4lodaNe_w94MM' 53 | msg = 'Secret has invalid content' ). 54 | 55 | ENDMETHOD. 56 | 57 | 58 | ENDCLASS. 59 | -------------------------------------------------------------------------------- /src/zcl_jwt_generator.clas.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | ZCL_JWT_GENERATOR 7 | E 8 | Json Web Token Generator 9 | 1 10 | X 11 | X 12 | X 13 | X 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/zcx_jwt_generator.clas.abap: -------------------------------------------------------------------------------- 1 | CLASS zcx_jwt_generator DEFINITION 2 | PUBLIC 3 | INHERITING FROM cx_static_check 4 | FINAL 5 | CREATE PUBLIC . 6 | 7 | PUBLIC SECTION. 8 | 9 | INTERFACES if_t100_dyn_msg . 10 | INTERFACES if_t100_message . 11 | 12 | DATA: 13 | text1 TYPE string READ-ONLY, 14 | text2 TYPE string READ-ONLY, 15 | text3 TYPE string READ-ONLY, 16 | text4 TYPE string READ-ONLY. 17 | 18 | CLASS-METHODS raise_system 19 | RAISING 20 | zcx_jwt_generator. 21 | 22 | METHODS constructor 23 | IMPORTING 24 | !textid LIKE if_t100_message=>t100key OPTIONAL 25 | !text1 TYPE string OPTIONAL 26 | !text2 TYPE string OPTIONAL 27 | !text3 TYPE string OPTIONAL 28 | !text4 TYPE string OPTIONAL 29 | !previous LIKE previous OPTIONAL. 30 | 31 | PROTECTED SECTION. 32 | 33 | PRIVATE SECTION. 34 | 35 | ENDCLASS. 36 | 37 | 38 | 39 | CLASS zcx_jwt_generator IMPLEMENTATION. 40 | 41 | 42 | METHOD raise_system. 43 | 44 | RAISE EXCEPTION TYPE zcx_jwt_generator 45 | EXPORTING 46 | textid = VALUE scx_t100key( msgid = sy-msgid 47 | msgno = sy-msgno 48 | attr1 = 'TEXT1' 49 | attr2 = 'TEXT2' 50 | attr3 = 'TEXT3' 51 | attr4 = 'TEXT4' ) 52 | text1 = CONV #( sy-msgv1 ) 53 | text2 = CONV #( sy-msgv2 ) 54 | text3 = CONV #( sy-msgv3 ) 55 | text4 = CONV #( sy-msgv4 ). 56 | 57 | ENDMETHOD. 58 | 59 | 60 | METHOD constructor ##ADT_SUPPRESS_GENERATION. 61 | 62 | super->constructor( previous = previous ). 63 | 64 | me->text1 = text1. 65 | me->text2 = text2. 66 | me->text3 = text3. 67 | me->text4 = text4. 68 | 69 | CLEAR me->textid. 70 | IF textid IS INITIAL. 71 | if_t100_message~t100key = if_t100_message=>default_textid. 72 | ELSE. 73 | if_t100_message~t100key = textid. 74 | ENDIF. 75 | 76 | ENDMETHOD. 77 | 78 | 79 | ENDCLASS. 80 | -------------------------------------------------------------------------------- /src/zcx_jwt_generator.clas.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | ZCX_JWT_GENERATOR 7 | E 8 | Generator exception 9 | 40 10 | 1 11 | X 12 | X 13 | X 14 | 15 | 16 | 17 | ZCX_JWT_GENERATOR 18 | CONSTRUCTOR 19 | E 20 | CONSTRUCTOR 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/zjwt_profile.tabl.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | ZJWT_PROFILE 7 | E 8 | TRANSP 9 | Json Web Token Profile 10 | X 11 | C 12 | 1 13 | 14 | 15 | ZJWT_PROFILE 16 | A 17 | 0 18 | APPL0 19 | N 20 | R 21 | 22 | 23 | 24 | ZJWT_PROFILE 25 | PROFILE_NAME 26 | 0001 27 | X 28 | CHAR40 29 | 0 30 | X 31 | E 32 | 33 | 34 | ZJWT_PROFILE 35 | ISS 36 | E 37 | 0002 38 | 0 39 | g 40 | 000008 41 | STRG 42 | STRG 43 | Issuer 44 | 45 | 46 | ZJWT_PROFILE 47 | SUB 48 | E 49 | 0003 50 | 0 51 | g 52 | 000008 53 | STRG 54 | STRG 55 | subject 56 | 57 | 58 | ZJWT_PROFILE 59 | AUD 60 | E 61 | 0004 62 | 0 63 | g 64 | 000008 65 | STRG 66 | STRG 67 | Audience 68 | 69 | 70 | ZJWT_PROFILE 71 | TIME_INTERVAL 72 | E 73 | 0005 74 | 0 75 | 8 76 | 000008 77 | INT8 78 | 000019 79 | INT8 80 | 81 | 82 | ZJWT_PROFILE 83 | SSF_ID 84 | 0006 85 | SSFID 86 | 0 87 | E 88 | 89 | 90 | ZJWT_PROFILE 91 | SSF_PROFILE 92 | 0007 93 | SSFPROF 94 | 0 95 | E 96 | 97 | 98 | ZJWT_PROFILE 99 | ALG 100 | E 101 | 0008 102 | 0 103 | g 104 | 000008 105 | STRG 106 | STRG 107 | Encryption Algorithm 108 | 109 | 110 | 111 | 112 | 113 | --------------------------------------------------------------------------------