Authentication with Passkey
Prerequisites​
- Set up a developer account
- Create an application
- Create at least one identity
- Have at least one passkey bound to an identity
Overview​
This guide describes how to authenticate an application with a passkey using Beyond Identity within a standard OAuth2/OIDC authorization flow.
In response to an OIDC request to the Beyond Identity /authorize endpoint, Beyond Identity initiates passwordless authentication by returning an authentication challenge and other information to your app. Before authenticating, your app can use the Beyond Identity SDK to enumerate available passkeys and should perform some logic to select one, such as presenting selection UI to the user. Once a passkey is selected, you can then use the SDK to complete authentication and finally perform the OAuth code for token exchange.
Authorization​
1. Craft Authorization Url​
First you will need to craft an authorization URL. The base url can be found in the Beyond Identity Admin Console
under your application, select "EXTERNAL PROTOCOL". Copy the Authorization Endpoint
and add the following additional query parameters:
https://auth-$REGION.beyondidentity.com/v1/tenants/$TENANT_ID/realms/$REALM_ID/applications/$APPLICATION_ID/authorize?
response_type=code
&client_id=$APPLICATION_CLIENT_ID
&redirect_uri=$REDIRECT_URI
&scope=openid
&state=$STATE
&code_challenge_method=256
&code_challenge=$PKCE_CODE_CHALLENGE
Check your appliction config in the admin console for your APPLICATION_CLIENT_ID
.
The REDIRECT_URI
is your application's App Scheme or Universal URL.
Note that the following query parameters includes PKCE as it is recommeded, but optional. If you send an authorization request with PKCE, you will need to store the hash of the code_challenge
so that it can be passed to the token exchange endpoint later as a code_verifier
.
You will need to set PKCE as a Client Configuration in your Application Config.
The STATE
parameter is used to mitigiate CSRF attacks. Have your application generate a random string for the STATE
value on each authentication request. You should check that this string is returned back to you to in the response.
2. Configure Authenticator Config​
There are three pieces we need to check in the Authenticator Config before authentication. To check your config, navigate the Beyond Identity Admin Console and find your application. Select "AUTHENTICATOR CONFIG".
- In order to use the Embedded SDKs, the
Configuration Type
should be set to Embedded SDK. - Set the Invoke URL to a URL that "points" to where your application is. In the case of a native application (iOS, Android, Flutter, React Native), this is either an App Scheme or an Universal URL / App Link. In the case of a web application, this is just a URL to your web application or a specific page of your web application.
While app schemes are generally easier to set up, Universal URLs and App Links are recommended as they provide protection against App Scheme hijacking.
- Set the the Invocation Type. This specifies how our authentication URL is delivered to your application. Invocation Type can be one of two values:
Automatic: redirect to your application using the Invoke URL with a challenge that your app will need to sign.
Manual: the challenge will be returned to you as part of a JSON response.
Automatic
does a lot of the heavy lifting for you. If you initiate an OAuth2.0 request and specify the "Invoke URL" correctly, we'll get the Beyond Identity authentication URL to where it needs to be, whether this is inside of a native app or a web application.
Manual
gives you a lot more control, but you'll have to do a little extra work to wire this flow up. The possibilities include:
- Completley silent OAuth 2.0 authentication using Passkeys. No redirects needed in a web app and no web view needed in a native application.
- The flexibility to write your own intelligent routing layer using the Beyond Identity authentication URL. You may want to authenticate against passkeys in your browser on desktop, but use passkeys on your native app on mobile.
3. Start Authorization for Invocation Type​
- Manual
- Automatic
Start with a GET request to your crafted authorization URL. If successful, you will receive an authenticate_url
in the response. Pass that url along with a passkey id to the Embedded SDK's authenticate
function. You can confirm the validity of the URL with isAuthenticateUrl
.
Don't forget to initalize your SDK and present some logic to the user to select a passkey ahead of time.
- Javascript
- Kotlin
- Swift
- React Native
- Flutter
const BeyondIdentityAuthUrl = `https://auth-${REGION}.beyondidentity.com/v1/tenants/${TENANT_ID}/realms/${REALM_ID}/applications/${APPLICATION_ID}/authorize?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${URI_ENCODED_REDIRECT_URI}&scope=openid&state=${STATE}&code_challenge_method=S256&code_challenge=${PKCE_CODE_CHALLENGE}`;
let response = await fetch(BeyondIdentityAuthUrl, {
method: 'GET',
headers: new Headers({
'Content-Type': 'application/json',
}),
});
const data = await response.json();
// Display UI for user to select a passkey
const passkeys = await embedded.getPasskeys();
if (await Embedded.isAuthenticateUrl(data.authenticate_url)) {
// Pass url and selected passkey ID into the Beyond Identity Embedded SDK authenticate function
// Parse query parameters from the 'redirectUrl' for a 'code' and then exchange that code for an access token in a server
const { redirectUrl } = await embedded.authenticate(
data.authenticate_url,
passkeys[0].id
);
}
interface BeyondIdentityService {
@GET("/v1/tenants/${TENANT_ID}/realms/${REALM_ID}/applications/${APPLICATION_ID}/authorize")
suspend fun authorize(
@Query("client_id") client_id: String,
@Query("code_challenge") code_challenge: String,
@Query("code_challenge_method") code_challenge_method: String = "S256",
@Query("redirect_uri") redirect_uri: String,
@Query("response_type") response_type: String = "code",
@Query("scope") scope: String = "openid",
@Query("state") state: String,
): Response<AuthorizeResponse>
}
val response = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://auth-${REGION}.beyondidentity.com")
.build()
.create(BeyondIdentityService::class.java)
.authorize(
client_id = "${CLIENT_ID}",
code_challenge = "${CODE_CHALLENGE}",
code_challenge_method = "S256",
redirect_uri = "${REDIRECT_URI}",
response_type = "code",
scope = "openid",
state = "{$STATE}",
)
EmbeddedSdk.isAuthenticateUrl(response.authenticate_url) -> {
EmbeddedSdk.authenticate(
url = response.authenticate_url,
passkeyId = selectedPasskeyId,
) { result ->
result.onSuccess { authenticateResponse ->
authenticateResponse.redirectUrl?.let { redirectUrl ->
// This URL contains authorization code and state parameters
// Exchange the authorization code for an id_token using Beyond Identity's token endpoint.
var code = parseCode(redirectUrl)
var token = exchangeForToken(code)
}
}
}
}
let redirectURI = "\(REDIRECT_URI)".addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
let authURL = URL(string: "https://auth-\(REGION).beyondidentity.com/v1/tenants/\(TENANT_ID)/realms/\(REALM_ID)/applications/\(APPLICATION_ID)/authorize?response_type=code&client_id=\(CLIENT_ID)&redirect_uri=\(redirectURI)&scope=openid&state=\(STATE)")!
let request = URLRequest(url: authURL)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
struct AuthResponse: Decodable {
let authenticate_url: String
}
let webTask = URLSession.shared.dataTask(with: request) {(data, response, error) in
let response = try? JSONDecoder().decode(AuthResponse.self, from: data)
let authenticateURL = URL(string: response.authenticate_url)!
// Display UI to user to select a passkey with Embedded.shared.getPasskeys()
if Embedded.shared.isAuthenticateUrl(authenticateURL){
// Pass url and selected passkey ID into the Beyond Identity Embedded SDK authenticate function
// Parse query parameters from the 'redirectURL' for a 'code' and then exchange that code for an access token in a server
Embedded.shared.authenticate(
url: URL(string: authenticateURL,
id: passkeys.first!.id
){ result in
switch result {
case let .success(response):
print(response)
case let .failure(error):
print(error.localizedDescription)
}
}
}
}
webTask.resume()
import * as React from 'react';
import { Button } from 'react-native';
import { Embedded } from '@beyondidentity/bi-sdk-react-native';
export default function App() {
async function authenticate() {
const BeyondIdentityAuthUrl = `https://auth-${REGION}.beyondidentity.com/v1/tenants/${TENANT_ID}/realms/${REALM_ID}/applications/${APPLICATION_ID}/authorize?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${URI_ENCODED_REDIRECT_URI}&scope=openid&state=${STATE}&code_challenge_method=S256&code_challenge=${PKCE_CODE_CHALLENGE}`;
let response = await fetch(BeyondIdentityAuthUrl, {
method: 'GET',
headers: new Headers({
'Content-Type': 'application/json',
}),
});
const data = await response.json();
// Display UI for user to select a passkey
const passkeys = await Embedded.getPasskeys();
if (await Embedded.isAuthenticateUrl(data.authenticate_url)) {
// Pass url and selected passkey ID into the Beyond Identity Embedded SDK authenticate function
// Parse query parameters from the 'redirectUrl' for a 'code' and then exchange that code for an access token in a server
const { redirectUrl } = await Embedded.authenticate(
data.authenticate_url,
passkeys[0].id
);
}
}
return <Button title="Passwordless Login" onPress={authenticate} />;
}
var response = await client.get(
Uri.parse(
"https://auth-${REGION}.beyondidentity.com/v1/tenants/${TENANT_ID}/realms/${REALM_ID}/applications/${APPLICATION_ID}/authorize"
"?state=${STATE}"
"&scope=openid"
"&response_type=code"
"&redirect_uri=${Uri.encodeComponent("your.app")}"
"&code_challenge_method=S256"
"&code_challenge=${pkcePair.codeChallenge}"
"&client_id=${CLIENT_ID}",
),
);
Map responseBody = json.decode(response.body);
url = responseBody['authenticate_url']
// Display UI for user to select a passkey
var passkeys = await EmbeddedSdk.getPasskeys();
if (await EmbeddedSdk.isAuthenticateUrl(url)) {
// Pass url and selected passkey ID into the Beyond Identity Embedded SDK authenticate function
// Parse query parameters from the 'redirectUrl' for a 'code' and then exchange that code for an access token in a server
var authenticateResponse = await EmbeddedSdk.authenticate(url, passkeys.first.id);
}
The authenticate url that is redirected to your application will append a /bi-authenticate
path to your Invoke URL. Use a "/bi-authenticate" route to intercept this url in your application:
$invoke_url/bi-authenticate?request=<request>
Native application deep linking​
Make sure to set up deep linking in native applications to intercept the authenticate url.
While app schemes are generally easier to set up, Universal URLs and App Links are recommended as they provide protection against App Scheme hijacking.
- Kotlin
- Swift
- React Native
- Flutter
Android supports two ways of deep linking into an application. Pick one of the following and follow the guide:
Apple supports two ways of deep linking into an application. Pick one of the following and follow the guide:
If you are using Expo, follow the deep linking guide.
If you are using an app created with the react native CLI, follow the Kotlin and Swift guides to setting up deep linking on React Native.
Also check out React Native Linking:
import { useEffect, useState } from 'react';
import { Linking } from 'react-native';
export default function useDeepLinkURL() {
const [linkedURL, setLinkedURL] = (useState < string) | (null > null);
// If the app is not already open
useEffect(() => {
const getUrlAsync = async () => {
const initialUrl = await Linking.getInitialURL();
if (initialUrl !== null) {
setLinkedURL(decodeURI(initialUrl));
}
};
getUrlAsync();
}, []);
// If the app is already open
useEffect(() => {
const callback = ({ url }: { url: string }) => setLinkedURL(decodeURI(url));
const linkingEventListener = Linking.addEventListener('url', callback);
return () => {
linkingEventListener.remove();
};
}, []);
return { linkedURL };
}
// in App.js
const { linkedURL } = useDeepLinkURL();
useEffect(() => {
async function register() {
if (linkedURL !== null) {
console.log('intercepted:', linkedURL);
}
}
register();
}, [linkedURL]);
Follw the Deep linking flutter guide.
Handle Authorization in your applicaiton​
The authorization flow should begin in a webview with your crafted authorization URL.
Don't forget to initalize your SDK and present some logic to the user to select a passkey ahead of time.
In a native application, the webview used should follows best practices set out in RFC 8252 - OAuth 2.0 for Native Apps. This means that on iOS applications ASWebAuthenticationSession should be used, and Custom Tabs should be used on Android devices.
UIWebView
, WKWebView
, and WebView
are explicitly not supported due to the security and usability reasons explained in Section 8.12 of RFC 8252.
- Javascript
- Kotlin
- Swift
- React Native
- Flutter
This guide uses NextAuth for all OAuth/OIDC flows. All code snippets are provided in the context of the NextAuth Example App. Note that your application must be set up as a confidential application.
- Configuring NextAuth
Under next-auth-example/pages/api/auth/[...nextauth].ts
, add the following Beyond Identity provider. The provider will go through an OAuth/OIDC that will result in fetching an id token that will log you in to the example app. Many of the values used to configure the request are the values from your crafted authorization URL.
...
providers: [
{
id: "beyondidentity",
name: "Beyond Identity",
type: "oauth",
wellKnown: `https://auth-us.beyondidentity.com/v1/tenants/${<TENANT_ID>}/realms/${<REALM_ID>}/applications/${<APPLICATION_ID>}/.well-known/openid-configuration`,
authorization: { params: { scope: "openid" } },
clientId: process.env.APP_CLIENT_ID,
clientSecret: process.env.APP_CLIENT_SECRET,
idToken: true,
checks: ["pkce", "state"],
profile(profile) {
return {
id: profile.sub,
name: profile.sub,
email: profile.sub,
}
}
}
],
callbacks: {
async jwt({ token }) {
console.log(token);
}
}
...
- Wiring up
embedded.authenticate
Create a bi-authenticate.tsx
page under /next-auth-example/pages
. As long as your Invoke URL
is configured properly in your Authenticator Config, this is the page that will be redirected to during an authorization flow. Copy the following code snippet into that page.
import { useEffect, useState } from "react";
import "bootstrap/dist/css/bootstrap.css";
import { Passkey } from "@beyondidentity/bi-sdk-js";
const BIAuthenticate = () => {
const [biAuthenticateResult, setBiAuthenticateResult] = useState("");
useEffect(() => {
// -- 1
const authenticate = async () => {
const BeyondIdentityEmbeddedSdk = await import("@beyondidentity/bi-sdk-js");
let embedded = await BeyondIdentityEmbeddedSdk.Embedded.initialize();
if (embedded.isAuthenticateUrl(window.location.href)) {
// Only authenticate if the URL is a "bi-authenticate" URL
let biAuthenticateUrl = window.location.href;
// -- 2
biAuthenticate(biAuthenticateUrl).then(redirectURL => {
// -- 4
window.location.href = redirectURL;
}).catch(error => {
setBiAuthenticateResult(error.toString());
});
}
}
authenticate().catch(console.error);
}, []);
// -- 3
async function biAuthenticate(url: string): Promise<string> {
const BeyondIdentityEmbeddedSdk = await import("@beyondidentity/bi-sdk-js");
let embedded = await BeyondIdentityEmbeddedSdk.Embedded.initialize();
// Display passkeys so user can select one
let passkeys = await embedded.getPasskeys();
let promptText = passkeys.map((passkey, index) => {
return `${index}: ${passkey.identity.username}`;
}).join("\n");
let selectedIndex = parseInt(prompt(promptText, "index")!!);
if (selectedIndex >= 0 && selectedIndex < passkeys.length) {
let selectedId = passkeys[selectedIndex].id;
// Perform authentication using selected id
let result = await embedded.authenticate(url, selectedId);
return Promise.resolve(result.redirectURL);
} else {
// This will fail in core as it won't match to any id
return Promise.resolve("unknown_id");
}
}
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
}}
>
<div className="container">
<div className="row">
<div className="d-flex justify-content-center">
<div className="spinner-border" role="status">
<span className="sr-only"></span>
</div>
</div>
</div>
<div className="row">
{
biAuthenticateResult.length > 0 &&
<div className="row row-cols-1 row-cols-md-1 mt-3">
<div className="col">
<code>
{JSON.stringify(biAuthenticateResult, null, 2)}
</code>
</div>
</div>
}
</div>
</div>
</div>
);
};
export default BIAuthenticate;
What's happening here?
- The
useEffect
is only called once on page load. In this function, we initialize the Beyond Identity SDK and useembedded.isAuthenticateUrl
to check if the current page that was redirected to is in fact a validbi-authenticate
URL. - If the URL is valid, we pull the URL using
window.location.href
and pass that directly intobiAuthenticate
in step 3. biAuthenticate
callsembedded.authenticate
with a validbi-authenticate
URL. This function performs a challenge/response against a passkey bound to your browser. Note that the callback inembedded.authenticate
contains logic in order to prompt a user to select a passkey if there is more than one.- Finally, the response of
embedded.authenticate
contains aredirectURL
. Follow this redirectURL to complete the OAuth/OIDC flow.
- Build a CustomTabsIntent, and launch your crafted authorization URL.
val builder = CustomTabsIntent.Builder()
...
builder.build().launchUrl(context, AUTH_URL)
- A URL with the Invoke URL scheme should be triggered by the web page. The Android OS will look for an appropraite Activity to handle the Intent. In your Activity, which handles your Beyond Identity scheme, override onCreate &/ onNewIntent, and call
EmbeddedSdk.authenticate()
. You can confirm the validity of the URL withEmbeddedSdk.isAuthenticateUrl()
.
intent?.data?.let { uri ->
when {
EmbeddedSdk.isAuthenticateUrl(uri.toString()) -> {
EmbeddedSdk.authenticate(
url = uri.toString(),
passkeyId = selectedPasskeyId,
) {
...
}
}
...
}
}
- A
redirectURL
is returned from a successfulAuthenticateResponse
. The authorization code and the state parameter are attached to this URL. You can now exchange the code for an id token using your Beyond Identity Token Endpoint.
intent?.data?.let { uri ->
when {
EmbeddedSdk.isAuthenticateUrl(uri.toString()) -> {
EmbeddedSdk.authenticate(
url = uri.toString(),
passkeyId = selectedPasskeyId,
) { result ->
result.onSuccess { authenticateResponse ->
authenticateResponse.redirectUrl?.let { redirectUrl ->
// This URL contains authorization code and state parameters
// Exchange the authorization code for an id_token using Beyond Identity's token endpoint.
var code = parseCode(redirectUrl)
var token = exchangeForToken(code)
}
}
}
}
...
}
}
Full Example:
private fun launchBI(context: Context, url: Uri = AUTH_URL) {
CustomTabsIntent.Builder().build().launchUrl(context, url)
}
private fun handleIntent(context: Context, intent: Intent?) {
selectPasskeyId { selectedPasskeyId ->
intent?.data?.let { uri ->
when {
EmbeddedSdk.isAuthenticateUrl(uri.toString()) -> {
EmbeddedSdk.authenticate(
url = uri.toString(),
passkeyId = selectedPasskeyId,
) { result ->
result.onSuccess { authenticateResponse ->
authenticateResponse.redirectUrl?.let { redirectUrl ->
// This URL contains authorization code and state parameters
// Exchange the authorization code for an id_token using Beyond Identity's token endpoint.
var code = parseCode(redirectUrl)
var token = exchangeForToken(code)
}
}
}
}
}
}
}
}
private fun selectPasskeyId(callback: (String) -> Unit) {
// Where you can perform some logic here to select a passkey, or
// present UI to a user to enable them to select a passkey.
}
- Start an
ASWebAuthenticationSession
, and launch your crafted authorization URL.
let session = ASWebAuthenticationSession(
url: viewModel.authorizationURL,
callbackURLScheme: viewModel.callbackScheme
completionHandler: { (url, error) in }
)
session.presentationContextProvider = self
session.start()
- During the session completionHandler, a URL with the invoke URL scheme should be returned. You can confirm the validity of the URL with
Embedded.shared.isAuthenticateUrl
. When the webpage loads a URL, callEmbedded.shared.authenticate
with the URL and user selected passkey id.
let session = ASWebAuthenticationSession(
url: viewModel.authorizationURL,
callbackURLScheme: viewModel.callbackScheme
){ (url, error) in
guard Embedded.shared.isAuthenticateUrl(url) else {/*not valid*/}
Embedded.shared.authenticate(
url: url,
id: passkeyId
) { result in
switch result {
case let .success(response):
case let .failure(error):
}
}
}
- A
redirectURL
is returned from a successfulAuthenticateResponse
. The authorization code and the state parameter are attached to this URL. You can now exchange the code for an id token using your Beyond Identity Token Endpoint.
Embedded.shared.authenticate(
url: url,
id: passkeyId
) { result in
switch result {
case let .success(response):
let code = parseCode(from: response.redirectURL)
let token = exchangeForToken(code)
case let .failure(error):
}
}
Full Example:
let session = ASWebAuthenticationSession(
url: viewModel.authorizationURL,
callbackURLScheme: viewModel.callbackScheme
){ (url, error) in
guard Embedded.shared.isAuthenticateUrl(url) else {
print("url is not valid")
return
}
presentPasskeySelection { selectedPasskeyId in
Embedded.shared.authenticate(
url: url,
id: selectedPasskeyId
) { result in
switch result {
case let .success(response):
let code = parseCode(from: response.redirectURL)
let token = exchangeForToken(code)
case let .failure(error):
print(error)
}
}
}
}
session.presentationContextProvider = self
session.start()
private fun presentPasskeySelection(callback: (PasskeyID) -> Void) {
// Where you can perform some logic here to select a passkey, or
// present UI to a user to enable them to select a passkey.
}
For the reasons stated in RFC 8252, unfortunately, the react-native-community/react-native-webview is NOT recommended. If you application is able to use expo, please use expo-auth-session. If you are not using expo, please find a library that uses ASWebAuthenticationSession on iOS, and Custom Tabs on Android. One possible library is InAppBrowser.
- expo
- react-native init
- Subscribe to the
useAuthRequest
hook with the Beyond Identity discovery url. Many of the values used to configure the request are the values from your crafted authorization URL.
const discovery = useAutoDiscovery(
`https://auth-${REGION}.beyondidentity.com/v1/tenants/${TENANT_ID}/realms/${REALM_ID}/applications/${APPLICATION_ID}`
);
// Request
const [request, response, promptAsync] = useAuthRequest(
{
clientId: `${CLIENT_ID}`,
scopes: ['openid'],
redirectUri: makeRedirectUri({
scheme: 'your.app',
}),
},
discovery
);
- Use
React.useEffect
to subscribe the theresponse
of theuseAuthRequest
. The response will contain aurl
that can be passed toEmbedded.authenticate
. You can confirm the validity of the url withEmbedded.isAuthenticateUrl
.
React.useEffect(() => {
const authenticate = async (url) => {
// Display UI for user to select a Passwordless Passkey
const passkeys = await Embedded.getPasskeys();
if (await Embedded.isAuthenticateUrl(url)) {
// Pass url and selected Passkey ID into the Beyond Identity Embedded SDK authenticate function
// Parse query parameters from the 'redirectURL' for a 'code' and then exchange that code for an access token in a server */
const { redirectURL } = await Embedded.authenticate(url, passkeys[0].id);
}
};
// response does not need to be of type 'success' if it has a url.
// The state value is stored in a JWT in the url 'request' paramater and the url will be validated through the Beyond Identity Embedded SDK.
if (response?.url) {
authenticate(url);
}
}, [response]);
- A
redirectURL
is returned from a successfulAuthenticateResponse
. The authorization code and the state parameter are attached to this URL. You can now exchange the code for an id token using your Beyond Identity Token Endpoint.
const { redirectURL } = await Embedded.authenticate(url, passkeys[0].id);
Full Example:
import * as React from 'react';
import {
makeRedirectUri,
useAuthRequest,
useAutoDiscovery,
} from 'expo-auth-session';
import { Button } from 'react-native';
import { Embedded } from '@beyondidentity/bi-sdk-react-native';
export default function App() {
// Endpoint
const discovery = useAutoDiscovery(
`https://auth-${REGION}.beyondidentity.com/v1/tenants/${TENANT_ID}/realms/${REALM_ID}/applications/${APPLICATION_ID}`
);
// Request
const [request, response, promptAsync] = useAuthRequest(
{
clientId: `${CLIENT_ID}`,
scopes: ['openid'],
redirectUri: makeRedirectUri({
scheme: 'your.app',
}),
},
discovery
);
React.useEffect(() => {
const authenticate = async (url) => {
// Display UI for user to select a Passwordless Passkey
const passkeys = await Embedded.getPasskeys();
if (await Embedded.isAuthenticateUrl(url)) {
// Pass url and selected Passkey ID into the Beyond Identity Embedded SDK authenticate function
// Parse query parameters from the 'redirectURL' for a 'code' and then exchange that code for an access token in a server */
const { redirectURL } = await Embedded.authenticate(
url,
passkeys[0].id
);
}
};
// response does not need to be of type 'success' if it has a url.
// The state value is stored in a JWT in the url 'request' paramater and the url will be validated through the Beyond Identity Embedded SDK.
if (response?.url) {
authenticate(url);
}
}, [response]);
return (
<Button
disabled={!request}
title="Passwordless Login"
onPress={() => {
promptAsync();
}}
/>
);
}
- Check that an
InAppBrowser
isAvailable, and launch your crafted authorization URL.
if (await InAppBrowser.isAvailable()) {
let response = await InAppBrowser.openAuth(authUrl, 'yourScheme://', {});
}
- A URL with the invoke URL scheme should be returned from the webview. Call
Embedded.authenticate
with this url. You can confirm the validity of the URL withEmbedded.isAuthenticateUrl
.
if (await InAppBrowser.isAvailable()) {
const response = await InAppBrowser.openAuth(authUrl, 'yourScheme://', {});
if (await Embedded.isAuthenticateUrl(response.url)) {
const authResponse = await Embedded.authenticate(response.url, passkeyId);
}
}
- A
redirectURL
is returned from a successfulAuthenticateResponse
. The authorization code and the state parameter are attached to this URL. You can now exchange the code for an id token using your Beyond Identity Token Endpoint.
if (await Embedded.isAuthenticateUrl(response.url)) {
const authResponse = await Embedded.authenticate(response.url, passkeyId);
const code = parseCodeFrom(authResponse.redirectURL);
const token = exchangeForToken(code);
}
Full Example:
if (await InAppBrowser.isAvailable()) {
const response = await InAppBrowser.openAuth(authUrl, 'yourScheme://', {});
if (await Embedded.isAuthenticateUrl(response.url)) {
const selectedPasskeyId = await presentPasskeySelection();
const authResponse = await Embedded.authenticate(
response.url,
selectedPasskeyId
);
const code = parseCodeFrom(authResponse.redirectURL);
const token = exchangeForToken(code);
}
}
function presentPasskeySelection(): selectedPasskeyId {
// Where you can perform some logic here to select a passkey, or
// present UI to a user to enable them to select a passkey.
}
- Make a call to
FlutterWebAuth.authenticate()
, and launch your crafted authorization URL.
var result = await FlutterWebAuth.authenticate(
url: AUTH_URL,
callbackUrlScheme: CALLBACK_URL_SCHEME,
);
- The result will be a URL with the Invoke URL scheme. You can call
EmbeddedSdk.authenticate()
, using the result. You can confirm the validity of the URL withEmbeddedSdk.isAuthenticateUrl()
.
var authenticateResponse = await Embeddedsdk.authenticate(
result,
selectedPasskeyId
);
- A
redirectURL
is returned from a successfulAuthenticateResponse
. The authorization code and the state parameter are attached to this URL. You can now exchange the code for an id token using your Beyond Identity Token Endpoint.
// This URL contains authorization code and state parameters
// Exchange the authorization code for an id_token using Beyond Identity's token endpoint.
var code = parseCode(authenticateResponse.redirectUrl);
var token = exchangeForToken(code);
Full Example:
selectPasskeyId((selectedPasskeyId) async {
var result = await FlutterWebAuth.authenticate(
url: AUTH_URL,
callbackUrlScheme: CALLBACK_URL_SCHEME,
);
var authenticateResponse = await Embeddedsdk.authenticate(result, selectedPasskeyId);
// This URL contains authorization code and state parameters
// Exchange the authorization code for an id_token using Beyond Identity's token endpoint.
var code = parseCode(authenticateResponse.redirectUrl)
var token = exchangeForToken(code)
});
Future<void> selectPasskeyId(Function(String) callback) async {
// Where you can perform some logic here to select a passkey, or
// present UI to a user to enable them to select a passkey.
}
Token Exchange​
Calling the token endpoint is the second step in the authorization flow and usually happens in your backend if your application's Client Type is Confidential
. Make sure to a call the authorization endpoint first to retrieve an authorization code.
If your application is using the NextAuth provider (see the Javascript Authorization example using Automatic Invocation Type), you will not need to complete authentication with a token exchange.
1. Craft Token Url​
The token endpoint base url can also be found in the Beyond Identity Admin Console under your application, select "EXTERNAL PROTOCOL". Copy the Token Endpoint
.
2. Token Endpoint Auth Method​
Next, scroll down to "Client Configuration" and make note of your "Token Endpoint Auth Method". The "Token Endpoint Auth Method" will determin how to make the token exchange call. The token auth method can either be set to Client Secret Post
or Client Secret Basic
.
3. Start Token Exchange​
- Client Secret Basic
- Client Secret Post
The $CLIENT_ID
and $CLIENT_SECRET
are sent in the Basic Authorization header.
- Curl
- CSharp
- Dart
- Go
- Java
- Node
- Python
- Ruby
- Rust
/token
1 2 3 4 5
curl "https://auth-$(REGION).beyondidentity.com/v1/tenants/$(TENANT_ID)/realms/$(REALM_ID)/applications/$(APPLICATION_ID)/token" \ -X POST \ -u "$(CLIENT_ID):$(CLIENT_SECRET)" --basic \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code&code=$(CODE_FROM_AUTHORIZATION_RESPONSE)&code_verifier=$(CODE_VERIFIER_IF_USED_PKCE_IN_AUTHORIZATION_REQUEST)&redirect_uri=$(REDIRECT_URI_MUST_MATCH_VALUE_USED_IN_AUTHORIZATION_REQUEST)"
/token
/token
/token
/token
/token
/token
/token
/token
The $CLIENT_ID
and $CLIENT_SECRET
are sent in the body of the POST request as a form parameter.
- Curl
- CSharp
- Dart
- Go
- Java
- Node
- Python
- Ruby
- Rust
/token
1 2 3 4 5 6 7 8 9
curl "https://auth-$(REGION).beyondidentity.com/v1/tenants/$(TENANT_ID)/realms/$(REALM_ID)/applications/$(APPLICATION_ID)/token" \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -F "grant_type=authorization_code" \ -F "code=$(CODE_FROM_AUTHORIZATION_RESPONSE)" \ -F "client_id=$(CLIENT_ID)" \ -F "client_secret=$(CLIENT_SECRET_FROM_CONFIDENTIAL_APPLICATION)" \ -F "code_verifier=$(CODE_VERIFIER_IF_USED_PKCE_IN_AUTHORIZATION_REQUEST)" \ -F "redirect_uri=$(REDIRECT_URI_MUST_MATCH_VALUE_USED_IN_AUTHORIZATION_REQUEST)"
/token
/token
/token
/token
/token
/token
/token
/token