diff --git a/app/controllers/authenticate_controller.rb b/app/controllers/authenticate_controller.rb index 12e8b73c2b..563294e02b 100644 --- a/app/controllers/authenticate_controller.rb +++ b/app/controllers/authenticate_controller.rb @@ -9,13 +9,14 @@ def oidc_authenticate_code_redirect # params. This will likely need to be done via the Handler. params.permit! - auth_token = Authentication::Handler::AuthenticationHandler.new( + auth_token, refresh_token = Authentication::Handler::AuthenticationHandler.new( authenticator_type: params[:authenticator] ).call( parameters: params.to_hash.symbolize_keys, request_ip: request.ip ) + response.set_header('X-OIDC-Refresh-Token', refresh_token) unless refresh_token.nil? render_authn_token(auth_token) rescue => e log_backtrace(e) diff --git a/app/domain/authentication/authn_oidc/v2/client.rb b/app/domain/authentication/authn_oidc/v2/client.rb index 86488ac368..b27e97b10f 100644 --- a/app/domain/authentication/authn_oidc/v2/client.rb +++ b/app/domain/authentication/authn_oidc/v2/client.rb @@ -49,6 +49,7 @@ def callback(code:) nonce: @authenticator.nonce ) id_token = bearer_token.id_token || bearer_token.access_token + refresh_token = bearer_token.refresh_token begin attempts ||= 0 @@ -72,7 +73,7 @@ def callback(code:) client_id: @authenticator.client_id, nonce: @authenticator.nonce ) - decoded_id_token + [decoded_id_token, refresh_token] rescue OpenIDConnect::ValidationFailed => e raise Errors::Authentication::AuthnOidc::TokenVerificationFailed, e.message end diff --git a/app/domain/authentication/authn_oidc/v2/strategy.rb b/app/domain/authentication/authn_oidc/v2/strategy.rb index a76f6ca64d..f71e87c42d 100644 --- a/app/domain/authentication/authn_oidc/v2/strategy.rb +++ b/app/domain/authentication/authn_oidc/v2/strategy.rb @@ -18,16 +18,13 @@ def callback(args) # TODO: Check that `code` and `state` attributes are present raise Errors::Authentication::AuthnOidc::StateMismatch unless args[:state] == @authenticator.state - identity = resolve_identity( - jwt: @client.callback( - code: args[:code] - ) - ) + jwt, refresh_token = @client.callback(code: args[:code]) + identity = resolve_identity(jwt: jwt) unless identity.present? raise Errors::Authentication::AuthnOidc::IdTokenClaimNotFoundOrEmpty, @authenticator.claim_mapping end - identity + [identity, refresh_token] end def resolve_identity(jwt:) diff --git a/app/domain/authentication/handler/authentication_handler.rb b/app/domain/authentication/handler/authentication_handler.rb index 9731b6d1ee..93f71d5945 100644 --- a/app/domain/authentication/handler/authentication_handler.rb +++ b/app/domain/authentication/handler/authentication_handler.rb @@ -52,10 +52,12 @@ def call(parameters:, request_ip:) ) end + identity, refresh_token = @strategy.new( + authenticator: authenticator + ).callback(parameters) + role = @identity_resolver.new.call( - identity: @strategy.new( - authenticator: authenticator - ).callback(parameters), + identity: identity, account: parameters[:account], allowed_roles: @role.that_can( :authenticate, @@ -72,10 +74,10 @@ def call(parameters:, request_ip:) log_audit_success(authenticator, role, request_ip, @authenticator_type) - TokenFactory.new.signed_token( + [TokenFactory.new.signed_token( account: parameters[:account], username: role.role_id.split(':').last - ) + ), refresh_token] rescue => e log_audit_failure(parameters[:account], parameters[:service_id], request_ip, @authenticator_type, e) handle_error(e) diff --git a/cucumber/_authenticators_common/features/support/authenticator_helpers.rb b/cucumber/_authenticators_common/features/support/authenticator_helpers.rb index e460bffa12..d629031b26 100644 --- a/cucumber/_authenticators_common/features/support/authenticator_helpers.rb +++ b/cucumber/_authenticators_common/features/support/authenticator_helpers.rb @@ -76,6 +76,7 @@ def get(path, options = {}) ) result = RestClient::Request.execute(options) @response_body = result.body + @response_headers = result.headers @http_status = result.code rescue RestClient::Exception => e @rest_client_error = e diff --git a/cucumber/authenticators_oidc/features/authn_oidc_v2.feature b/cucumber/authenticators_oidc/features/authn_oidc_v2.feature index a0c1b38355..7c55dc1e8f 100644 --- a/cucumber/authenticators_oidc/features/authn_oidc_v2.feature +++ b/cucumber/authenticators_oidc/features/authn_oidc_v2.feature @@ -55,6 +55,14 @@ Feature: OIDC Authenticator V2 - Users can authenticate with OIDC authenticator cucumber:user:alice successfully authenticated with authenticator authn-oidc service cucumber:webservice:conjur/authn-oidc/keycloak2 """ + @smoke + Scenario: A valid code to get Conjur access token and OIDC refresh token + Given I enable OIDC V2 refresh token flows for "keycloak2" + And I fetch a code for username "alice" and password "alice" + When I authenticate via OIDC V2 with code + Then user "alice" has been authorized by Conjur + And The authentication response includes header "X-OIDC-Refresh-Token" + @smoke Scenario: A valid code with email as claim mapping Given I extend the policy with: diff --git a/cucumber/authenticators_oidc/features/step_definitions/authn_oidc_steps.rb b/cucumber/authenticators_oidc/features/step_definitions/authn_oidc_steps.rb index 00b54b3697..a77c3a4018 100644 --- a/cucumber/authenticators_oidc/features/step_definitions/authn_oidc_steps.rb +++ b/cucumber/authenticators_oidc/features/step_definitions/authn_oidc_steps.rb @@ -180,6 +180,10 @@ create_oidc_secret("provider-scope", oidc_scope, service_id) end +Given(/^I enable OIDC V2 refresh token flows for "([^"]*)"$/) do |service_id| + create_oidc_secret("provider-scope", "#{oidc_scope},offline_access", service_id) +end + When(/^I authenticate via OIDC V2 with code and service-id "([^"]*)"$/) do |service_id| authenticate_code_with_oidc( service_id: service_id, @@ -187,6 +191,11 @@ ) end +Then(/^The authentication response includes header "([^"]*)"$/) do |header| + header_sym = header.parameterize.underscore.to_sym + expect(@response_headers[header_sym]).not_to be_nil +end + Then(/^The okta user has been authorized by conjur/) do username = ENV['OKTA_USERNAME'] expect(retrieved_access_token.username).to eq(username) diff --git a/spec/app/domain/authentication/authn-oidc/v2/client_spec.rb b/spec/app/domain/authentication/authn-oidc/v2/client_spec.rb index 8074da1d01..c0ce469919 100644 --- a/spec/app/domain/authentication/authn-oidc/v2/client_spec.rb +++ b/spec/app/domain/authentication/authn-oidc/v2/client_spec.rb @@ -3,17 +3,23 @@ require 'spec_helper' RSpec.describe(Authentication::AuthnOidc::V2::Client) do + let(:authn_config) do + { + :provider_uri => 'https://dev-92899796.okta.com/oauth2/default', + :redirect_uri => 'http://localhost:3000/authn-oidc/okta-2/cucumber/authenticate', + :client_id => '0oa3w3xig6rHiu9yT5d7', + :client_secret => 'e349BMTTIpLO-rPuPqLLkLyH_pO-loUzhIVJCrHj', + :claim_mapping => 'foo', + :nonce => '1656b4264b60af659fce', + :state => 'state', + :account => 'bar', + :service_id => 'baz' + } + end + let(:authenticator) do Authentication::AuthnOidc::V2::DataObjects::Authenticator.new( - provider_uri: 'https://dev-92899796.okta.com/oauth2/default', - redirect_uri: 'http://localhost:3000/authn-oidc/okta-2/cucumber/authenticate', - client_id: '0oa3w3xig6rHiu9yT5d7', - client_secret: 'e349BMTTIpLO-rPuPqLLkLyH_pO-loUzhIVJCrHj', - claim_mapping: 'foo', - nonce: '1656b4264b60af659fce', - state: 'state', - account: 'bar', - service_id: 'baz' + **authn_config ) end @@ -29,13 +35,16 @@ # Because JWT tokens have an expiration timeframe, we need to hold # time constant after caching the request. travel_to(Time.parse("2022-06-30 16:42:17 +0000")) do - token = client.callback( + id_token, refresh_token = client.callback( code: 'qdDm7On1dEEzNmMlk2bF7IcOF8gCgfvgMCMXXXDlYEE' ) - expect(token).to be_a_kind_of(OpenIDConnect::ResponseObject::IdToken) - expect(token.raw_attributes['nonce']).to eq('1656b4264b60af659fce') - expect(token.raw_attributes['preferred_username']).to eq('test.user3@mycompany.com') - expect(token.aud).to eq('0oa3w3xig6rHiu9yT5d7') + + expect(id_token).to be_a_kind_of(OpenIDConnect::ResponseObject::IdToken) + expect(id_token.raw_attributes['nonce']).to eq('1656b4264b60af659fce') + expect(id_token.raw_attributes['preferred_username']).to eq('test.user3@mycompany.com') + expect(id_token.aud).to eq('0oa3w3xig6rHiu9yT5d7') + + expect(refresh_token).to be_nil end end end @@ -97,6 +106,36 @@ end end end + + context 'when refresh token flow is enabled' do + # The 'offline_access' scope enables Okta's refresh token flow + let(:authenticator) do + Authentication::AuthnOidc::V2::DataObjects::Authenticator.new( + **authn_config.merge!({ :provider_scope => 'offline_access' }) + ) + end + + context 'when credentials are valid' do + it 'returns valid ID and refresh tokens', vcr: 'authenticators/authn-oidc/v2/client_callback-valid_oidc_credentials_and_refresh' do + # Because JWT tokens have an expiration timeframe, we need to hold + # time constant after caching the request. + travel_to(Time.parse("2022-06-30 16:42:17 +0000")) do + id_token, refresh_token = client.callback( + code: 'qdDm7On1dEEzNmMlk2bF7IcOF8gCgfvgMCMXXXDlYEE' + ) + + expect(id_token).to be_a_kind_of(OpenIDConnect::ResponseObject::IdToken) + expect(id_token.raw_attributes['nonce']).to eq('1656b4264b60af659fce') + expect(id_token.raw_attributes['preferred_username']).to eq('test.user3@mycompany.com') + expect(id_token.aud).to eq('0oa3w3xig6rHiu9yT5d7') + + expect(refresh_token).not_to be_nil + expect(refresh_token).to be_a_kind_of(String) + expect(refresh_token).to eq('kXMJFtgtaEpOGn0Zk2x15i8umXIWp4aqY1Mh7zscfGI') + end + end + end + end end describe '.oidc_client' do diff --git a/spec/app/domain/authentication/authn-oidc/v2/strategy_rspec.rb b/spec/app/domain/authentication/authn-oidc/v2/strategy_rspec.rb index 7ce0cb9d48..76da43c478 100644 --- a/spec/app/domain/authentication/authn-oidc/v2/strategy_rspec.rb +++ b/spec/app/domain/authentication/authn-oidc/v2/strategy_rspec.rb @@ -5,6 +5,7 @@ RSpec.describe(' Authentication::AuthnOidc::V2::Strategy') do let(:jwt) { double(raw_attributes: { claim_mapping: "alice" }) } + let(:refresh_token) { 'kXMJFtgtaEpOGn0Zk2x15i8umXIWp4aqY1Mh7zscfGI' } let(:authenticator) do Authentication::AuthnOidc::V2::DataObjects::Authenticator.new( @@ -30,7 +31,7 @@ let(:current_client) do instance_double(::Authentication::AuthnOidc::V2::Client).tap do |double| - allow(double).to receive(:callback).and_return(jwt) + allow(double).to receive(:callback).and_return([jwt, nil]) end end @@ -42,11 +43,13 @@ end describe('#callback') do - context 'when a role_id matches the identity exist' do + context 'When a role_id matches the identity exist' do let(:mapping) { "claim_mapping" } - it 'returns the role' do - expect(strategy.callback({ state: "foostate", code: "code" })) - .to eq("alice") + it 'returns only the role' do + role, token = strategy.callback({ state: "foostate", code: "code" }) + + expect(role).to eq("alice") + expect(token).to be_nil end end @@ -65,5 +68,23 @@ .to raise_error(Errors::Authentication::AuthnOidc::IdTokenClaimNotFoundOrEmpty) end end + + context 'When refresh token flow is enabled' do + let(:current_client) do + instance_double(::Authentication::AuthnOidc::V2::Client).tap do |double| + allow(double).to receive(:callback).and_return([jwt, refresh_token]) + end + end + + context 'when a role_id matches the identity exist' do + let(:mapping) { "claim_mapping" } + it 'returns the role and its refresh token' do + role, token = strategy.callback({ state: "foostate", code: "code" }) + + expect(role).to eq("alice") + expect(token).to eq(refresh_token) + end + end + end end end diff --git a/spec/fixtures/vcr_cassettes/authenticators/authn-oidc/v2/client_callback-valid_oidc_credentials_and_refresh.yml b/spec/fixtures/vcr_cassettes/authenticators/authn-oidc/v2/client_callback-valid_oidc_credentials_and_refresh.yml new file mode 100644 index 0000000000..6cf12a639d --- /dev/null +++ b/spec/fixtures/vcr_cassettes/authenticators/authn-oidc/v2/client_callback-valid_oidc_credentials_and_refresh.yml @@ -0,0 +1,218 @@ +--- +http_interactions: +- request: + method: get + uri: https://dev-92899796.okta.com/oauth2/default/.well-known/openid-configuration + body: + encoding: UTF-8 + string: '' + headers: + User-Agent: + - SWD (1.3.0) (2.8.3, ruby 3.0.4 (2022-04-12)) + Accept: + - "*/*" + Date: + - Thu, 30 Jun 2022 16:43:02 GMT + response: + status: + code: 200 + message: OK + headers: + Date: + - Thu, 30 Jun 2022 16:43:02 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Public-Key-Pins-Report-Only: + - pin-sha256="r5EfzZxQVvQpKo3AgYRaT7X2bDO/kj3ACwmxfdT2zt8="; pin-sha256="MaqlcUgk2mvY/RFSGeSwBRkI+rZ6/dxe/DuQfBT/vnQ="; + pin-sha256="72G5IEvDEWn+EThf3qjR7/bQSWaS2ZSLqolhnO6iyJI="; pin-sha256="rrV6CLCCvqnk89gWibYT0JO6fNQ8cCit7GGoiVTjCOg="; + max-age=60; report-uri="https://okta.report-uri.com/r/default/hpkp/reportOnly" + X-Xss-Protection: + - '0' + P3p: + - CP="HONK" + Content-Security-Policy: + - 'default-src ''self'' dev-92899796.okta.com *.oktacdn.com; connect-src ''self'' + dev-92899796.okta.com dev-92899796-admin.okta.com *.oktacdn.com *.mixpanel.com + *.mapbox.com app.pendo.io data.pendo.io pendo-static-5634101834153984.storage.googleapis.com + https://oinmanager.okta.com data:; script-src ''unsafe-inline'' ''unsafe-eval'' + ''self'' dev-92899796.okta.com *.oktacdn.com; style-src ''unsafe-inline'' + ''self'' dev-92899796.okta.com *.oktacdn.com app.pendo.io cdn.pendo.io pendo-static-5634101834153984.storage.googleapis.com; + frame-src ''self'' dev-92899796.okta.com dev-92899796-admin.okta.com login.okta.com; + img-src ''self'' dev-92899796.okta.com *.oktacdn.com *.tiles.mapbox.com *.mapbox.com + app.pendo.io data.pendo.io cdn.pendo.io pendo-static-5634101834153984.storage.googleapis.com + data: blob:; font-src ''self'' dev-92899796.okta.com data: *.oktacdn.com fonts.gstatic.com; + frame-ancestors ''self''' + Expect-Ct: + - report-uri="https://oktaexpectct.report-uri.com/r/t/ct/reportOnly", max-age=0 + Cache-Control: + - max-age=86400, must-revalidate + Expires: + - Tue, 12 Jul 2022 16:31:08 GMT + Vary: + - Origin + X-Content-Type-Options: + - nosniff + Strict-Transport-Security: + - max-age=315360000; includeSubDomains + X-Okta-Request-Id: + - YsxQTEzo3y4Lo9fSGY_LawAABPE + body: + encoding: UTF-8 + string: '{"issuer":"https://dev-92899796.okta.com/oauth2/default","authorization_endpoint":"https://dev-92899796.okta.com/oauth2/default/v1/authorize","token_endpoint":"https://dev-92899796.okta.com/oauth2/default/v1/token","userinfo_endpoint":"https://dev-92899796.okta.com/oauth2/default/v1/userinfo","registration_endpoint":"https://dev-92899796.okta.com/oauth2/v1/clients","jwks_uri":"https://dev-92899796.okta.com/oauth2/default/v1/keys","response_types_supported":["code","id_token","code + id_token","code token","id_token token","code id_token token"],"response_modes_supported":["query","fragment","form_post","okta_post_message"],"grant_types_supported":["authorization_code","implicit","refresh_token","password","urn:ietf:params:oauth:grant-type:device_code"],"subject_types_supported":["public"],"id_token_signing_alg_values_supported":["RS256"],"scopes_supported":["openid","profile","email","address","phone","offline_access","device_sso"],"token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","client_secret_jwt","private_key_jwt","none"],"claims_supported":["iss","ver","sub","aud","iat","exp","jti","auth_time","amr","idp","nonce","name","nickname","preferred_username","given_name","middle_name","family_name","email","email_verified","profile","zoneinfo","locale","address","phone_number","picture","website","gender","birthdate","updated_at","at_hash","c_hash"],"code_challenge_methods_supported":["S256"],"introspection_endpoint":"https://dev-92899796.okta.com/oauth2/default/v1/introspect","introspection_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","client_secret_jwt","private_key_jwt","none"],"revocation_endpoint":"https://dev-92899796.okta.com/oauth2/default/v1/revoke","revocation_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","client_secret_jwt","private_key_jwt","none"],"end_session_endpoint":"https://dev-92899796.okta.com/oauth2/default/v1/logout","request_parameter_supported":true,"request_object_signing_alg_values_supported":["HS256","HS384","HS512","RS256","RS384","RS512","ES256","ES384","ES512"],"device_authorization_endpoint":"https://dev-92899796.okta.com/oauth2/default/v1/device/authorize"}' + recorded_at: Thu, 30 Jun 2022 17:07:38 GMT +- request: + method: post + uri: https://dev-92899796.okta.com/oauth2/default/v1/token + body: + encoding: UTF-8 + string: grant_type=authorization_code&code=7wKEGhsN9UEL5MG9EfDJ8KWMToKINzvV29uyPsQZYpo&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauthn-oidc%2Fokta-2%2Fcucumber%2Fauthenticate&scope=true&nonce=1656b4264b60af659fce + headers: + User-Agent: + - Rack::OAuth2 (1.19.0) (2.8.3, ruby 3.0.4 (2022-04-12)) + Accept: + - "*/*" + Date: + - Thu, 30 Jun 2022 16:43:02 GMT + Authorization: + - Basic MG9hM3czeGlnNnJIaXU5eVQ1ZDc6ZTM0OUJNVFRJcExPLXJQdVBxTExrTHlIX3BPLWxvVXpoSVZKQ3JIag== + Content-Type: + - application/x-www-form-urlencoded + response: + status: + code: 200 + message: OK + headers: + Date: + - Thu, 30 Jun 2022 16:43:03 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Public-Key-Pins-Report-Only: + - pin-sha256="r5EfzZxQVvQpKo3AgYRaT7X2bDO/kj3ACwmxfdT2zt8="; pin-sha256="MaqlcUgk2mvY/RFSGeSwBRkI+rZ6/dxe/DuQfBT/vnQ="; + pin-sha256="72G5IEvDEWn+EThf3qjR7/bQSWaS2ZSLqolhnO6iyJI="; pin-sha256="rrV6CLCCvqnk89gWibYT0JO6fNQ8cCit7GGoiVTjCOg="; + max-age=60; report-uri="https://okta.report-uri.com/r/default/hpkp/reportOnly" + X-Okta-Request-Id: + - Yr3Slh4a2cZq__AflSs8wQAADtA + X-Xss-Protection: + - '0' + P3p: + - CP="HONK" + Content-Security-Policy: + - 'default-src ''self'' dev-92899796.okta.com *.oktacdn.com; connect-src ''self'' + dev-92899796.okta.com dev-92899796-admin.okta.com *.oktacdn.com *.mixpanel.com + *.mapbox.com app.pendo.io data.pendo.io pendo-static-5634101834153984.storage.googleapis.com + https://oinmanager.okta.com data:; script-src ''unsafe-inline'' ''unsafe-eval'' + ''self'' dev-92899796.okta.com *.oktacdn.com; style-src ''unsafe-inline'' + ''self'' dev-92899796.okta.com *.oktacdn.com app.pendo.io cdn.pendo.io pendo-static-5634101834153984.storage.googleapis.com; + frame-src ''self'' dev-92899796.okta.com dev-92899796-admin.okta.com login.okta.com; + img-src ''self'' dev-92899796.okta.com *.oktacdn.com *.tiles.mapbox.com *.mapbox.com + app.pendo.io data.pendo.io cdn.pendo.io pendo-static-5634101834153984.storage.googleapis.com + data: blob:; font-src ''self'' dev-92899796.okta.com data: *.oktacdn.com fonts.gstatic.com; + frame-ancestors ''self''' + X-Rate-Limit-Limit: + - '300' + X-Rate-Limit-Remaining: + - '299' + X-Rate-Limit-Reset: + - '1656607442' + Cache-Control: + - no-cache, no-store + Pragma: + - no-cache + Expires: + - '0' + Expect-Ct: + - report-uri="https://oktaexpectct.report-uri.com/r/t/ct/reportOnly", max-age=0 + X-Content-Type-Options: + - nosniff + Strict-Transport-Security: + - max-age=315360000; includeSubDomains + X-Robots-Tag: + - noindex,nofollow + Set-Cookie: + - JSESSIONID=7D061355C0A559E9DE2767B0389890C8; Path=/; Secure; HttpOnly + - autolaunch_triggered=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/ + - sid=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/ + body: + encoding: UTF-8 + string: '{"token_type":"Bearer","expires_in":3600,"access_token":"eyJraWQiOiJZR1NUUUxBVDdLb1JPd2RhTWtWa1RyNXhIUXM3Zm1jNG5CTUJsT1NuZHVzIiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULnl1SkFCZmtqT2FJNHVsOVczMzFabFp0Rk5UQ0ZhSTA3aVJHZFNxcmtJTEEiLCJpc3MiOiJodHRwczovL2Rldi05Mjg5OTc5Ni5va3RhLmNvbS9vYXV0aDIvZGVmYXVsdCIsImF1ZCI6ImFwaTovL2RlZmF1bHQiLCJpYXQiOjE2NTY2MDczODIsImV4cCI6MTY1NjYxMDk4MiwiY2lkIjoiMG9hM3czeGlnNnJIaXU5eVQ1ZDciLCJ1aWQiOiIwMHU1Z2tvNmRmNDRqMmZHcDVkNyIsInNjcCI6WyJwcm9maWxlIiwib3BlbmlkIl0sImF1dGhfdGltZSI6MTY1NjYwNjcxNywic3ViIjoidGVzdC51c2VyM0BteWNvbXBhbnkuY29tIn0.w3mFIqKKJII8zWwr5pNFaLIF4R2JslczkUNBHetQfbqrJbw8QDTJi8wkrzVuYF6kmjEapUwyhfip49P7NLvmA88HO7yzqJ9Nu5ZeSftkeR29ldEUgDXN_wdmzer_AvqzNwZbb8W_BqrfUpXXDIGroIKhCjgDMAKW1JlQf85yM-oRuy1qSUIX983US2mixkQzvs0W2sd3Fc0ehTh8ZONjjQMvlbbzNgjOpixz-2gSi61KpQEbw4_jRzYZKavv6El_2lhv5i4AlwP0kXMs6b6W7n3y3bDEFagNj2H5GxLjvt0CUp-NvVX_aWCsJiVoFtu9ahgRbPe3J1PRipnKuQ3rIg","refresh_token":"kXMJFtgtaEpOGn0Zk2x15i8umXIWp4aqY1Mh7zscfGI","scope":"profile openid offline_access","id_token":"eyJraWQiOiJZR1NUUUxBVDdLb1JPd2RhTWtWa1RyNXhIUXM3Zm1jNG5CTUJsT1NuZHVzIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHU1Z2tvNmRmNDRqMmZHcDVkNyIsIm5hbWUiOiJUZXN0IFVzZXIzIiwidmVyIjoxLCJpc3MiOiJodHRwczovL2Rldi05Mjg5OTc5Ni5va3RhLmNvbS9vYXV0aDIvZGVmYXVsdCIsImF1ZCI6IjBvYTN3M3hpZzZySGl1OXlUNWQ3IiwiaWF0IjoxNjU2NjA3MzgyLCJleHAiOjE2NTY2MTA5ODIsImp0aSI6IklELjdubmF3YUdkc1daT2VKazl6YWxNUFpnVGhuX3Z2QkNDcndBazVHcHRjS00iLCJhbXIiOlsicHdkIl0sImlkcCI6IjAwbzN3MXk4dHV1YVljQ1Q3NWQ3Iiwibm9uY2UiOiIxNjU2YjQyNjRiNjBhZjY1OWZjZSIsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3QudXNlcjNAbXljb21wYW55LmNvbSIsImF1dGhfdGltZSI6MTY1NjYwNjcxNywiYXRfaGFzaCI6IkU3TDJUUEZrM0dGMXlRQzdEaUJ1UkEifQ.YDeYm3bP5hFmP4u6uuKV2fU8ICZ72LIa_tlG0qfYCVcHS1lZeHqbyJPEWfgmnSGAxenieavntCbsW-g6UdtCeGsoXGPw3tDW-oiNyZsdBPw-xTCg01JSd4d26Oponia0amkhvglXRkAuGVRJciO89oTVabxvYlcP-PvOeaiFjn4q9hFvTQTI6sItPhxp6rMa3Ri9VJkOR1fdkI-w9bwGW8WN-u4GscQoCU054HPVaHPT8fQ86Bl3Aty8Bf2e5Gw6WIpLSFgWd6Nmhiv1ANUcW8vSLxsefWI6N37j-0fCa1fgZefv-M-Kg_dfE-8a33YxzAwN5NB3HCbv7FNsYD1rIg"}' + recorded_at: Thu, 30 Jun 2022 16:43:02 GMT +- request: + method: get + uri: https://dev-92899796.okta.com/oauth2/default/v1/keys + body: + encoding: UTF-8 + string: '' + headers: + User-Agent: + - OpenIDConnect (1.3.0) (2.8.3, ruby 3.0.4 (2022-04-12)) + Accept: + - "*/*" + Date: + - Thu, 30 Jun 2022 16:43:02 GMT + response: + status: + code: 200 + message: OK + headers: + Date: + - Thu, 30 Jun 2022 16:43:03 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - nginx + Public-Key-Pins-Report-Only: + - pin-sha256="r5EfzZxQVvQpKo3AgYRaT7X2bDO/kj3ACwmxfdT2zt8="; pin-sha256="MaqlcUgk2mvY/RFSGeSwBRkI+rZ6/dxe/DuQfBT/vnQ="; + pin-sha256="72G5IEvDEWn+EThf3qjR7/bQSWaS2ZSLqolhnO6iyJI="; pin-sha256="rrV6CLCCvqnk89gWibYT0JO6fNQ8cCit7GGoiVTjCOg="; + max-age=60; report-uri="https://okta.report-uri.com/r/default/hpkp/reportOnly" + X-Xss-Protection: + - '0' + P3p: + - CP="HONK" + Content-Security-Policy: + - 'default-src ''self'' dev-92899796.okta.com *.oktacdn.com; connect-src ''self'' + dev-92899796.okta.com dev-92899796-admin.okta.com *.oktacdn.com *.mixpanel.com + *.mapbox.com app.pendo.io data.pendo.io pendo-static-5634101834153984.storage.googleapis.com + https://oinmanager.okta.com data:; script-src ''unsafe-inline'' ''unsafe-eval'' + ''self'' dev-92899796.okta.com *.oktacdn.com; style-src ''unsafe-inline'' + ''self'' dev-92899796.okta.com *.oktacdn.com app.pendo.io cdn.pendo.io pendo-static-5634101834153984.storage.googleapis.com; + frame-src ''self'' dev-92899796.okta.com dev-92899796-admin.okta.com login.okta.com; + img-src ''self'' dev-92899796.okta.com *.oktacdn.com *.tiles.mapbox.com *.mapbox.com + app.pendo.io data.pendo.io cdn.pendo.io pendo-static-5634101834153984.storage.googleapis.com + data: blob:; font-src ''self'' dev-92899796.okta.com data: *.oktacdn.com fonts.gstatic.com; + frame-ancestors ''self''' + Expect-Ct: + - report-uri="https://oktaexpectct.report-uri.com/r/t/ct/reportOnly", max-age=0 + Cache-Control: + - max-age=128312, must-revalidate + Expires: + - Sat, 02 Jul 2022 04:21:35 GMT + Vary: + - Origin + X-Content-Type-Options: + - nosniff + Strict-Transport-Security: + - max-age=315360000; includeSubDomains + X-Okta-Request-Id: + - Yr3SlwZrGYc1D5ItYgm_cgAABxk + body: + encoding: UTF-8 + string: '{"keys":[{"kty":"RSA","alg":"RS256","kid":"YGSTQLAT7KoROwdaMkVkTr5xHQs7fmc4nBMBlOSndus","use":"sig","e":"AQAB","n":"y4r4NkyseHCyJ44QE4n4_n4Xxi63nkZlcTuJkr8I-yOgXKWk9h9giq6FJcMYwHXTtM2sQQ-yVJu_GSCgWWq2QmgaQt-1H8ldrJhlw1_W33POdCPQHjOdvUGJ8xgKbasIuFJrR_SPXJ5WQz8yIQsTQXaIgSP2NHTLRRg_KFsMe9nkYY7Mz_T9ZsQyzxal9fnefJa5mExEuWi7STIEJfgQqbzezdN-t1ZFpoVCAUFhNwKNYcsTvMDR43e3zdY_ii09rWakRzEicoFGeeQPZcgxcHWnYAmirufmu_gj9lGE-sOFatcXkIK07gziDa8CzTqh6OKitQdT__CNKSiOgYT1Pw"}]}' + recorded_at: Thu, 30 Jun 2022 16:43:03 GMT +recorded_with: VCR 6.1.0