Fix minor typos in the 'Securing Applications and Services' guide (#1704)
This commit is contained in:
parent
f4827c5f26
commit
8f6aee0ca8
20 changed files with 39 additions and 38 deletions
|
@ -28,7 +28,7 @@ When enforcing the requirements of the FAPI CIBA specification, there is a need
|
|||
==== Open Banking Brasil Financial-grade API Security Profile
|
||||
|
||||
{project_name} is compliant with the https://openbanking-brasil.github.io/specs-seguranca/open-banking-brasil-dynamic-client-registration-1_ID2-ptbr.html[Open Banking Brasil Financial-grade API Security Profile 1.0 Implementers Draft 2].
|
||||
This one is more strict in some requirements than the <<_fapi-support,FAPI 1 Advanced>> specification and hence it may be needed to configure link:{adminguide_link}#_client_policies[Client Policies]
|
||||
This one is stricter in some requirements than the <<_fapi-support,FAPI 1 Advanced>> specification and hence it may be needed to configure link:{adminguide_link}#_client_policies[Client Policies]
|
||||
in the more strict way to enforce some of the requirements. Especially:
|
||||
|
||||
* If your client does not use PAR, make sure that it uses encrypted OIDC request objects. This can be achieved by using a client profile with the `secure-request-object` executor configured with `Encryption Required` enabled.
|
||||
|
@ -38,5 +38,5 @@ in the more strict way to enforce some of the requirements. Especially:
|
|||
|
||||
As confidential information is being exchanged, all interactions shall be encrypted with TLS (HTTPS). Moreover, there are some requirements in the FAPI specification for
|
||||
the cipher suites and TLS protocol versions used. To match these requirements, you can consider configure allowed ciphers. This configuration can be done by setting
|
||||
the `https-protocols` and `https-cipher-suites` options. {project_name} uses `TLSv1.3` by default and hence it is posibly not needed to change the default settings. However it
|
||||
may be needed to adjust ciphers if you need to fallback to lower TLS version for some reason. For more details, see https://www.keycloak.org/server/enabletls[Configuring TLS] guide.
|
||||
the `https-protocols` and `https-cipher-suites` options. {project_name} uses `TLSv1.3` by default and hence it is possibly not needed to change the default settings. However it
|
||||
may be needed to adjust ciphers if you need to fall back to lower TLS version for some reason. For more details, see https://www.keycloak.org/server/enabletls[Configuring TLS] guide.
|
||||
|
|
|
@ -16,7 +16,7 @@ You can set up an error-page within your `web.xml` file to handle the error howe
|
|||
----
|
||||
|
||||
{project_name} also sets a `HttpServletRequest` attribute that you can retrieve.
|
||||
The attribute name is `org.keycloak.adapters.spi.AuthenticationError`, which should be casted to `org.keycloak.adapters.OIDCAuthenticationError`.
|
||||
The attribute name is `org.keycloak.adapters.spi.AuthenticationError`, which should be cast to `org.keycloak.adapters.OIDCAuthenticationError`.
|
||||
|
||||
For example:
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ If admin URL contains `${application.session.host}` it will be replaced with the
|
|||
|
||||
The previous section describes how {project_name} can send logout request to node associated with a specific HTTP session.
|
||||
However, in some cases admin may want to propagate admin tasks to all registered cluster nodes, not just one of them.
|
||||
For example to push a new not before policy to the application or to logout all users from the application.
|
||||
For example to push a new not before policy to the application or to log out all users from the application.
|
||||
|
||||
In this case {project_name} needs to be aware of all application cluster nodes, so it can send the event to all of them.
|
||||
To achieve this, we support auto-discovery mechanism:
|
||||
|
@ -97,7 +97,7 @@ This means the adapter will send the registration request on startup and re-regi
|
|||
|
||||
In the {project_name} Admin Console you can specify the maximum node re-registration timeout (should be larger than _register-node-period_ from
|
||||
the adapter configuration). You can also manually add and remove cluster nodes in through the Admin Console, which is useful if you don't want to rely
|
||||
on the automatic registration feature or if you want to remove stale application nodes in the event your not using the automatic unregistration feature.
|
||||
on the automatic registration feature or if you want to remove stale application nodes in the event you're not using the automatic unregistration feature.
|
||||
|
||||
[[_refresh_token_each_req]]
|
||||
===== Refresh token in each request
|
||||
|
|
|
@ -14,13 +14,13 @@ reads the user credentials from `STDIN`.
|
|||
|
||||
To authenticate a user with the `desktop` variant the `KeycloakInstalled`
|
||||
adapter opens a desktop browser window where a user uses the regular {project_name}
|
||||
login pages to login when the `loginDesktop()` method is called on the `KeycloakInstalled` object.
|
||||
login pages to log in when the `loginDesktop()` method is called on the `KeycloakInstalled` object.
|
||||
|
||||
The login page URL is opened with redirect parameter
|
||||
that points to a local `ServerSocket` listening on a free ephemeral port
|
||||
on `localhost` which is started by the adapter.
|
||||
|
||||
After a succesful login the `KeycloakInstalled` receives the authorization code
|
||||
After a successful login the `KeycloakInstalled` receives the authorization code
|
||||
from the incoming HTTP request and performs the authorization code flow.
|
||||
Once the code to token exchange is completed the `ServerSocket` is shutdown.
|
||||
|
||||
|
@ -60,7 +60,7 @@ in the adapter configuration. Enabling PKCE is highly recommended, to avoid code
|
|||
|
||||
===== Usage
|
||||
|
||||
The `KeycloakInstalled` adapter reads it's configuration from
|
||||
The `KeycloakInstalled` adapter reads its configuration from
|
||||
`META-INF/keycloak.json` on the classpath. Custom configurations
|
||||
can be supplied with an `InputStream` or a `KeycloakDeployment`
|
||||
through the `KeycloakInstalled` constructor.
|
||||
|
|
|
@ -65,8 +65,8 @@ resource::
|
|||
realm-public-key::
|
||||
_OPTIONAL_ and it's not recommended to set it. PEM format of the realm public key. You can obtain this from the Admin Console.
|
||||
If not set, the adapter will download this from {project_name} and
|
||||
it will always re-download it when needed (eg. {project_name} rotates its keys). However if realm-public-key is set, then adapter
|
||||
will never download new keys from {project_name}, so when {project_name} rotate it's keys, adapter will break.
|
||||
it will always re-download it when needed (e.g. {project_name} rotates its keys). However if realm-public-key is set, then adapter
|
||||
will never download new keys from {project_name}, so when {project_name} rotate its keys, adapter will break.
|
||||
|
||||
auth-server-url::
|
||||
_REQUIRED._ The base URL of the {project_name} server. All other {project_name} pages and REST service endpoints are derived from this. It is usually of the form `\https://host:port{kc_base_path}`.
|
||||
|
@ -156,7 +156,8 @@ socket-timeout-millis::
|
|||
A timeout value of zero is interpreted as an infinite timeout.
|
||||
A negative value is interpreted as undefined (system default if applicable).
|
||||
The default value is `-1`.
|
||||
onnection-timeout-millis::
|
||||
|
||||
connection-timeout-millis::
|
||||
Timeout for establishing the connection with the remote host in milliseconds.
|
||||
A timeout value of zero is interpreted as an infinite timeout.
|
||||
A negative value is interpreted as undefined (system default if applicable).
|
||||
|
@ -192,7 +193,7 @@ truststore::
|
|||
If you prefix the path with `classpath:`, then the truststore will be obtained from the deployment's classpath instead.
|
||||
Used for outgoing HTTPS communications to the {project_name} server.
|
||||
Client making HTTPS requests need a way to verify the host of the server they are talking to.
|
||||
This is what the trustore does.
|
||||
This is what the truststore does.
|
||||
The keystore contains one or more trusted host certificates or certificate authorities.
|
||||
You can create this truststore by extracting the public certificate of the {project_name} server's SSL keystore.
|
||||
_REQUIRED_ unless `ssl-required` is `none` or `disable-trust-manager` is `true`.
|
||||
|
@ -258,14 +259,14 @@ token-minimum-time-to-live::
|
|||
min-time-between-jwks-requests::
|
||||
Amount of time, in seconds, specifying minimum interval between two requests to {project_name} to retrieve new public keys.
|
||||
It is 10 seconds by default.
|
||||
Adapter will always try to download new public key when it recognize token with unknown `kid` . However it won't try it more
|
||||
Adapter will always try to download new public key when it recognizes token with unknown `kid` . However it won't try it more
|
||||
than once per 10 seconds (by default). This is to avoid DoS when attacker sends lots of tokens with bad `kid` forcing adapter
|
||||
to send lots of requests to {project_name}.
|
||||
|
||||
public-key-cache-ttl::
|
||||
Amount of time, in seconds, specifying maximum interval between two requests to {project_name} to retrieve new public keys.
|
||||
It is 86400 seconds (1 day) by default.
|
||||
Adapter will always try to download new public key when it recognize token with unknown `kid` . If it recognize token with known `kid`, it will
|
||||
Adapter will always try to download new public key when it recognizes token with unknown `kid` . If it recognizes token with known `kid`, it will
|
||||
just use the public key downloaded previously. However at least once per this configured interval (1 day by default) will be new
|
||||
public key always downloaded even if the `kid` of token is already known.
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ ifeval::[{project_product}==true]
|
|||
|
||||
.Procedure
|
||||
|
||||
. Install the adapter that applies to your application server from the link:https://access.redhat.com/jbossnetwork/restricted/listSoftware.html?downloadType=distributions&product=core.service.rhsso[Sotware Downloads] site.
|
||||
. Install the adapter that applies to your application server from the link:https://access.redhat.com/jbossnetwork/restricted/listSoftware.html?downloadType=distributions&product=core.service.rhsso[Software Downloads] site.
|
||||
|
||||
* Install on JBoss EAP 7:
|
||||
+
|
||||
|
|
|
@ -22,7 +22,7 @@ defined on particular client. Note that the scope `openid` will be always be add
|
|||
enter the scope options `address phone`, then the request to {project_name} will contain the scope parameter `scope=openid address phone`.
|
||||
|
||||
* prompt - {project_name} supports these settings:
|
||||
** `login` - SSO will be ignored and the {project_name} login page will be always shown, even if the user is already authenticated
|
||||
** `login` - SSO will be ignored and the {project_name} login page will always be shown, even if the user is already authenticated
|
||||
** `consent` - Applicable only for the clients with `Consent Required`. If it is used, the Consent page will always be displayed,
|
||||
even if the user previously granted consent to this client.
|
||||
** `none` - The login page will never be shown; instead the user will be redirected to the application, with an error if the user
|
||||
|
|
|
@ -221,7 +221,7 @@ Keycloak support hybrid mobile apps developed with https://cordova.apache.org/[A
|
|||
The default is cordova, which the adapter will automatically select if no adapter type has been configured and window.cordova is present.
|
||||
When logging in, it will open an https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-inappbrowser/[InApp Browser] that lets the user interact with {project_name} and afterwards returns to the app by redirecting to `http://localhost`. Because of this, you must whitelist this URL as a valid redirect-uri in the client configuration section of the Admin Console.
|
||||
|
||||
While this mode is easy to setup, it also has some disadvantages:
|
||||
While this mode is easy to set up, it also has some disadvantages:
|
||||
|
||||
* The InApp-Browser is a browser embedded in the app and is not the phone's default browser. Therefore it will have different settings and stored credentials will not be available.
|
||||
* The InApp-Browser might also be slower, especially when rendering more complex themes.
|
||||
|
@ -252,7 +252,7 @@ The technical details for linking to an app differ on each platform and special
|
|||
Please refer to the Android and iOS sections of the https://github.com/e-imaxina/cordova-plugin-deeplinks/blob/master/README.md[deeplinks plugin documentation] for further instructions.
|
||||
|
||||
There are different kinds of links for opening apps: custom schemes (i.e. `myapp://login` or `android-app://com.example.myapp/https/example.com/login`) and https://developer.apple.com/ios/universal-links/[Universal Links (iOS)]) / https://developer.android.com/training/app-links/deep-linking[Deep Links (Android)].
|
||||
While the former are easier to setup and tend to work more reliably, the later offer extra security as they are unique and only the owner of a domain can register them.
|
||||
While the former are easier to set up and tend to work more reliably, the later offer extra security as they are unique and only the owner of a domain can register them.
|
||||
Custom-URLs are deprecated on iOS.
|
||||
We recommend that you use universal links, combined with a fallback site with a custom-url link on it for best reliability.
|
||||
|
||||
|
@ -287,7 +287,7 @@ keycloak.init({
|
|||
|
||||
This specific package does not exist, but it gives a pretty good example of how such an adapter could be passed into the client.
|
||||
|
||||
It's also possible to make your own adapter, to do so you will have to implement the methods described in the `KeycloakAdapter` interface. For example the following TypeScript code ensures that all of the methods are properly implemented:
|
||||
It's also possible to make your own adapter, to do so you will have to implement the methods described in the `KeycloakAdapter` interface. For example the following TypeScript code ensures that all the methods are properly implemented:
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
|
@ -468,7 +468,7 @@ provider instead. More info in the link:{adminguide_link}#_client_suggested_idp[
|
|||
* acr - Contains the information about `acr` claim, which will be sent inside `claims` parameter to the {project_name} server. Typical usage
|
||||
is for step-up authentication. Example of use `{ values: ["silver", "gold"], essential: true }`. See OpenID Connect specification
|
||||
and link:{adminguide_link}#_step-up-flow[Step-up authentication documentation] for more details.
|
||||
* action - If value is `register` then user is redirected to registration page, if the value is `UPDATE_PASSWORD` then the user will redirected to the reset password page (if not authenticated will send user to login page first and redirect after authenticated), otherwise to login page.
|
||||
* action - If value is `register` then user is redirected to registration page, if the value is `UPDATE_PASSWORD` then the user will be redirected to the reset password page (if not authenticated will send user to login page first and redirect after authenticated), otherwise to login page.
|
||||
* locale - Sets the 'ui_locales' query param in compliance with https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest[section 3.1.2.1 of the OIDC 1.0 specification].
|
||||
* cordovaOptions - Specifies the arguments that are passed to the Cordova in-app-browser (if applicable). Options `hidden` and `location` are not affected by these arguments. All available options are defined at https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-inappbrowser/. Example of use: `{ zoom: "no", hardwareback: "yes" }`;
|
||||
|
||||
|
@ -488,7 +488,7 @@ Options is an Object, where:
|
|||
|
||||
*createLogoutUrl(options)*
|
||||
|
||||
Returns the URL to logout the user.
|
||||
Returns the URL to log out the user.
|
||||
|
||||
Options is an Object, where:
|
||||
|
||||
|
@ -596,4 +596,4 @@ The available events are:
|
|||
* *onAuthRefreshSuccess* - Called when the token is refreshed.
|
||||
* *onAuthRefreshError* - Called if there was an error while trying to refresh the token.
|
||||
* *onAuthLogout* - Called if the user is logged out (will only be called if the session status iframe is enabled, or in Cordova mode).
|
||||
* *onTokenExpired* - Called when the access token is expired. If a refresh token is available the token can be refreshed with updateToken, or in cases where it is not (that is, with implicit flow) you can redirect to login screen to obtain a new access token.
|
||||
* *onTokenExpired* - Called when the access token is expired. If a refresh token is available the token can be refreshed with updateToken, or in cases where it is not (that is, with implicit flow) you can redirect to the login screen to obtain a new access token.
|
||||
|
|
|
@ -35,7 +35,7 @@ ServerName ${HOSTIP}
|
|||
OIDCClientSecret ${CLIENT_SECRET}
|
||||
OIDCRedirectURI http://${HOSTIP}/${CLIENT_APP_NAME}/redirect_uri
|
||||
|
||||
# maps the prefered_username claim to the REMOTE_USER environment variable
|
||||
# maps the preferred_username claim to the REMOTE_USER environment variable
|
||||
OIDCRemoteUserClaim preferred_username
|
||||
|
||||
<Location /${CLIENT_APP_NAME}/>
|
||||
|
|
|
@ -94,7 +94,7 @@ Now we are ready to obtain the `keycloak.json` file by visiting the {project_nam
|
|||
|
||||
Paste the downloaded file on the root folder of our project.
|
||||
|
||||
Instantiation with this method results in all of the reasonable defaults
|
||||
Instantiation with this method results in all the reasonable defaults
|
||||
being used. As alternative, it's also possible to provide a configuration
|
||||
object, rather than the `keycloak.json` file:
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ The downside to this approach is that you have to make a network invocation to t
|
|||
server if you have too many validation requests going on at the same time. {project_name} issued access tokens are https://datatracker.ietf.org/doc/html/rfc7519[JSON Web Tokens (JWT)] digitally signed and encoded using https://datatracker.ietf.org/doc/html/rfc7515[JSON Web Signature (JWS)].
|
||||
Because they are encoded in this way, this allows you to locally validate access tokens using the public key of the issuing realm. You can either hard code the
|
||||
realm's public key in your validation code, or lookup and cache the public key using the <<_certificate_endpoint, certificate endpoint>> with the Key ID (KID) embedded within the
|
||||
JWS. Depending what language you code in, there are a multitude of third party libraries out there that can help you with JWS validation.
|
||||
JWS. Depending on what language you code in, there are a multitude of third party libraries out there that can help you with JWS validation.
|
||||
|
||||
|
||||
==== Flows
|
||||
|
|
|
@ -39,7 +39,7 @@ allowAnyHostname::
|
|||
but host name validation is not done.
|
||||
This setting should only be used during development and *never* in production
|
||||
as it will partly disable verification of SSL certificates.
|
||||
This seting may be useful in test environments. This is _OPTIONAL_.
|
||||
This setting may be useful in test environments. This is _OPTIONAL_.
|
||||
The default value is `false`.
|
||||
|
||||
truststore::
|
||||
|
@ -47,7 +47,7 @@ truststore::
|
|||
If you prefix the path with `classpath:`, then the truststore will be obtained from the deployment's classpath instead.
|
||||
Used for outgoing HTTPS communications to the {project_name} server.
|
||||
Client making HTTPS requests need a way to verify the host of the server they are talking to.
|
||||
This is what the trustore does.
|
||||
This is what the truststore does.
|
||||
The keystore contains one or more trusted host certificates or certificate authorities.
|
||||
You can create this truststore by extracting the public certificate of the {project_name} server's SSL keystore.
|
||||
This is _REQUIRED_ unless `disableTrustManager` is `true`.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
===== IDP SingleLogoutService sub element
|
||||
|
||||
The `SingleLogoutService` sub element defines the logout SAML endpoint of the IDP. The client adapter will send requests
|
||||
to the IDP formatted via the settings within this element when it wants to logout.
|
||||
to the IDP formatted via the settings within this element when it wants to log out.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
The `SingleSignOnService` sub element defines the login SAML endpoint of the IDP.
|
||||
The client adapter will send requests
|
||||
to the IDP formatted via the settings within this element when it wants to login.
|
||||
to the IDP formatted via the settings within this element when it wants to log in.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
@ -22,7 +22,7 @@ signRequest::
|
|||
Defaults to whatever the IDP `signaturesRequired` element value is.
|
||||
|
||||
validateResponseSignature::
|
||||
Should the client expect the IDP to sign the assertion response document sent back from an auhtn request?
|
||||
Should the client expect the IDP to sign the assertion response document sent back from an authn request?
|
||||
This setting _OPTIONAL_. Defaults to whatever the IDP `signaturesRequired` element value is.
|
||||
|
||||
requestBinding::
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
The `RoleMappingsProvider` is an optional element that allows for the specification of the id and configuration of the
|
||||
`org.keycloak.adapters.saml.RoleMappingsProvider` SPI implementation that is to be used by the SAML adapter.
|
||||
|
||||
When {project_name} is used as the IDP, it is possible to use the built in role mappers to map any roles before adding them to the
|
||||
When {project_name} is used as the IDP, it is possible to use the built-in role mappers to map any roles before adding them to the
|
||||
SAML assertion. However, the SAML adapters can be used to send SAML requests to third party IDPs and in this case it might be
|
||||
necessary to map the roles extracted from the assertion into a different set of roles as required by the SP. The
|
||||
`RoleMappingsProvider` SPI allows for the configuration of pluggable role mappers that can be used to perform the necessary
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
==== Logout
|
||||
|
||||
There are multiple ways you can logout from a web application.
|
||||
There are multiple ways you can log out from a web application.
|
||||
For Jakarta EE servlet containers, you can call `HttpServletRequest.logout()`. For any other browser application, you can point
|
||||
the browser at any url of your web application that has a security constraint and pass in a query parameter GLO, i.e. `$$http://myapp?GLO=true$$`.
|
||||
This will log you out if you have an SSO session with your browser.
|
||||
|
|
|
@ -9,7 +9,7 @@ While you could have multiple instances of your WAR with different adapter confi
|
|||
|
||||
{project_name} makes it possible to have a custom config resolver, so you can choose which adapter config is used for each request. In SAML, the configuration is only interesting in the login processing; once the user is logged in, the session is authenticated and it does not matter if the `keycloak-saml.xml` returned is different. For that reason, returning the same configuration for the same session is the correct way to go.
|
||||
|
||||
To achieve this, create an implementation of `org.keycloak.adapters.saml.SamlConfigResolver`. The following example uses the `Host` header to locate the proper configuration and load it and the associated elements from the applications's Java classpath:
|
||||
To achieve this, create an implementation of `org.keycloak.adapters.saml.SamlConfigResolver`. The following example uses the `Host` header to locate the proper configuration and load it and the associated elements from the applications' Java classpath:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
|
|
@ -11,7 +11,7 @@ Instead you define a filter mapping using the {project_name} servlet filter adap
|
|||
|
||||
NOTE: Backchannel logout works a bit differently than the standard adapters.
|
||||
Instead of invalidating the http session it instead marks the session ID as logged out.
|
||||
There's just no way of arbitrarily invalidating an http session based on a session ID.
|
||||
There's just no way of arbitrarily invalidating an HTTP session based on a session ID.
|
||||
|
||||
WARNING: Backchannel logout does not currently work when you have a clustered application that uses the SAML filter.
|
||||
|
||||
|
|
|
@ -216,7 +216,7 @@ Use this procedure to set important client configuration parameters.
|
|||
|
||||
Many SAML SPs determine authorization based on a user's membership in a group. The {project_name} IdP can manage user group information but it does not supply the user's groups unless the IdP is configured to supply it as a SAML attribute.
|
||||
|
||||
Perform the following procedure to configure the IdP to supply the user's groups as as a SAML attribute.
|
||||
Perform the following procedure to configure the IdP to supply the user's groups as a SAML attribute.
|
||||
|
||||
.Procedure
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ a JSON document as described in the link:https://datatracker.ietf.org/doc/html/d
|
|||
|
||||
Clients requesting a refresh token will get back both an access and refresh token in the response. Clients requesting only
|
||||
access token type will only get an access token in the response. Expiration information may or may not be included for
|
||||
clients requesting an external issuer through the `requested_issuer` paramater.
|
||||
clients requesting an external issuer through the `requested_issuer` parameter.
|
||||
|
||||
Error responses generally fall under the 400 HTTP response code category, but other error status codes may be returned
|
||||
depending on the severity of the error. Error responses may include content depending on the `requested_issuer`.
|
||||
|
@ -462,7 +462,7 @@ are any untrustworthy clients that are managed by your realm, public clients may
|
|||
This is why direct naked exchanges do not allow public clients and will abort with an error if the calling client is public.
|
||||
|
||||
It is possible to exchange social tokens provided by Facebook, Google, etc. for a realm token. Be careful and vigilante on what
|
||||
the exchange token is allowed to do as its not hard to create fake accounts on these social websites. Use default roles, groups, and identity provider mappers to control what attributes and roles
|
||||
the exchange token is allowed to do as it's not hard to create fake accounts on these social websites. Use default roles, groups, and identity provider mappers to control what attributes and roles
|
||||
are assigned to the external social user.
|
||||
|
||||
Direct naked exchanges are quite dangerous. You are putting a lot of trust in the calling client that it will never leak out
|
||||
|
|
Loading…
Reference in a new issue