Integrate Strong Customer Authentication (SCA)
If you want to embed Swan into your own mobile app, you'll need to integrate Strong Customer Authentication (SCA) using in-app browsers. This way, your users will have the security of Swan's SCA directly from your app.
For security reasons, all links must open in in-app browsers.
Therefore, never use WebViews to open URLs (consentUrl or oAuthUrl).
Overview
To integrate Swan's Strong Customer Authentication (SCA), you'll need:
- An in-app browser package.
- A custom URL scheme with a deep link listener package.
- A backend server (this guide uses
https://my-company-api.com).
Check out Swan's demo app GitHub repository for an integration example.
Step 1: Set up your in-app browser
Use the documentation for your system's recommended API to set up your in-app browser.
| System | API | Reference |
|---|---|---|
| Android | Custom Tabs API | Swan implementation |
| iOS | SFSafariViewController API | Swan implementation |
We provide an official React Native package: @swan-io/react-native-browser
When using frameworks other than React Native, ensure that your package is configured in a way that follows Swan's implementation references.
Step 2: Set up your deep link listener
Use the documentation for your system's recommended tutorial to set up the deep link listener for your app.
| System | Recommended guide |
|---|---|
| Android | Create deep links to app content |
| iOS | Defining a custom URL scheme for your app |
If you are using React Native, follow this guide.
Step 3: Configure the sign-in process
This five-step process describes an ideal path for your users to sign in.
3.1 Sign-in button
First, the user clicks a sign-in button in your app. Clicking this button opens an in-app browser and starts listening for deep links.
Linking.addListener("url", ({ url }) => {
// called when deep link is visited
});
// …
openBrowser("https://my-company-api.com/auth").catch((error) => {
console.error(error);
});
3.2 Authorization URL
Next, your server constructs a Swan authorization URL and redirects the user.
app.get("/auth", async (req, reply) => {
const params = querystring.encode({
client_id: "$YOUR_CLIENT_ID",
response_type: "code",
redirect_uri: "https://my-company-api.com/auth/callback", // must be registered in our dashboard
scope: ["openid", "offline"].join(" "),
state: generateUserState(req),
});
return reply.redirect(`https://oauth.swan.io/oauth2/auth?${params}`);
});
3.3 User logs in
The user logs in and is redirected to https://my-company-api.com/auth/callback.
3.4 Server responds
Your server:
- Reads the authorization code.
- Exchanges it for a user access token.
- Stores the token using a unique identifier.
- Redirects the user to a deep link.
app.get("/auth/callback", async (req, reply) => {
if (req.query.state !== generateUserState(req)) {
throw new Error("Something isn't right, abort");
}
const token = await client.post("https://oauth.swan.io/oauth2/token", {
client_id: "$YOUR_CLIENT_ID",
client_secret: "$YOUR_CLIENT_SECRET",
grant_type: "authorization_code",
code: req.query.code,
redirect_uri: "https://my-company-api.com/auth/callback",
});
const sessionId = randomUUID();
await storeTokenInKeyValueStore(sessionId, token);
return reply.redirect(`com.company.myapp://close?sessionId=${sessionId}`);
});
3.5 Close browser and store identifier
Finally, the listener closes the in-app browser and stores the identifier.
Linking.addListener("url", ({ url }) => {
// called when deep link is visited
if (url.startsWith("com.company.myapp://close")) {
closeBrowser(); // required on iOS
const { query } = parseUrl(url);
if (query.sessionId) {
AsyncStorage.setItem("sessionId", query.sessionId);
}
}
});
// …
openBrowser("https://my-company-api.com/auth").catch((error) => {
console.error(error);
});
Step 4: Configure the consent process
This six-step process describes an ideal path for your users to consent. This example uses the action of creating a card.
It's important that, when clicked, consent links respect one of the following behaviors:
- Option 1: The link opens a pop-up that redirects the user to another page. Upon redirect, the pop-up closes automatically.
- Option 2: The link opens within the same page, then redirects the user to the rest of the flow.
4.1 Create card button
First, the user clicks a Create a card button in your app.
Your server API receives the call with the following sessionId header.
const sessionId = await AsyncStorage.getItem("sessionId");
await client.post("https://my-company-api.com/add-card", {
headers: { sessionId },
});
4.2 Consent URL
Your server calls the Swan API with a user access token and returns the consentUrl.
app.get("/add-card", async (req, reply) => {
const token = await getTokenFromKeyValueStore(req.headers.sessionId);
const { consentUrl } = await swanClient.addCard(
{
input: {
// …
consentRedirectUrl: "https://my-company-api.com/add-card/callback",
},
},
{
headers: {
Authorization: `Bearer $TOKEN`,
},
}
);
return reply.json({ consentUrl });
});
4.3 Open browser and start listening
The app opens an in-app browser and starts listening for deep links.
Linking.addListener("url", ({ url }) => {
// called when deep link is visited
});
// …
const sessionId = await AsyncStorage.getItem("sessionId");
const { consentUrl } = await client.post(
"https://my-company-api.com/add-card",
{ headers: { sessionId } }
);
openBrowser(consentUrl).catch((error) => {
console.error(error);
});
4.4 User consents
The user consents and is redirected to https://my-company-api.com/add-card/callback.
During redirection, Swan adds these query parameters to the URL.
consentIdenv: Sandbox or Live environmentstatus: Accepted, CustomerRefused, OperationCommitting, CredentialRefused, Created, Started, Expired, Failed, or Canceled- Including the status doesn't replace the need to query Swan's API to confirm the consent.
resourceId(if the consent only impacts one resource)
4.5 Additional operations
Your server performs any necessary additional operations, then redirects the user to a deep link.
app.get("/add-card/callback", async (req, reply) => {
// perform additional operations…
return reply.redirect("com.company.myapp://close");
});
4.6 Close browser
Finally, the listener closes the in-app browser.
Linking.addListener("url", ({ url }) => {
// called when deep link is visited
if (url.startsWith("com.company.myapp://close")) {
closeBrowser(); // required on iOS
}
});
const sessionId = await AsyncStorage.getItem("sessionId");
const { consentUrl } = await client.post(
"https://my-company-api.com/add-card",
{ headers: { sessionId } }
);
openBrowser(consentUrl).catch((error) => {
console.error(error);
});