Class: LogtoClient
- Inherits:
-
Object
- Object
- LogtoClient
- Defined in:
- lib/logto/client/index.rb,
lib/logto/client/index_types.rb,
lib/logto/client/index_storage.rb,
lib/logto/client/index_constants.rb
Overview
The main client class for the Logto client.
It provides the main functionalities for the client to interact with the Logto server.
Defined Under Namespace
Classes: AbstractStorage, Config, RailsCacheStorage, SessionStorage, SignInSession
Constant Summary collapse
- STORAGE_KEY =
{ id_token: "id_token", refresh_token: "refresh_token", access_token_map: "access_token_map", sign_in_session: "sign_in_session" }
Instance Attribute Summary collapse
-
#config ⇒ LogtoClient::Config
readonly
The configuration object for the Logto client.
Instance Method Summary collapse
-
#access_token(resource: nil, organization_id: nil) ⇒ String?
Get the access token for the specified resource and organization ID.
-
#access_token_claims(resource: nil, organization_id: nil) ⇒ LogtoCore::AccessTokenClaims?
Get the access token claims for the specified resource and organization ID.
-
#clear_all_tokens ⇒ Object
Clear all the tokens from the storage.
-
#fetch_user_info ⇒ LogtoCore::UserInfoResponse
Fetch the user information from the OpenID Connect UserInfo endpoint.
-
#handle_sign_in_callback(url:) ⇒ String?
Handle the sign-in callback from the redirect URI.
-
#id_token ⇒ String?
Get the raw ID token from the storage.
-
#id_token_claims ⇒ LogtoCore::IdTokenClaims?
Get the ID token claims from the storage.
-
#initialize(config:, navigate:, storage:, cache: RailsCacheStorage.new(app_id: config.app_id)) ⇒ LogtoClient
constructor
A new instance of LogtoClient.
-
#is_authenticated? ⇒ Boolean
Check if the client is authenticated by checking if the ID token is present.
-
#refresh_token ⇒ String?
Get the raw refresh token from the storage.
-
#sign_in(redirect_uri:, first_screen: nil, identifiers: nil, login_hint: nil, direct_sign_in: nil, post_redirect_uri: nil, extra_params: nil) ⇒ Object
Triggers the sign-in experience.
-
#sign_out(post_logout_redirect_uri: nil) ⇒ Object
Start the sign-out flow with the specified redirect URI.
-
#verify_jwt(token:) ⇒ Object
Verify the JWT token with the configured client ID and the OIDC issuer.
Constructor Details
#initialize(config:, navigate:, storage:, cache: RailsCacheStorage.new(app_id: config.app_id)) ⇒ LogtoClient
Returns a new instance of LogtoClient.
26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/logto/client/index.rb', line 26 def initialize(config:, navigate:, storage:, cache: RailsCacheStorage.new(app_id: config.app_id)) raise ArgumentError, "Config must be a LogtoClient::Config" unless config.is_a?(LogtoClient::Config) raise ArgumentError, "Navigate must be a Proc" unless navigate.is_a?(Proc) raise ArgumentError, "Storage must be a LogtoClient::AbstractStorage" unless storage.is_a?(LogtoClient::AbstractStorage) @config = config @navigate = navigate @storage = storage @cache = cache @core = LogtoCore.new(endpoint: @config.endpoint, cache: cache) # A local access token map cache @access_token_map = @storage.get(STORAGE_KEY[:access_token_map]) || {} end |
Instance Attribute Details
#config ⇒ LogtoClient::Config (readonly)
The configuration object for the Logto client.
12 13 14 |
# File 'lib/logto/client/index.rb', line 12 def config @config end |
Instance Method Details
#access_token(resource: nil, organization_id: nil) ⇒ String?
Get the access token for the specified resource and organization ID. If both are nil, it will return the opaque access token for the OpenID Connect UserInfo endpoint.
If the access token is not found or expired, it will try to use the refresh token to fetch a new access token, if possible.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/logto/client/index.rb', line 184 def access_token(resource: nil, organization_id: nil) raise LogtoError::NotAuthenticatedError, "Not authenticated" unless is_authenticated? key = LogtoUtils.build_access_token_key(resource: resource, organization_id: organization_id) token = @access_token_map[key] # Give it some leeway if token&.[]("expires_at")&.> Time.now + 10 return token["token"] end @access_token_map.delete(key) return nil unless refresh_token # Try to use refresh token to fetch a new access token token_response = @core.fetch_token_by_refresh_token( client_id: @config.app_id, client_secret: @config.app_secret, refresh_token: refresh_token, resource: resource, organization_id: organization_id ) handle_token_response(token_response, resource: resource, organization_id: organization_id) token_response[:access_token] end |
#access_token_claims(resource: nil, organization_id: nil) ⇒ LogtoCore::AccessTokenClaims?
Get the access token claims for the specified resource and organization ID. If both are nil, an ArgumentError will be raised.
215 216 217 218 219 220 221 222 223 |
# File 'lib/logto/client/index.rb', line 215 def access_token_claims(resource: nil, organization_id: nil) raise ArgumentError, "Resource and organization ID cannot be nil at the same time" if resource.nil? && organization_id.nil? return nil unless (token = access_token(resource: resource, organization_id: organization_id)) LogtoUtils.parse_json_safe( JWT.decode(token, nil, false).first, LogtoCore::AccessTokenClaims ) end |
#clear_all_tokens ⇒ Object
Clear all the tokens from the storage.
It will also clear the access token map cache.
249 250 251 252 253 254 |
# File 'lib/logto/client/index.rb', line 249 def clear_all_tokens @access_token_map = {} @storage.remove(STORAGE_KEY[:access_token_map]) @storage.remove(STORAGE_KEY[:id_token]) @storage.remove(STORAGE_KEY[:refresh_token]) end |
#fetch_user_info ⇒ LogtoCore::UserInfoResponse
Fetch the user information from the OpenID Connect UserInfo endpoint.
228 229 230 |
# File 'lib/logto/client/index.rb', line 228 def fetch_user_info @core.fetch_user_info(access_token: access_token) end |
#handle_sign_in_callback(url:) ⇒ String?
Handle the sign-in callback from the redirect URI.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/logto/client/index.rb', line 106 def handle_sign_in_callback(url:) query_params = URI.decode_www_form(URI(url).query).to_h data = @storage.get(STORAGE_KEY[:sign_in_session]) raise LogtoError::SessionNotFoundError, "No sign-in session found" unless data error = query_params[LogtoCore::QUERY_KEY[:error]] error_description = query_params[LogtoCore::QUERY_KEY[:error_description]] raise LogtoError::ServerCallbackError, "Error: #{error}, Description: #{error_description}" if error current_session = data.is_a?(SignInSession) ? data : SignInSession.new(**data) # A loose URI check here raise LogtoError::SessionMismatchError, "Redirect URI mismatch" unless url.start_with?(current_session.redirect_uri) raise LogtoError::SessionMismatchError, "No state found in query parameters" unless query_params[LogtoCore::QUERY_KEY[:state]] raise LogtoError::SessionMismatchError, "Session state mismatch" unless current_session.state == query_params[LogtoCore::QUERY_KEY[:state]] raise LogtoError::SessionMismatchError, "No code found in query parameters" unless query_params[LogtoCore::QUERY_KEY[:code]] token_response = @core.( client_id: @config.app_id, client_secret: @config.app_secret, redirect_uri: current_session.redirect_uri, code_verifier: current_session.code_verifier, code: query_params[LogtoCore::QUERY_KEY[:code]] ) verify_jwt(token: token_response[:id_token]) handle_token_response(token_response, resource: nil) clear_sign_in_session @navigate.call(current_session.post_redirect_uri) current_session.post_redirect_uri end |
#id_token ⇒ String?
Get the raw ID token from the storage.
162 163 164 |
# File 'lib/logto/client/index.rb', line 162 def id_token @storage.get(STORAGE_KEY[:id_token]) end |
#id_token_claims ⇒ LogtoCore::IdTokenClaims?
Get the ID token claims from the storage. It will return nil if the ID token is not found.
170 171 172 173 |
# File 'lib/logto/client/index.rb', line 170 def id_token_claims return nil unless (token = id_token) LogtoUtils.parse_json_safe(JWT.decode(token, nil, false).first, LogtoCore::IdTokenClaims) end |
#is_authenticated? ⇒ Boolean
Check if the client is authenticated by checking if the ID token is present.
242 243 244 |
# File 'lib/logto/client/index.rb', line 242 def is_authenticated? id_token ? true : false end |
#refresh_token ⇒ String?
Get the raw refresh token from the storage.
235 236 237 |
# File 'lib/logto/client/index.rb', line 235 def refresh_token @storage.get(STORAGE_KEY[:refresh_token]) end |
#sign_in(redirect_uri:, first_screen: nil, identifiers: nil, login_hint: nil, direct_sign_in: nil, post_redirect_uri: nil, extra_params: nil) ⇒ Object
Triggers the sign-in experience.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/logto/client/index.rb', line 48 def sign_in(redirect_uri:, first_screen: nil, identifiers: nil, login_hint: nil, direct_sign_in: nil, post_redirect_uri: nil, extra_params: nil) code_verifier = LogtoUtils.generate_code_verifier code_challenge = LogtoUtils.generate_code_challenge(code_verifier) state = LogtoUtils.generate_state sign_in_uri = @core.generate_sign_in_uri( client_id: @config.app_id, redirect_uri: redirect_uri, code_challenge: code_challenge, state: state, scopes: @config.scopes, resources: @config.resources, prompt: @config.prompt, first_screen: first_screen, identifiers: identifiers, login_hint: login_hint, direct_sign_in: direct_sign_in, extra_params: extra_params ) save_sign_in_session(SignInSession.new( redirect_uri: redirect_uri, code_verifier: code_verifier, state: state, post_redirect_uri: post_redirect_uri )) clear_all_tokens @navigate.call(sign_in_uri) end |
#sign_out(post_logout_redirect_uri: nil) ⇒ Object
Start the sign-out flow with the specified redirect URI. The URI must be registered in the Logto Console.
It will also revoke all the tokens and clean up the storage.
The user will be redirected to that URI after the sign-out flow is completed. If the ‘post_logout_redirect_uri` is not specified, the user will be redirected to a default page.
89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/logto/client/index.rb', line 89 def sign_out(post_logout_redirect_uri: nil) if refresh_token @core.revoke_token(client_id: @config.app_id, client_secret: @config.app_secret, token: refresh_token) end uri = @core.generate_sign_out_uri( client_id: @config.app_id, post_logout_redirect_uri: post_logout_redirect_uri ) clear_all_tokens @navigate.call(uri) end |
#verify_jwt(token:) ⇒ Object
Verify the JWT token with the configured client ID and the OIDC issuer.
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/logto/client/index.rb', line 141 def verify_jwt(token:) raise ArgumentError, "Token must be a string" unless token.is_a?(String) JWT.decode( token, nil, true, # List our current and future possibilities. It could use the `alg` header from the token, # but it will be tricky to handle the case of caching. algorithms: ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "ES256K"], jwks: fetch_jwks, iss: @core.oidc_config[:issuer], verify_iss: true, aud: @config.app_id, verify_aud: true ) end |