Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
27 changes: 27 additions & 0 deletions android/src/main/java/com/auth0/react/A0Auth0Module.kt
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,33 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0
}
}

@ReactMethod
override fun getSSOCredentials(parameters: ReadableMap?, headers: ReadableMap?, promise: Promise) {
val params = parameters?.toHashMap() ?: emptyMap()
val headerMap = headers?.toHashMap() ?: emptyMap()

secureCredentialsManager.getSSOCredentials(
params as Map<String, String>,
headerMap as Map<String, String>,
object : com.auth0.android.callback.Callback<com.auth0.android.result.SessionTransferCredentials, CredentialsManagerException> {
override fun onSuccess(result: com.auth0.android.result.SessionTransferCredentials) {
val map = WritableNativeMap().apply {
putString("sessionTransferToken", result.sessionTransferToken)
putString("tokenType", result.tokenType)
putInt("expiresIn", result.expiresIn)
result.idToken?.let { putString("idToken", it) }
result.refreshToken?.let { putString("refreshToken", it) }
}
promise.resolve(map)
}

override fun onFailure(error: CredentialsManagerException) {
handleCredentialsManagerError(error, promise)
}
}
)
}

override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
// No-op
}
Expand Down
4 changes: 4 additions & 0 deletions android/src/main/oldarch/com/auth0/react/A0Auth0Spec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,8 @@ abstract class A0Auth0Spec(context: ReactApplicationContext) : ReactContextBaseJ
@ReactMethod
@DoNotStrip
abstract fun clearDPoPKey(promise: Promise)

@ReactMethod
@DoNotStrip
abstract fun getSSOCredentials(parameters: ReadableMap?, headers: ReadableMap?, promise: Promise)
}
7 changes: 7 additions & 0 deletions ios/A0Auth0.mm
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ - (dispatch_queue_t)methodQueue
[self.nativeBridge getDPoPHeadersWithUrl:url method:method accessToken:accessToken tokenType:tokenType nonce:nonce resolve:resolve reject:reject];
}

RCT_EXPORT_METHOD(getSSOCredentials:(NSDictionary *)parameters
headers:(NSDictionary *)headers
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
[self.nativeBridge getSSOCredentialsWithParameters:parameters headers:headers resolve:resolve reject:reject];
}




Expand Down
29 changes: 29 additions & 0 deletions ios/NativeBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,35 @@ public class NativeBridge: NSObject {
resolve(removed)
}

@objc public func getSSOCredentials(parameters: [String: Any], headers: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
credentialsManager.ssoCredentials(parameters: parameters, headers: headers) { result in
switch result {
case .success(let ssoCredentials):
var response: [String: Any] = [
"sessionTransferToken": ssoCredentials.sessionTransferToken,
"tokenType": ssoCredentials.tokenType,
"expiresIn": ssoCredentials.expiresIn
]

// Add optional fields if present
if let idToken = ssoCredentials.idToken {
response["idToken"] = idToken
}
if let refreshToken = ssoCredentials.refreshToken {
response["refreshToken"] = refreshToken
}

resolve(response)
case .failure(let error):
reject(
NativeBridge.credentialsManagerErrorCode,
error.localizedDescription,
error
)
}
}
}

@objc public func getDPoPHeaders(url: String, method: String, accessToken: String, tokenType: String, nonce: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
// Validate parameters
guard !url.isEmpty else {
Expand Down
43 changes: 42 additions & 1 deletion src/core/interfaces/ICredentialsManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Credentials } from '../../types';
import type { Credentials, SessionTransferCredentials } from '../../types';

/**
* Defines the contract for securely managing user credentials on the device.
Expand Down Expand Up @@ -48,4 +48,45 @@ export interface ICredentialsManager {
* @returns A promise that resolves when the credentials have been cleared.
*/
clearCredentials(): Promise<void>;

/**
* Obtains session transfer credentials for performing Native to Web SSO.
*
* @remarks
* This method exchanges the stored refresh token for a session transfer token
* that can be used to authenticate in web contexts without requiring the user
* to log in again. The session transfer token can be passed as a cookie or
* query parameter to the `/authorize` endpoint to establish a web session.
*
* Session transfer tokens are short-lived and expire after a few minutes.
* Once expired, they can no longer be used for web SSO.
*
* If Refresh Token Rotation is enabled, this method will also update the stored
* credentials with new tokens (ID token and refresh token) returned from the
* token exchange.
*
* @param parameters Optional additional parameters to pass to the token exchange.
* @param headers Optional additional headers to include in the token exchange request.
* @returns A promise that resolves with the session transfer credentials.
*
* @example
* ```typescript
* // Get session transfer credentials
* const ssoCredentials = await auth0.credentialsManager.getSSOCredentials();
*
* // Option 1: Use as a cookie
* const cookie = `auth0_session_transfer_token=${ssoCredentials.sessionTransferToken}; path=/; domain=.yourdomain.com; secure; httponly`;
* document.cookie = cookie;
*
* // Option 2: Use as a query parameter
* const authorizeUrl = `https://${domain}/authorize?session_transfer_token=${ssoCredentials.sessionTransferToken}&...`;
* window.location.href = authorizeUrl;
* ```
*
* @see https://auth0.com/docs/authenticate/login/configure-silent-authentication
*/
getSSOCredentials(
parameters?: Record<string, any>,
headers?: Record<string, string>
): Promise<SessionTransferCredentials>;
}
47 changes: 47 additions & 0 deletions src/hooks/Auth0Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
ResetPasswordParameters,
MfaChallengeResponse,
DPoPHeadersParams,
SessionTransferCredentials,
} from '../types';
import type {
NativeAuthorizeOptions,
Expand Down Expand Up @@ -251,6 +252,51 @@ export interface Auth0ContextInterface extends AuthState {
getDPoPHeaders: (
params: DPoPHeadersParams
) => Promise<Record<string, string>>;

/**
* Obtains session transfer credentials for performing Native to Web SSO.
*
* @remarks
* This method exchanges the stored refresh token for a session transfer token
* that can be used to authenticate in web contexts without requiring the user
* to log in again. The session transfer token can be passed as a cookie or
* query parameter to the `/authorize` endpoint to establish a web session.
*
* Session transfer tokens are short-lived and expire after a few minutes.
* Once expired, they can no longer be used for web SSO.
*
* If Refresh Token Rotation is enabled, this method will also update the stored
* credentials with new tokens (ID token and refresh token) returned from the
* token exchange.
*
* **Platform specific:** This method is only available on native platforms (iOS/Android).
* On web, it will throw an error.
*
* @param parameters Optional additional parameters to pass to the token exchange.
* @param headers Optional additional headers to include in the token exchange request.
* @returns A promise that resolves with the session transfer credentials.
*
* @example
* ```typescript
* // Get session transfer credentials
* const ssoCredentials = await getSSOCredentials();
*
* // Option 1: Use as a cookie (recommended)
* const cookie = `auth0_session_transfer_token=${ssoCredentials.sessionTransferToken}; path=/; domain=.yourdomain.com; secure; httponly`;
* document.cookie = cookie;
* window.location.href = `https://yourdomain.com/authorize?client_id=${clientId}&...`;
*
* // Option 2: Use as a query parameter
* const authorizeUrl = `https://yourdomain.com/authorize?session_transfer_token=${ssoCredentials.sessionTransferToken}&client_id=${clientId}&...`;
* window.location.href = authorizeUrl;
* ```
*
* @see https://auth0.com/docs/authenticate/login/configure-silent-authentication
*/
getSSOCredentials: (
parameters?: Record<string, any>,
headers?: Record<string, string>
) => Promise<SessionTransferCredentials>;
}

const stub = (): any => {
Expand Down Expand Up @@ -283,6 +329,7 @@ const initialContext: Auth0ContextInterface = {
resetPassword: stub,
revokeRefreshToken: stub,
getDPoPHeaders: stub,
getSSOCredentials: stub,
};

export const Auth0Context =
Expand Down
21 changes: 21 additions & 0 deletions src/hooks/Auth0Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,25 @@ export const Auth0Provider = ({
}
}, [client]);

const getSSOCredentials = useCallback(
async (
parameters?: Record<string, any>,
headers?: Record<string, string>
) => {
try {
return await client.credentialsManager.getSSOCredentials(
parameters,
headers
);
} catch (e) {
const error = e as AuthError;
dispatch({ type: 'ERROR', error });
throw error;
}
},
[client]
);

const cancelWebAuth = useCallback(
() => voidFlow(client.webAuth.cancelWebAuth()),
[client, voidFlow]
Expand Down Expand Up @@ -339,6 +358,7 @@ export const Auth0Provider = ({
getCredentials,
hasValidCredentials,
clearCredentials,
getSSOCredentials,
cancelWebAuth,
loginWithPasswordRealm,
createUser,
Expand All @@ -364,6 +384,7 @@ export const Auth0Provider = ({
getCredentials,
hasValidCredentials,
clearCredentials,
getSSOCredentials,
cancelWebAuth,
loginWithPasswordRealm,
createUser,
Expand Down
Loading