Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated documentation for N2W #803

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- [Sign Up with a database connection](#sign-up-with-a-database-connection)
- [Get user information](#get-user-information)
- [Custom Token Exchange](#custom-token-exchange)
- [Native to Web SSO login](#native-to-web-sso-login)
- [Credentials Manager](#credentials-manager)
- [Secure Credentials Manager](#secure-credentials-manager)
- [Usage](#usage)
Expand Down Expand Up @@ -540,6 +541,64 @@ authentication
</details>


## Native to Web SSO login (Experimental)
> **Warning**
>
> Native to Web SSO login support in Auth0.Android is still experimental and can change in the future.

This feature allows you to authenticate a user in a web session using the refresh token obtained from the native session without requiring the user to log in again.

Call the api to fetch a webSsoToken in exchange for a refresh token. Use the obtained token to authenticate the user by calling the `/authorize` end point.

```kotlin
authentication
.fetchWebSsoToken("refresh_token")
.start(object : Callback<SSOCredentials, AuthenticationException> {
override fun onSuccess(result: SSOCredentials) {
// Use the web_sso token to authenticate the user in a web session in your app
}

override fun onFailure(exception: AuthenticationException) {
// Handle error
}

})
```

<details>
<summary>Using coroutines</summary>

``` kotlin
try {
val ssoCredentials = authentication
.fetchWebSsoToken("refresh_token")
.await()
} catch (e: AuthenticationException) {
e.printStacktrace()
}
```
</details>

<details>
<summary>Using Java</summary>

```java
authentication
.fetchWebSsoToken("refresh_token")
.start(new Callback<SSOCredentials, AuthenticationException>() {
@Override
public void onSuccess(@Nullable SSOCredentials payload) {
// Handle success
}
@Override
public void onFailure(@NonNull AuthenticationException error) {
// Handle error
}
});
```
</details>


## Credentials Manager

### Secure Credentials Manager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.annotation.VisibleForTesting
import com.auth0.android.Auth0
import com.auth0.android.Auth0Exception
import com.auth0.android.NetworkErrorException
import com.auth0.android.annotation.ExperimentalAuth0Api
import com.auth0.android.request.*
import com.auth0.android.request.internal.*
import com.auth0.android.request.internal.GsonAdapter.Companion.forMap
Expand Down Expand Up @@ -923,18 +924,21 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
}

/**
* Creates a new request to fetch a session token in exchange for a refresh token.
* Creates a new request to fetch a web sso token in exchange for a refresh token.
* This is still an experimental feature, test it thoroughly in the targeted devices and OS variants and let us know your feedback.
*
* @param refreshToken A valid refresh token obtained as part of Auth0 authentication
* @return a request to fetch a session token
* @return a request to fetch a web sso token
*
*/
public fun fetchSessionToken(refreshToken: String): Request<SSOCredentials, AuthenticationException> {
@ExperimentalAuth0Api
public fun fetchWebSsoToken(refreshToken: String): Request<SSOCredentials, AuthenticationException> {
val params = ParameterBuilder.newBuilder()
.setClientId(clientId)
.setGrantType(ParameterBuilder.GRANT_TYPE_TOKEN_EXCHANGE)
.set(SUBJECT_TOKEN_KEY, refreshToken)
.set(SUBJECT_TOKEN_TYPE_KEY, ParameterBuilder.TOKEN_TYPE_REFRESH_TOKEN)
.set(REQUESTED_TOKEN_TYPE_KEY, ParameterBuilder.TOKEN_TYPE_SESSION_TOKEN)
.set(REQUESTED_TOKEN_TYPE_KEY, ParameterBuilder.TOKEN_TYPE_SESSION_TRANSFER_TOKEN)
.asDictionary()
return loginWithTokenGeneric<SSOCredentials>(params)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public class ParameterBuilder private constructor(parameters: Map<String, String
"urn:ietf:params:oauth:grant-type:token-exchange"
public const val GRANT_TYPE_PASSKEY :String = "urn:okta:params:oauth:grant-type:webauthn"
public const val TOKEN_TYPE_REFRESH_TOKEN :String = "urn:ietf:params:oauth:token-type:refresh_token"
public const val TOKEN_TYPE_SESSION_TOKEN :String = "urn:auth0:params:oauth:token-type:session_token"
public const val TOKEN_TYPE_SESSION_TRANSFER_TOKEN :String = "urn:auth0:params:oauth:token-type:session_transfer_token"
public const val SCOPE_OPENID: String = "openid"
public const val SCOPE_OFFLINE_ACCESS: String = "openid offline_access"
public const val SCOPE_KEY: String = "scope"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,16 @@ public abstract class BaseCredentialsManager internal constructor(

@Throws(CredentialsManagerException::class)
public abstract fun saveCredentials(credentials: Credentials)
public abstract fun saveSsoCredentials(ssoCredentials: SSOCredentials)
public abstract fun getCredentials(callback: Callback<Credentials, CredentialsManagerException>)
public abstract fun getSsoCredentials(callback: Callback<SSOCredentials, CredentialsManagerException>)
public abstract fun getSsoCredentials(
parameters: Map<String, String>,
callback: Callback<SSOCredentials, CredentialsManagerException>
)

public abstract fun getSsoCredentials(
callback: Callback<SSOCredentials, CredentialsManagerException>
)

public abstract fun getCredentials(
scope: String?,
minTtl: Int,
Expand Down Expand Up @@ -65,7 +72,13 @@ public abstract class BaseCredentialsManager internal constructor(

@JvmSynthetic
@Throws(CredentialsManagerException::class)
public abstract suspend fun awaitSsoCredentials(): SSOCredentials
public abstract suspend fun awaitSsoCredentials(parameters: Map<String, String>)
: SSOCredentials

@JvmSynthetic
@Throws(CredentialsManagerException::class)
public abstract suspend fun awaitSsoCredentials()
: SSOCredentials

@JvmSynthetic
@Throws(CredentialsManagerException::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.auth0.android.authentication.storage

import android.text.TextUtils
import androidx.annotation.VisibleForTesting
import com.auth0.android.annotation.ExperimentalAuth0Api
import com.auth0.android.authentication.AuthenticationAPIClient
import com.auth0.android.authentication.AuthenticationException
import com.auth0.android.callback.Callback
Expand Down Expand Up @@ -54,44 +55,41 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
storage.store(LEGACY_KEY_CACHE_EXPIRES_AT, credentials.expiresAt.time)
}


/**
* Stores the given [SSOCredentials] refresh token in the storage.
* This method must be called if the SSOCredentials are obtained by directly invoking [AuthenticationAPIClient.fetchSessionToken] api and
* [rotating refresh token](https://auth0.com/docs/secure/tokens/refresh-tokens/refresh-token-rotation) are enabled for
* the client. Method will silently return ,if the passed credentials has no refresh token.
*
* @param ssoCredentials the credentials to save in the storage.
* Fetches a new [SSOCredentials] . It will fail with [CredentialsManagerException]
* if the existing refresh_token is null or no longer valid. This method will handle saving the refresh_token,
* if a new one is issued.
* This is still an experimental feature, test it thoroughly and let us know your feedback.
*/
override fun saveSsoCredentials(ssoCredentials: SSOCredentials) {
if (ssoCredentials.refreshToken.isNullOrEmpty())
return // No refresh token to save
serialExecutor.execute {
val existingRefreshToken = storage.retrieveString(KEY_REFRESH_TOKEN)
// Checking if the existing one needs to be replaced with the new one
if (ssoCredentials.refreshToken == existingRefreshToken)
return@execute
storage.store(KEY_REFRESH_TOKEN, ssoCredentials.refreshToken)
}
@ExperimentalAuth0Api
override fun getSsoCredentials(callback: Callback<SSOCredentials, CredentialsManagerException>) {
getSsoCredentials(emptyMap(), callback)
}

/**
* Fetches a new [SSOCredentials] . It will fail with [CredentialsManagerException]
* if the existing refresh_token is null or no longer valid. This method will handle saving the refresh_token,
* if a new one is issued
* if a new one is issued.
* This is still an experimental feature, test it thoroughly and let us know your feedback.
*/
override fun getSsoCredentials(callback: Callback<SSOCredentials, CredentialsManagerException>) {
@ExperimentalAuth0Api
override fun getSsoCredentials(
parameters: Map<String, String>,
callback: Callback<SSOCredentials, CredentialsManagerException>
) {
serialExecutor.execute {
val refreshToken = storage.retrieveString(KEY_REFRESH_TOKEN)
if (refreshToken.isNullOrEmpty()) {
callback.onFailure(CredentialsManagerException.NO_REFRESH_TOKEN)
return@execute
}

val request = authenticationClient.fetchWebSsoToken(refreshToken)
try {
val sessionCredentials =
authenticationClient.fetchSessionToken(refreshToken)
.execute()
if (parameters.isNotEmpty()) {
request.addParameters(parameters)
}
val sessionCredentials = request.execute()
saveSsoCredentials(sessionCredentials)
callback.onSuccess(sessionCredentials)
} catch (error: AuthenticationException) {
Expand All @@ -115,21 +113,37 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
/**
* Fetches a new [SSOCredentials] . It will fail with [CredentialsManagerException]
* if the existing refresh_token is null or no longer valid. This method will handle saving the refresh_token,
* if a new one is issued
* if a new one is issued.
* This is still an experimental feature, test it thoroughly and OS variants and let us know your feedback.
*/
@JvmSynthetic
@Throws(CredentialsManagerException::class)
@ExperimentalAuth0Api
override suspend fun awaitSsoCredentials(): SSOCredentials {
return awaitSsoCredentials(emptyMap())
}

/**
* Fetches a new [SSOCredentials] . It will fail with [CredentialsManagerException]
* if the existing refresh_token is null or no longer valid. This method will handle saving the refresh_token,
* if a new one is issued.
* This is still an experimental feature, test it thoroughly and OS variants and let us know your feedback.
*/
@JvmSynthetic
@Throws(CredentialsManagerException::class)
@ExperimentalAuth0Api
override suspend fun awaitSsoCredentials(parameters: Map<String, String>): SSOCredentials {
return suspendCancellableCoroutine { continuation ->
getSsoCredentials(object : Callback<SSOCredentials, CredentialsManagerException> {
override fun onSuccess(result: SSOCredentials) {
continuation.resume(result)
}
getSsoCredentials(parameters,
object : Callback<SSOCredentials, CredentialsManagerException> {
override fun onSuccess(result: SSOCredentials) {
continuation.resume(result)
}

override fun onFailure(error: CredentialsManagerException) {
continuation.resumeWithException(error)
}
})
override fun onFailure(error: CredentialsManagerException) {
continuation.resumeWithException(error)
}
})
}
}

Expand Down Expand Up @@ -458,6 +472,23 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
storage.remove(LEGACY_KEY_CACHE_EXPIRES_AT)
}

/**
* Helper method to store the given [SSOCredentials] refresh token in the storage.
* Method will silently return ,if the passed credentials has no refresh token.
*
* @param ssoCredentials the credentials to save in the storage.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun saveSsoCredentials(ssoCredentials: SSOCredentials) {
if (ssoCredentials.refreshToken.isNullOrEmpty())
return // No refresh token to save
val existingRefreshToken = storage.retrieveString(KEY_REFRESH_TOKEN)
// Checking if the existing one needs to be replaced with the new one
if (ssoCredentials.refreshToken == existingRefreshToken)
return // Same refresh token, no need to save
storage.store(KEY_REFRESH_TOKEN, ssoCredentials.refreshToken)
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun recreateCredentials(
idToken: String,
Expand Down
Loading