Skip to main content

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.

In-app browser required

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:

  1. An in-app browser package.
  2. A custom URL scheme with a deep link listener package.
  3. A backend server (this guide uses https://awesome-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.

SystemAPIReference
AndroidCustom Tabs APISwan implementation
iOSSFSafariViewController APISwan implementation

There are also several wrappers available for these APIs:

Follow the references

When using frameworks other than React Native, ensure that your package is configured in a way that follows Swan's implementation references.

Use the documentation for your system's recommended tutorial to set up the deep link listener for your app.

SystemRecommended guide
AndroidCreate deep links to app content
iOSDefining a custom URL scheme for your app

There are also several guides available to listen for deep link configuration:

Step 3: Configure the sign-in process​

This five-step process describes an ideal path for your users to sign in.

Using Swan's react-native-browser package

Swan's react-native-browser takes care of listening to the deep link event. Please ignore the listener declarations in steps 3.1, 3.5, and 4.3.

Instead, use the following onClose option instead:

import { openBrowser } from "@swan-io/react-native-browser";

const onAction = () => {
// not needed with @swan-io/react-native-browser
// const listener = Linking.addListener("url", ({ url }) => {});

openBrowser("https://awesome-api.com/endpoint", {
onClose: (url) => {
// fired on browser closed
// url will be defined if the browser has been closed via deeplink
},
});
};

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.

const onSignInWithSwanButtonPress = () => {
const listener = Linking.addListener("url", ({ url }) => {
// called when deep link is visited
});

InAppBrowser.open("https://awesome-api.com/auth", {
onClose: () => listener.remove(), // executed when the browser is closed
});
};

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://awesome-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://awesome-api.com/auth/callback.

3.4 Server responds​

Your server:

  1. Reads the authorization code.
  2. Exchanges it for a user access token.
  3. Stores the token using a unique identifier.
  4. 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://awesome-api.com/auth/callback",
});

const sessionId = randomUUID();
await storeTokenInKeyValueStore(sessionId, token);

return reply.redirect(
`com.awesome.app://browser/close?sessionId=${sessionId}`
);
});

3.5 Close browser and store identifier​

Finally, the listener closes the in-app browser and stores the identifier.

const onSignInWithSwanButtonPress = () => {
const listener = Linking.addListener("url", ({ url }) => {
// called when deep link is visited

if (url.startsWith("com.awesome.app://browser/close")) {
InAppBrowser.close();

const { query } = parseUrl(url);

if (query.sessionId) {
AsyncStorage.setItem("sessionId", query.sessionId);
}
}
});

InAppBrowser.open("https://awesome-api.com/auth", {
onClose: () => listener.remove(), // executed when the browser is closed
});
};

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.

First, the user clicks a Create a card button in your app. Your server API receives the call with the following sessionId header.

const onAddCardButtonPress = async () => {
const sessionId = await AsyncStorage.getItem("sessionId");

await client.post("https://awesome-api.com/add-card", {
headers: { sessionId },
});
};

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://awesome-api.com/add-card/callback",
},
},
{
headers: {
Authorization: `Bearer $TOKEN`,
},
}
);

return reply.json({ consentUrl });
});

The app opens an in-app browser and starts listening for deep links.

const onAddCardButtonPress = async () => {
const sessionId = await AsyncStorage.getItem("sessionId");

const { consentUrl } = await client.post("https://awesome-api.com/add-card", {
headers: { sessionId },
});

const listener = Linking.addListener("url", ({ url }) => {
// called when deep link is visited
});

InAppBrowser.open(consentUrl, {
onClose: () => listener.remove(), // executed when the browser is closed
});
};

The user consents and is redirected to https://awesome-api.com/add-card/callback.

During redirection, Swan adds these query parameters to the URL.

  • consentId
  • env: Sandbox or Live environment
  • status: 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)

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.awesome.app://browser/close");
});

Finally, the listener closes the in-app browser.

const onAddCardButtonPress = async () => {
const sessionId = await AsyncStorage.getItem("sessionId");

const { consentUrl } = await client.post("https://awesome-api.com/add-card", {
headers: { sessionId },
});

const listener = Linking.addListener("url", ({ url }) => {
// called when deep link is visited

if (url.startsWith("com.awesome.app://browser/close")) {
InAppBrowser.close();
}
});

InAppBrowser.open(consentUrl, {
onClose: () => listener.remove(), // executed when the browser is closed
});
};