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://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.
System | API | Reference |
---|---|---|
Android | Custom Tabs API | Swan implementation |
iOS | SFSafariViewController API | Swan implementation |
There are also several wrappers available for these APIs:
- Swan's official package: @swan-io/react-native-browser
- @capacitor/browser
- flutter_inappwebview ChromeSafariBrowser
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 |
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.
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:
- 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://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
});
};
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 onAddCardButtonPress = async () => {
const sessionId = await AsyncStorage.getItem("sessionId");
await client.post("https://awesome-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://awesome-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.
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
});
};
4.4 User consentsβ
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 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.awesome.app://browser/close");
});