Skip to content

Commit

Permalink
Support web flows
Browse files Browse the repository at this point in the history
  • Loading branch information
itaihanski committed Mar 20, 2024
1 parent 04227ea commit c013512
Show file tree
Hide file tree
Showing 9 changed files with 391 additions and 62 deletions.
83 changes: 73 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ You can read more on the [Descope Website](https://descope.com).
- [Session Management](#session-management)
- [Custom Claims](#custom-claims)
- [Error handling](#error-handling)
- [Authentication Flows](#running-flows)
- [Running Flows](#running-flows)
- Authenticate users using the authentication methods that suit your needs:
- [OTP](#otp-authentication) (one-time password)
- [TOTP](#totp-authentication) (timed one-time password / authenticator app)
Expand Down Expand Up @@ -225,24 +225,28 @@ define both the behavior and the UI that take the user through their
authentication journey. Read more about it in the Descope
[getting started](https://docs.descope.com/build/guides/gettingstarted/) guide.

### Setup #1: Define and host your flow
The flow setup differs according to the targeted platforms

Before we can run a flow, it must first be defined and hosted. Every project
### Mobile Flows Setup

#### Setup #1: Define and host your flow

Before we can run a mobile flow, it must first be defined and hosted. Every project
comes with predefined flows out of the box. You can customize your flows to suit your needs
and host it. Follow
the [getting started](https://docs.descope.com/build/guides/gettingstarted/) guide for more details.
You can host the flow yourself or leverage Descope's hosted flow page. Read more about it [here](https://docs.descope.com/customize/auth/oidc/#hosted-flow-application).
You can also check out the [auth-hosting repo itself](https://github.com/descope/auth-hosting).

### (Android Only) Setup #2: Enable App Links
#### (Android Only) Setup #2: Enable App Links

Running a flow via the Flutter SDK, when targeting Android, requires setting up [App Links](https://developer.android.com/training/app-links#android-app-links).
This is essential for the SDK to be notified when the user has successfully
authenticated using a flow. Once you have a domain set up and
[verified](https://developer.android.com/training/app-links/verify-android-applinks)
for sending App Links, you'll need to handle the incoming deep links in your app:

#### Define a route to handle the App Link sent at the end of a flow
##### Define a route to handle the App Link sent at the end of a flow
_this code example demonstrates how app links should be handled - you can customize it to fit your app_
```dart
final _router = GoRouter(
Expand All @@ -269,7 +273,7 @@ final _router = GoRouter(
);
```

#### Add a matching Manifest declaration
##### Add a matching Manifest declaration
Read more about the flutter specific `meta-data` tag mentioned here in the [official documentation](https://docs.flutter.dev/ui/navigation/deep-linking).
```xml
<activity
Expand Down Expand Up @@ -297,7 +301,7 @@ Read more about the flutter specific `meta-data` tag mentioned here in the [offi
</activity>
```

### (OPTIONAL) Setup #3: Support Magic Link Redirects
#### (OPTIONAL) Setup #3: Support Magic Link Redirects

Supporting Magic Link authentication in flows requires some platform specific setup:
- On Android: add another path entry to the [App Links](https://developer.android.com/training/app-links#android-app-links).
Expand All @@ -309,7 +313,7 @@ Supporting Magic Link authentication in flows requires some platform specific se
Regardless of the platform, another path is required to handle magic link redirects specifically. For the sake of this README, let's name
it `/magiclink`

#### Define a route to handle the App Link or Universal Link sent when a magic link is sent
##### Define a route to handle the App Link or Universal Link sent when a magic link is sent
_this code example demonstrates how app links or universal links should be handled - you can customize it to fit your app_
```dart
final _router = GoRouter(
Expand Down Expand Up @@ -347,6 +351,42 @@ final _router = GoRouter(
);
```

### Web Flows Setup

When targeting the Web, the flow is embedded into the web app as a `Web Component`.
Provide a `flowId` to indicate which flow to run. It's recommended to provide the CSS key-value
pairs to control how the flow is positioned and displayed in your page. You can also provide
an optional `loadingElement` to be displayed in the web component loading state.

```dart
final options = DescopeFlowOptions(
web: DescopeWebFlowOptions(
flowId: 'flowId',
flowContainerCss: {
"background-color": "antiquewhite",
"width": "500px",
"min-height": "300px",
"margin": "auto",
"position": "relative",
"top": "50%",
"transform": "translateY(-50%)",
"display": "flex",
"flex-direction": "column",
"align-items": "center",
"justify-content": "center",
"box-shadow": "0px 0px 10px gray",
},
loadingElement: myCustomLoadingElement,
));
```

#### Handling Redirections

When targeting the web, authentication methods that redirect, such as OAuth and Magic Link,
require a different and simpler handling to the mobile flows: In these cases, the redirection
will include a few URL query parameters. Simply `run` the flow, and it will pick up
where it left off.

### Run a Flow

After completing the prerequisite steps, it is now possible to run a flow.
Expand All @@ -355,12 +395,35 @@ or via [ASWebAuthenticationSession](https://developer.apple.com/documentation/au
Run the flow by calling the flow start function:

```dart
final authResponse = await Descope.flow.start('<URL_FOR_FLOW_IN_SETUP_#1>', deepLinkUrl: '<URL_FOR_APP_LINK_IN_SETUP_#2>');
final options = DescopeFlowOptions(
mobile: DescopeMobileFlowOptions(
flowUrl: '<URL_FOR_FLOW_IN_MOBILE_SETUP_#1>',
deepLinkUrl: '<URL_FOR_APP_LINK_IN_MOBILE_SETUP_#2>'
),
web: DescopeWebFlowOptions(
flowId: 'flowId',
flowContainerCss: {
"background-color": "antiquewhite",
"width": "500px",
"min-height": "300px",
"margin": "auto",
"position": "relative",
"top": "50%",
"transform": "translateY(-50%)",
"display": "flex",
"flex-direction": "column",
"align-items": "center",
"justify-content": "center",
"box-shadow": "0px 0px 10px gray",
},
loadingElement: myCustomLoadingElement,
));
final authResponse = await Descope.flow.run(options);
final session = DescopeSession.fromAuthenticationResponse(authResponse);
Descope.sessionManager.manageSession(session);
```

When running on iOS nothing else is required. When running on Android, `Descope.flow.exchange()` function must be called.
When targeting Android, in order to complete the flow successfully, `Descope.flow.exchange()` function must be called.
See the [app link setup](#-android-only--setup-2--enable-app-links) for more details.

## Authentication Methods
Expand Down
3 changes: 2 additions & 1 deletion lib/descope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ export '/src/session/lifecycle.dart' show DescopeSessionLifecycle, SessionLifecy
export '/src/session/session.dart' show DescopeSession;
export '/src/session/storage.dart' show DescopeSessionStorage, SessionStorage, SessionStorageStore;
export '/src/session/token.dart' show DescopeToken;
export '/src/types/error.dart';
export '/src/types/flows.dart';
export '/src/types/others.dart';
export '/src/types/responses.dart';
export '/src/types/user.dart' show DescopeUser;
export '/src/types/error.dart';

/// Provides functions for working with the Descope API.
///
Expand Down
6 changes: 3 additions & 3 deletions lib/src/internal/http/http_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ typedef ResponseDecoder<T> = T Function(Map<String, dynamic> json, Map<String, S
ResponseDecoder<void> emptyResponse = (json, headers) => {};

class HttpClient {
final String baseURL;
final String baseUrl;
final DescopeLogger? logger;
final DescopeNetworkClient networkClient;

HttpClient(this.baseURL, this.logger, DescopeNetworkClient? client) : networkClient = client ?? _DefaultNetworkClient();
HttpClient(this.baseUrl, this.logger, DescopeNetworkClient? client) : networkClient = client ?? _DefaultNetworkClient();

// Convenience functions

Expand Down Expand Up @@ -82,7 +82,7 @@ class HttpClient {
}

Uri makeUrl(String route, Map<String, String?> params) {
var url = Uri.parse('$baseURL$basePath$route');
var url = Uri.parse('$baseUrl$basePath$route');
if (params.isNotEmpty) {
url = url.replace(queryParameters: params.compacted());
}
Expand Down
38 changes: 36 additions & 2 deletions lib/src/internal/others/stub_html.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,41 @@
// stub window & document
class StubWindow {
final localStorage = <String, String>{};
}
class StubDocument {
StubHtml? head;
StubHtml? body;
}
class StubHtml {
final children = <Element>[];
}
final window = StubWindow();
final document = StubDocument();
Iterable<Element> querySelectorAll(String _) => <Element>[];

class Platform {}
// stub html elements
class NodeValidatorBuilder {
NodeValidatorBuilder.common();
allowElement(String _, {Iterable<String>? attributes}) => this;
}
class Element {
Element();
Element.html(String _, {NodeValidatorBuilder? validator});
remove() => {};
addEventListener(String _, EventListener? __) => {};
removeEventListener(String _, EventListener? __) => {};
String? className;
}
class DivElement extends Element{
final children = <Element>[];
}

final window = StubWindow();
// stub events
typedef EventListener = Function(Event event);
class Event {}
class CustomEvent {
final dynamic detail = <dynamic, dynamic>{};
}

// others
class Platform {}
Loading

0 comments on commit c013512

Please sign in to comment.