diff --git a/get-started/authentication-approach/token-based.md b/get-started/authentication-approach/token-based.md index 5779f78..e86b0ee 100644 --- a/get-started/authentication-approach/token-based.md +++ b/get-started/authentication-approach/token-based.md @@ -46,6 +46,10 @@ Choose your platform below: {% page-ref page="../ios.md" %} +{% page-ref page="../flutter.md" %} + +{% page-ref page="../xamarin.md" %} + ### 2. Backend Integration {% page-ref page="../backend-integration/" %} diff --git a/get-started/xamarin.md b/get-started/xamarin.md index b5087c6..81bbd97 100644 --- a/get-started/xamarin.md +++ b/get-started/xamarin.md @@ -265,6 +265,30 @@ namespace MyApp } ``` +## Get the Logged In State + +The `SessionState` reflects the user logged in state in the SDK locally on the device. That means even the `SessionState` is `Authenticated`, the session may be invalid if it is revoked remotely. After initializing the Authgear SDK, call `FetchUserInfoAsync` to update the `SessionState` as soon as it is proper to do so. + +```csharp +// value can be NoSession or Authenticated +// After Authgear.ConfigureAsync, it only reflects local state. +var sessionState = authgear.SessionState; + +if (sessionState == SessionState.Authenticated) +{ + try + { + var userInfo = await authgear.FetchUserInfoAsync(); + // sessionState is now up to date + } + catch (Exception ex) + { + // sessionState is now up to date + // it will change to NoSession if the session is invalid + } +} +``` + ## Logout To log out the user from the current app session, you need to invoke the`logout`function. @@ -273,6 +297,25 @@ To log out the user from the current app session, you need to invoke the`logout` await authgear.LogoutAsync(); ``` +## Calling An API + +To include the access token to the HTTP requests to your application server, you set the bearer token manually by using `authgear.AccessToken`. + +### Using HttpClient + +You can get the access token through `authgear.AccessToken`. Call `RefreshAccessTokenIfNeededAsync` every time before using the access token, the function will check and make the network call only if the access token has expired. Then, include the access token into the Authorization header of the http request. + +```csharp +await authgear.RefreshAccessTokenIfNeededAsync(); +// Access token is ready to use +// AccessToken can be string or undefined +// It will be empty if user is not logged in or session is invalid +var accessToken = authgear.AccessToken; +var client = GetHttpClient(); // Get the re-used http client of your app, as per recommendation. +var httpRequestMessage = new HttpRequestMessage(myHttpMethod, myUrl); +httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); +``` + ## Next steps To protect your application server from unauthorized access. You will need to **integrate your backend with Authgear**. diff --git a/integrate/account-deletion.md b/integrate/account-deletion.md index d4136f6..bff2ef1 100644 --- a/integrate/account-deletion.md +++ b/integrate/account-deletion.md @@ -39,6 +39,15 @@ await authgear.getUserInfo(); ``` {% endtab %} +{% tab title="Xamarin" %} +```csharp +// This method blocks until the user closes User Settings. +await authgear.OpenAsync(SettingsPage.Settings); +// One way to verify the validity of the session is to get User Info once. +await authgear.FetchUserInfoAsync(); +``` +{% endtab %} + {% endtabs %} ## Deactivated User diff --git a/integrate/auth-ui.md b/integrate/auth-ui.md index 6332fc5..75eda53 100644 --- a/integrate/auth-ui.md +++ b/integrate/auth-ui.md @@ -68,6 +68,15 @@ Future onPressOpenSettingsPage() async { ``` {% endtab %} +{% tab title="Xamarin" %} +```csharp +async void OnOpenSettingsClicked(object sender, EventArgs args) +{ + await authgear.OpenAsync(SettingsPage.Settings); +} +``` +{% endtab %} + {% tab title="iOS" %} ```swift func onPressOpenSettingsPage(sender: UIButton, forEvent event: UIEvent) { diff --git a/integrate/force-authentication-on-app-launch.md b/integrate/force-authentication-on-app-launch.md index 749207d..4a5e3f8 100644 --- a/integrate/force-authentication-on-app-launch.md +++ b/integrate/force-authentication-on-app-launch.md @@ -29,6 +29,24 @@ final authgear = Authgear( ``` {% endtab %} +{% tab title="Xamarin" %} +```csharp +var authgearOptions = new AuthgearOptions +{ + ClientId = CLIENT_ID, + AuthgearEndpoint = ENDPOINT, + TokenStorage: new TransientTokenStorage(), +}; +#if __ANDROID__ +var authgear = new AuthgearSdk(GetActivity().ApplicationContext, authgearOptions); +#else +#if __IOS__ +var authgear = new AuthgearSdk(UIKit.UIApplication.SharedApplication, authgearOptions); +#endif +#endif +``` +{% endtab %} + {% tab title="iOS" %} ```swift Authgear( diff --git a/integrate/reauthentication.md b/integrate/reauthentication.md index 2a063cc..0ada9a7 100644 --- a/integrate/reauthentication.md +++ b/integrate/reauthentication.md @@ -130,6 +130,65 @@ Future onClickPerformSensitiveOperation() async { ``` {% endtab %} +{% tab title="Xamarin" %} +```csharp +var ios = new BiometricOptionsIos +{ + LocalizedReason = "Use biometric to authenticate", + AccessConstraint = BiometricAccessConstraintIos.BiometricAny, +}; +var android = new BiometricOptionsAndroid +{ + Title = "Biometric Authentication", + Subtitle = "Biometric authentication", + Description = "Use biometric to authenticate", + NegativeButtonText = "Cancel", + AccessConstraint = BiometricAccessConstraintAndroid.BiometricOnly, + InvalidatedByBiometricEnrollment = false, +}; + +async void OnPerformSensitiveOperationClicked(object sender, EventArgs args) +{ + // Step 1: Refresh the ID token to ensure the claims are up-to-date. + await authgear.RefreshIdTokenAsync(); + + // Step 2: Check if the end-user can be reauthenticated. + var canReauthenticate = authgear.CanReauthenticate; + if (!canReauthenticate) + { + // Step 2.1: Depending on your business need, you may want to allow + // the end-user to proceed. + // Here we assume you want to proceed. + var idTokenHint = authgear.IdTokenHint; + + // Step 2.2: Call the sensitive endpoint with the ID token. + // It is still required to pass the ID token to the endpoint so that + // the endpoint can know the end-user CANNOT be reauthenticated. + await CallMySensitiveEndpointAsync(idTokenHint); + return; + } + + // Step 3: The end-user can be reauthenticated. + // If your app supports biometric authentication, you can pass + // the biometric options to reauthenticate. + // If biometric is enabled for the current user, it will be used instead. + await authgear.ReauthenticateAsync(new ReauthenticateOptions + { + RedirectURI: THE_REDIRECT_URI, + }, new BiometricOptions + { + Ios = ios, + Android = android, + }); + + // Step 4: If we reach here, the reauthentication was done. + // The ID token have up-to-date auth_time claim. + var idTokenHint = authgear.IdTokenHint; + await CallMySensitiveEndpointAsync(idTokenHint); +} +``` +{% endtab %} + {% tab title="Web" %} ```typescript async function onClickPerformSensitiveOperation() { @@ -358,6 +417,28 @@ public void onClickPerformSensitiveOperation() { } ``` {% endtab %} + +{% tab title="Xamarin" %} +```csharp +public async void OnPerformSensitiveOperationClicked(object sender, EventArgs args) +{ + await authgear.RefreshIdTokenAsync(); + var authTime = authgear.AuthTime; + if (authTime != null) + { + var now = DateTimeOffset.UtcNow; + var timedelta = now - authTime.Value; + if (timedelta < TimeSpan.FromMinutes(5)) + { + var idTokenHint = authgear.IdTokenHint; + callMySensitiveEndpoint(idTokenHint); + return; + } + } +} +``` +{% endtab %} + {% endtabs %} ## Backend Integration diff --git a/integrate/single-sign-on.md b/integrate/single-sign-on.md index f23c72f..b7acb72 100644 --- a/integrate/single-sign-on.md +++ b/integrate/single-sign-on.md @@ -34,6 +34,25 @@ final authgear = Authgear( ``` {% endtab %} +{% tab title="Xamarin" %} +```csharp +var authgearOptions = new AuthgearOptions +{ + ClientId = CLIENT_ID, + AuthgearEndpoint = ENDPOINT, + ShareSessionWithSystemBrowser = true, +}; +// Android +#if __ANDROID__ +var authgear = new AuthgearSdk(GetActivity().ApplicationContext, authgearOptions); +#else +#if __IOS__ +var authgear = new AuthgearSdk(UIKit.UIApplication.SharedApplication, authgearOptions); +#endif +#endif +``` +{% endtab %} + {% tab title="iOS" %} ```swift Authgear( diff --git a/integrate/user-profile.md b/integrate/user-profile.md index 15a3b62..97c44b2 100644 --- a/integrate/user-profile.md +++ b/integrate/user-profile.md @@ -60,6 +60,21 @@ authgear.fetchUserInfo(new OnFetchUserInfoListener() { }); ``` {% endtab %} + +{% tab title="Xamarin" %} +```csharp +try +{ + var userInfo = await authgear.FetchUserInfoAsync() +} +catch +{ + // failed to fetch user info + // the refresh token maybe expired or revoked +} +``` +{% endtab %} + {% endtabs %} ## Standard Attributes diff --git a/integrate/using-sdk-to-call-your-application-server.md b/integrate/using-sdk-to-call-your-application-server.md index 0ea81f4..af6f3f8 100644 --- a/integrate/using-sdk-to-call-your-application-server.md +++ b/integrate/using-sdk-to-call-your-application-server.md @@ -71,6 +71,25 @@ authgear.configure(configureOptions, new OnConfigureListener() { }); ``` {% endtab %} + +{% tab title="Xamarin" %} +```csharp +var authgearOptions = new AuthgearOptions +{ + ClientId = "", + AuthgearEndpoint: "", +}; +#if __ANDROID__ +var authgear = new AuthgearSdk(GetActivity().ApplicationContext, authgearOptions); +#else +#if __IOS__ +var authgear = new AuthgearSdk(UIKit.UIApplication.SharedApplication, authgearOptions); +#endif +#endif +await authgear.ConfigureAsync(); +``` +{% endtab %} + {% endtabs %} ### Get the latest session state @@ -146,6 +165,29 @@ if (sessionState == SessionState.AUTHENTICATED) { } ``` {% endtab %} + +{% tab title="Xamarin" %} +```csharp +// value can be NoSession or Authenticated +// After Authgear.ConfigureAsync, it only reflects local state. +var sessionState = authgear.SessionState; + +if (sessionState == SessionState.Authenticated) +{ + try + { + var userInfo = await authgear.FetchUserInfoAsync(); + // sessionState is now up to date + } + catch (Exception ex) + { + // sessionState is now up to date + // it will change to NoSession if the session is invalid + } +} +``` +{% endtab %} + {% endtabs %} ## Calling an API @@ -222,22 +264,42 @@ authgear.refreshAccessTokenIfNeeded() { result in try { authgear.refreshAccessTokenIfNeededSync(); } catch (OauthException e) { - // The token is expired. + // failed to refresh access token + // the refresh token maybe expired or revoked } +// access token is ready to use +// accessToken can be string or undefined +// it will be empty if user is not logged in or session is invalid String accessToken = authgear.getAccessToken(); -if (accessToken == null) { - // The user is not logged in, or the token is expired. - // It is up to the caller to decide how to handle this situation. Typically, the request could be aborted - // immediately as the response would be 401 anyways. - return; -} - HashMap headers = new HashMap<>(); headers.put("authorization", "Bearer " + accessToken); // Submit the request with the headers... ``` {% endtab %} + +{% tab title="Xamarin" %} +```csharp +try +{ + await authgear.RefreshAccessTokenIfNeededAsync(); +} +catch (OauthException ex) +{ + // failed to refresh access token + // the refresh token maybe expired or revoked +} +// access token is ready to use +// accessToken can be string or undefined +// it will be empty if user is not logged in or session is invalid +var accessToken = authgear.AccessToken; +var client = GetHttpClient(); // Get the re-used http client of your app, as per recommendation. +var httpRequestMessage = new HttpRequestMessage(myHttpMethod, myUrl); +httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); +// Send the request with the headers... +``` +{% endtab %} + {% endtabs %} ### Handle revoked sessions @@ -293,13 +355,28 @@ if let response = response as? HTTPURLResponse { // example only // if your application server return HTTP status code 401 for unauthorized request responseCode = httpConn.getResponseCode(); -if (responseCode != HttpURLConnection.Unauthorized) { +if (responseCode == HttpURLConnection.Unauthorized) { // if you want to clear the session state locally, call clearSessionState - // ` authgear.getSessionState()` will become `NO_SESSION` after calling + // `authgear.getSessionState()` will become `NO_SESSION` after calling authgear.clearSessionState(); } ``` {% endtab %} + +{% tab title="Xamarin" %} +```csharp +// example only +// if your application server return HTTP status code 401 for unauthorized request +statusCode = httpResponseMessage.StatusCode; +if (statusCode == HttpStatusCode.Unauthorized) +{ + // if you want to clear the session state locally, call ClearSessionState + // `authgear.SessionState` will become `NoSession` after calling + authgear.ClearSessionState(); +} +``` +{% endtab %} + {% endtabs %} diff --git a/strategies/biometric.md b/strategies/biometric.md index 163c3d2..f57309d 100644 --- a/strategies/biometric.md +++ b/strategies/biometric.md @@ -128,6 +128,41 @@ try { ``` {% endtab %} +{% tab title="Xamarin" %} +```csharp +// We will need the options for the other biometric api +var ios = new BiometricOptionsIos +{ + LocalizedReason = "Use biometric to authenticate", + AccessConstraint = BiometricAccessConstraintIos.BiometricAny, +}; +var android = new BiometricOptionsAndroid +{ + Title = "Biometric Authentication", + Subtitle = "Biometric authentication", + Description = "Use biometric to authenticate", + NegativeButtonText = "Cancel", + AccessConstraint = BiometricAccessConstraintAndroid.BiometricOnly, + InvalidatedByBiometricEnrollment = false, +}; +var biometricOptions = new BiometricOptions +{ + Ios = ios, + Android = android +}; +try +{ + // check if current device supports biometric login + authgear.EnsureBiometricIsSupported(biometricOptions); + // biometric login is supported +} +catch +{ + // biometric login is not supported +} +``` +{% endtab %} + {% endtabs %} * Enable biometric login for logged in user @@ -202,6 +237,20 @@ try { } ``` {% endtab %} + +{% tab title="Xamarin" %} +```csharp +try +{ + await authgear.EnableBiometricAsync(biometricOptions); + // enabled biometric login successfully +} +catch +{ + // failed to enable biometric with error +} +``` +{% endtab %} {% endtabs %} * Check if the current device enabled biometric login, we should check this before asking the user to log in with biometric credentials @@ -245,6 +294,21 @@ try { } ``` {% endtab %} + +{% tab title="Xamarin" %} +```csharp +try +{ + var enabled = await authgear.GetIsBiometricEnabledAsync(); + // show if biometric login is enabled +} +catch +{ + // failed to check the enabled status +} +``` +{% endtab %} + {% endtabs %} * Login with biometric credentials @@ -306,6 +370,20 @@ try { } ``` {% endtab %} + +{% tab title="Xamrin" %} +```csharp +try +{ + var userInfo = await authgear.AuthenticateBiometricAsync(biometricOptions); + // logged in successfully +} +catch +{ + // failed to login +} +``` +{% endtab %} {% endtabs %} * Disable biometric login in the current device @@ -356,6 +434,21 @@ try { } ``` {% endtab %} + +{% tab title="Xamarin" %} +```csharp +try +{ + await authgear.DisableBiometricAsync(); + // disabled biometric login successfully +} +catch +{ + // failed to disable biometric login +} +``` +{% endtab %} + {% endtabs %} * Error handling @@ -476,5 +569,46 @@ try { ``` {% endtab %} +{% tab title="Xamarin" %} +```csharp +try +{ + // ... +} +catch (OperationCanceledException ex) +{ + // user cancel +} +catch (BiometricPrivateKeyNotFoundException ex) +{ + // biometric info has changed. e.g. Touch ID or Face ID has changed. + // user have to set up biometric authentication again +} +catch (BiometricNoEnrollmentException ex) +{ + // device does not have biometric set up + // e.g. have not set up Face ID or Touch ID in the device +} +catch (BiometricNotSupportedOrPermissionDeniedException ex) +{ + // biometric is not supported in the current device + // or user has denied the permission of using Face ID +} +catch (BiometricNoPasscodeException ex) +{ + // device does not have unlock credential or passcode set up +} +catch (BiometricLockoutException ex) +{ + // the biometric is locked out due to too many failed attempts +} +catch +{ + // other error + // you may consider showing a generic error message to the user +} +``` +{% endtab %} + {% endtabs %}