App-to-App Account Linking (Starting From Your App)
The app-to-app account linking flow, starting from your app, enables users to link their Alexa user identity with their identity in another service by starting from your app or website. When you support the account linking flow from your app, your users can:
Discover your Alexa skill through your app.
Start skill enablement and account linking from within your app.
Link their account without entering their account credentials in either your app or the Alexa app, when they're logged in to both of the apps on their mobile device.
Link their account from your app using Login with Amazon (LWA) authorization server, when the Alexa app isn't installed on their mobile device.
Feature support for this account linking flow includes the following:
Launching the Alexa app from your app – iOS and Android
Login with Amazon – Web and as a fallback for iOS and Android
OAuth 2.0 authorization code grant with PKCE – LWA supports Proof Key for Code Exchange (PKCE) security measures in the authorization code grant account linking flow. This process verifies that the token request comes from the same client that initiated the authorization request, preventing authorization code interception attacks.
Account linking supports the following other implementation options:
App-to-app starting from the Alexa app – Users link their account by starting from the Alexa app instead of your app. For details, see App-to-App Account Linking (Starting From the Alexa App). This flow supports both authorization code and implicit grant types.
Alexa app only (browser flow) – Users accomplish account linking entirely within the Alexa app. This is the most common flow. For details, see Choosing an account linking flow. This flow supports both authorization code and implicit grant types.
If you have an app or website, implement one of the app-to-app account linking flows in addition to the Alexa app-only flow.
For the rest of this topic, the term app-to-app account linking refers specifically to app-to-app account linking that starts from your app.
Service – The service that you provide. For example, you might have a web-based service, "Ride Hailer", that lets users order taxis.
App – The app that your users use to interact with your service. Continuing the previous example, you might have a "Ride Hailer" app. This discussion assumes that you are the developer of the app.
Skill – The Alexa skill that enables the user to interact with your service using Alexa. This topic assumes that you're the developer of the skill.
Alexa app – The Amazon Alexa app that users can download for their mobile device.
Login with Amazon (LWA) – An authentication system that enables users to log in and grant access to their user profile data. In terms of app-to-app account linking, LWA is a fallback that you can implement to handle the case in which users don't have the Alexa app installed on their mobile device. For general information about LWA, see the Login with Amazon documentation.
OAuth 2.0 – An authentication standard by which your service can allow Alexa, with the user's permission, to access information from the account that the user has set up with you. For the OAuth 2.0 standard, see OAuth 2.0.
App Link – A deep link on Android that a user clicks to launch an app. For details about App Links, see the Android documentation.
Universal Link – A deep link on iOS that a user clicks to launch an app. For details about Universal Links, see the iOS documentation.
PKCE Authorization – LWA supports PKCE in the authorization code grant account linking flow for enhanced security. PKCE uses code challenge and code verifier parameters as defined in RFC 7636. Amazon recommends that your app support PKCE using SHA-256.
User experience
During app-to-app account linking, the user goes through the following workflow:
Your app gives the user the option to enable your skill and link their account with Alexa. If the user chooses to link their account, one of two things happens, depending on whether the user has the Alexa app installed on their mobile device.
(Alexa app flow) If the user has the Alexa app installed on their device, the Alexa app launches and asks the user to acknowledge the account linking request. After acknowledging the request, the user is returned to your app.
(LWA flow) If the user doesn't have the Alexa app installed on their device, an in-app browser window with LWA opens, and the user can enter their Amazon credentials or create an Amazon account. The user is then asked to give your skill permission to link the accounts. After acknowledging the request, the user is returned to your app.
After the account is linked and the user is using your skill, the skill uses the same workflow as Alexa-app only account linking. In any case, disabling the skill causes the accounts to unlink.
The following example shows the flow when the user installed the Alexa app on their mobile device.
Alexa app-to-app account linking using the Alexa app
Alexa app isn't installed
The following example shows the LWA flow when the user hasn't installed the Alexa app on their mobile device.
Alexa app-to-app account linking using Amazon login
Which flow to implement
The Alexa app account linking flow is available for iOS and Android. As such, the flow that you implement depends on whether you want to develop an iOS app, an Android app, a website, or any combination of these as follows:
iOS and Android apps – Implement the Alexa app flow as the primary flow, and the LWA flow as a fallback for when the Alexa app isn't installed.
Websites – Implement the LWA flow.
How it works
App-to-app account linking works by using OAuth 2.0. The following steps describe the authorization code grant flow to link the user accounts. The flow assumes the user installed and signed in to your app on their mobile device. For details about the URLs mentioned, see URLs and endpoints.
The following diagram shows the account linking flow when the user starts account linking from your app on their mobile device. Here, your app obtains the access token from LWA as described in steps 1–9.
(Click image to enlarge.)
App-to-app account linking authorization code grant flow
Your app presents the user with the option to enable your skill and link their account with Alexa, with a mention of the benefit (for example, "You can now order a car through Ride Hailer by voice with Alexa").
After the user acknowledges the linking request, your app backend starts the account linking flow. If your backend supports PKCE, it generates the code_verifier and derives the code_challenge using SHA-256, as defined in RFC 7636. Then, the backend returns the Alexa app and LWA website URLs to your app.
What happens next depends on whether the user installed the Alexa app on their mobile device.
The Alexa app launches and asks the user if they want to link Alexa with your service.
The user acknowledges the linking request.
If the Alexa app isn't installed:
Your app launches the LWA website at the LWA fallback URL in an in-app browser tab (not a native browser app) with the authorization request parameters, and Alexa app and LWA fallback URL query parameters.
LWA launches and asks the user to log in to their Amazon account.
LWA asks the user if they want to link Alexa with your service. The user acknowledges the linking request.
In both cases, if you include PKCE parameters in the authorization request, LWA temporarily stores the code_challenge and code_challenge_method along with the authorization code.
If the user agrees to link the accounts and logs in successfully, the Alexa app or LWA sends the user back to your app using your redirect URLs, and, on success, includes the user's Amazon authorization code and state as a part of that redirect.
Your app forwards the authorization code to your app backend.
LWA verifies the authorization code. If the request includes the PKCE parameter, LWA uses the code_verifier to verify the token endpoint, based on the stored PKCE parameters, as defined in RFC 7636.
If these values match, LWA issues access and refresh tokens. If they don't match, LWA returns an error.
Your backend app calls your authorization server to get the user's authorization code for their account in your service.
The Alexa service sends a token request to your app's access token URL to exchange the user's authorization code for your service for an access token for your service, thereby completing account linking.
The user's account in your service is now linked to their Alexa account, and the skill is ready for use.
URLs and endpoints
Your app needs the following URLs and endpoints for app-to-app account linking.
You can store the Alexa app URL and the LWA fallback URL in the back end, and retrieve them with a GET request. You can also store the URLs locally to avoid an extra network call; this might require a new build if you modify the skill security profile in the future.
Format: https://alexa.amazon.com/spa/skill-account-linking-consent?fragment=skill-account-linking-consent&client_id={ClientId}&scope=alexa::skills:account_linking&skill_stage={skillStage}&response_type=code&redirect_uri={yourRedirectUrl}&state={yourState}&code_challenge={hashed value}&code_challenge_method=S256
For example: https://alexa.amazon.com/spa/skill-account-linking-consent?fragment=skill-account-linking-consent&client_id=amzn1.application-oa-client-1&scope=alexa::skills:account_linking&skill_stage=development&response_type=code&redirect_uri=https://yourAppRedirectUri&state=aGVsbG8&code_challenge=AB12CCEXAMPLE
&code_challenge_method=S256
LWA fallback URL
A link that sends the user to LWA to enter their Amazon credentials. The URL works for iOS, Android, and websites. Your app uses this link when the user doesn't have the Alexa app installed on their device. For parameter descriptions, see Parameters for the Alexa app URL and the LWA fallback URL.
See the previous note for the Alexa app URL about storing the URLs.
For example: https://www.amazon.com/ap/oa?client_id=amzn1.application-oa-client-1&scope=alexa::skills:account_linking&response_type=code&redirect_uri=https://yourAppRedirectUri&state=aGVsbG8&code_challenge=AB12CCEXAMPLE&code_challenge_method=S256
Your app's redirect URLs
The Alexa app (or LWA, if the Alexa app isn't installed) uses this Universal Link or App Link to send the user back to your app after they acknowledge the linking request in the Alexa app or LWA. The parameters returned with the redirect provide your app with the user's Amazon authorization code, which is valid for 5 minutes.
You specify one or more URLs by using the Alexa developer console, ASK CLI, or Account Linking REST API. For the required syntax, see URI specification.
Authorization URL
The URL of your authorization server. The authorization server must accept the user's credentials, authenticate the user, and generate an authorization code that the Alexa app can later pass to your authorization server to retrieve an access token that uniquely identifies the user with your service.
This link is only used for regular (not app-to-app) account linking. You specify this by using the developer console, the ASK CLI, or Account Linking REST API.
Access token URL
Your token server, which Alexa uses to exchange the user's authorization code (for your service) for an access token, to complete account linking.
You specify the access token URL by using the developer console, the ASK CLI, or Account Linking REST API.
LWA authorization service
Your backend server uses a POST request to exchange the user's Amazon authorization code for an Amazon access token. This flow enables your app to call the Alexa service to enable the skill and link the account for the user.
Host: https://api.amazon.com with POST request on /auth/o2/token
Alexa Skill Enablement API endpoint
Your backend server calls this endpoint to enable the skill for the user. For details, see Alexa Skill Enablement API.
Depending on the user's location, the base URL could be https://api.amazonalexa.com, https://api.eu.amazonalexa.com, or https://api.fe.amazonalexa.com.
Alexa app and LWA fallback URL query parameters
Alexa supports the following parameters in the query string in the Alexa app and the LWA fallback URLs.
Parameter
Description
client_id
The Alexa client ID that the Alexa developer console provides when you enable app-to-app account linking in the developer console.
client_secret
The Alexa client secret that the developer console provides when you enable app-to-app account linking in the developer console.
redirect_uri
The Universal Link or App Link that you specify when you enable app-to-app account linking in the developer console. The Alexa app and LWA redirect the user back to this URL after completing the account linking request.
scope
Your app must include the alexa::skills:account_linking scope to obtain permission to enable the skill.
skill_id
The unique identifier of your skill. You can find this in the developer console.
stage
The skill stage. Until you publish your skill, set stage to development. After you published your skill, use live.
response_type
The type of response that the request returns after your service authenticates the user. Set to code for the authorization code grant type. App-to-app account linking supports authorization code grants only.
state
An opaque value that your app uses to maintain state between the current account linking request and the response. For the Alexa app URL, the state value is required. Alexa includes this state as-is in the response when it redirects the user back to your app at your redirect URLs. You must validate incoming requests using state to prevent cross-site request forgery.
Make sure that the state that you generate doesn't contain &, =, ‘, /, \, <, >, “, #, or |. You can use a base64 URI as a safe way to do this. For an example implementation, see the state helper code example in Step 4: Get the user's Amazon authorization code.
You can generate the state using a secure random number generator, and save it to your user session for validation. Alternatively, you can generate the state from your backend server and use a key to encrypt and decrypt it.
code_challenge
The hashed value of the PKCE code_verifier. Include if your app backend supports PKCE using SHA-256. For details, see RFC 7636.
code_challenge_method
The method to use to transform the code_verifier. Included if your app backend supports PKCE using SHA-256.
Account linking supports the SHA-256 code challenge method only.
Steps to implement app-to-app account linking in your app
Complete the following key steps and code examples to implement app-to-app account linking in an iOS app (iOS 10.0 or higher) and an Android app (Android 12 or higher).
Step 1: Display an account linking option in your app
Your app must have a screen from which a user can start skill enablement and account linking. This screen should show what the user can do by linking their account with Alexa, such as ordering a car by voice.
The way you let users start account linking depends on the experience you want to create for your users. You can offer account linking to users in multiple places. For example, you can give users the option to enable account linking after they register your app or provide them with a button on a "Link to Alexa" page in your app's settings page. For the options you choose, make sure to explain the benefit of linking an account with Alexa.
To add an account linking button to your iOS app, do the following:
Add a UIbutton to your view controller and name it something like "Link your account with Alexa". Consider using a name that indicates the benefit that the user gets by linking their account, such as "Listen to your music with Alexa", "Use Alexa to order a car with Ride Hailer".
On the top left of the Xcode IDE, click Show the assistant editor.
Create an action connection for your button by dragging the button to the code editor.
To add an account linking button to your Android app, do the following:
In Android Studio, in design mode, open the layout of the page that you want to add the app-to-app account linking button to.
From the palette of options, drag and drop a new button into your layout.
Define an ID for your button.
In the code for your page, initialize your app-to-app account linking button and define its onClick listener behavior to initiate the flow, as in the following example.
class AppToAppFragment : Fragment() {
private lateinit var appToAppButton: Button
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.appToAppPage, container, false)
initComponents(root)
appToApp.setOnClickListener {
doAppToApp()
}
}
private fun initComponents(root: View) {
appToAppButton = root.findViewById(R.id.appToAppAccountLinkingButton)
}
}
Step 2: Enable Universal Links (iOS) or App Links (Android) for your app
To allow the Alexa app to redirect the user to your app, you must enable Universal Links for your iOS app or App Links for your Android app.
Universal Links and App Links must comply with the specified URI syntax. Remember the domain and path that you added because you'll need them when you add redirect URLs to the skill details in the developer console.
Note: To support App Links for your app on Android, starting from Android 11(API level 30), you must declare the <queries> element in the package information. For details about the <queries> element, see the official Android documentation.
For examples of enabling Universal Links, see the Apple documentation at Enabling Universal Links.
First, make sure that you base your skill on a skill type that supports account linking. Then, configure your skill for account linking by using the developer console, the ASK CLI, or Account Linking REST API as described in the examples. If you're just getting started with account linking, see Add Account Linking to Your Alexa Skill.
To enable app-to-app account linking using the developer console, take the following steps.
Find your skill in the list. Under Actions for your skill, select Edit.
On the left side, click TOOLS, and then click ACCOUNT LINKING.
Turn on Allow users to link their account to your skill from within your application or website.
For the authorization grant type, select Auth Code Grant, if not already selected. App-to-app account linking supports authorization code grant only.
For the other settings, use the settings described in Configure Account Linking and heed the following additional requirements to fill in the redirect URLs:
The redirect URLs must comply with the specified URI syntax. The Alexa app opens this Universal Link or App Link URL to launch your app.
To handle different scenarios, you can add more than one redirect URL.
If your app doesn't support Universal Links or App Links, you should have a valid web page that your redirect URLs lead to. This should support the same process to receive the user's Amazon authorization code described in How it works.
Remember to add Your Redirect URLs for your iOS app, Android app, and website.
The following example shows the JSON format for the account-linking-request option. For more details about the fields, see Request body properties for Update account linking information REST API.
Set to true to let users enable the skill without starting the account linking flow. Set to false to require the normal account linking flow when users enable the skill. For more details, see Let Users Enable Your Skill without Account Linking.
Boolean
type
Specifies the OAuth authorization grant type. For app-to-app account linking, you must use AUTH_CODE.
List of additional domains that your log-in page gets content from. You can specify up to 15 domains.
Array of String
clientId
Identifier that the Amazon log-in page uses to recognize that the request came from your skill.
String
scopes
Strings that indicate the access that you need for the user account, such as the user_id. For smart home skills, this field is a required property. You can specify up to 15 scopes. You must include alexa::skills:account_linking.
Array of String
accessTokenUrl
Amazon OAuth URI for requesting authorization tokens
String
accessTokenScheme
Type of authentication used. Set to HTTP_BASIC or REQUEST_BODY_CREDENTIALS.
String
redirectUrls
Fallback website link when your app links aren't available. For more details, see URLs and endpoints.
Your app needs to get the user's Amazon authorization code so that it can later exchange it for an Amazon access token. The Amazon access token enables the app to enable the skill and complete account linking. The Amazon authorization code is valid for five minutes.
You get the user's Amazon authorization code by either opening the Alexa app URL (for the Alexa app flow) or by making an HTTP GET request to the LWA authorization server (for the LWA flow). For iOS and Android, Amazon strongly encourages you to implement the Alexa app flow and use LWA only as a fallback.
To get the user's Amazon authorization code, you assemble an authorization request and then open the Alexa app URL (or the LWA fallback URL, if the Alexa app isn't installed) with the parameters described in URLs and endpoints. Include PKCE security if your app backend supports PKCE using SHA-256. If your backend doesn't fully support PKCE with SHA-256 or you're unsure, omit the PKCE parameters. Account linking supports the SHA-256 code challenge method only.
Amazon highly recommends that you assemble these URLs in your backend server and pass the assembled URLs to your app. That way, you can quickly change parameters, such as the stage, without rebuilding your app.
When the user acknowledges the linking request, the Alexa app redirects the user to your app's redirect URL, which is a parameter in the Alexa or LWA fallback URLs. The redirection includes the success or error parameters described next.
If the user's device can't launch your app by using the redirect URL that you provided, the user is redirected to that same redirect URL in their default browser. This might happen, for example, when:
Universal Links or App Links aren't enabled in your app.
Universal Links or App Links aren't enabled in your app for the redirect URLs that you provided.
The version of the app on the user's device is an older version that doesn't support Universal Links or App Links.
In these cases, you should have a web page (at the same URL as the Universal Link or App Link) to receive the authorization code. When the user opens your web page, they might have to log in so that you can get the authorization code from your service.
// Example success response from the Alexa app
https://yourRedirectURL?code={Amazon.Authorization.code}&state={your state}
// Example success response from LWA
https://yourRedirectURL?code={Amazon.Authorization.code}&scope={permission scope}&state={your state}
where:
code: The Amazon authorization code that the Alexa app or LWA returns.
state: The state that you passed in the authorization request. You should validate the state to avoid cross-site request forgery.
scope: Permission scope. LWA returns alexa::skills:account_linking scope. The Alexa app doesn't return a scope parameter.
For additional details about OAuth2.0 authorization responses, see Authorization Response.
Error response of the HTTP GET request for the Alexa app URL
On error, the response is the redirect URL with the error and other parameters shown next. Your app must display the appropriate error message (an error page, UIAlertController, and so on). For the error message to display, see Errors when obtaining the Amazon authorization code.
error: A single ASCII error code. Possible values: invalid_request, unauthorized_client, access_denied, unsupported_response_type, invalid_scope, server_error, temporarily_unavailable.
error_description: A description of the error.
For additional details about OAuth2.0 error responses, see Error Response.
iOS examples
In this example, the app gets both the Alexa app URL and the LWA fallback URL.
import Alamofire
import SwiftyJSON
class AlamofireHelper {
// Your backend base URL from which you're requesting the URL
static let BASE_URL = "https://exampleBackEndURL"
/*! Get Alexa app Universal Link and LWA fallback URL
@param userAccessToken for your backend server
@param state maintained between client and server, caller should generate it
*/
static func getAlexaAppUrl(userAccessToken:String, state: String) -> DataRequest {
let alexaAppUrl = "/alexaurl"
let inputstate = "?state=" + state
var urlRequest = URLRequest(url: URL(string: BASE_URL + alexaAppUrl + inputstate)!)
urlRequest.addValue("Bearer " + userAccessToken, forHTTPHeaderField: "Authorization")
return Alamofire.request(urlRequest)
}
private init() {}
}
// Call to your backend to get the authorization request URLs (Alexa app / LWA)
AlamofireHelper.getAlexaAppUrl(userAccessToken: savedSession.accessToken, state: StateHelper.generateState()).responseJSON {
response in
guard let status = response.response?.statusCode, 200..<300 ~= status else {
self.createAlert(title: "Failed to get Alexa URL", message: "Failed to get Alexa URL")
return
}
guard let responseValue = response.result.value else {
self.createAlert(title: "Invalid response", message: "Invalid response")
return
}
let reponseInJSON = JSON(responseValue)
guard let companionApp = reponseInJSON["companionAppURL"].string,let lwaFallback = reponseInJSON["LWAFallBackURL"].string else {
self.createAlert(title: "Error response", message: "Error response")
return
}
guard let companionAppURL = URL(string: companionApp), let lwaFallbackURL = URL(string: lwaFallback) else {
self.createAlert(title: "Incorrect URL format", message: "Incorrect URL format")
return
}
// The openUniversalLinks code snippet can be found in another example
self.openUniversalLinks(companionAppURL: companionAppURL, lwaFallbackURL: lwaFallbackURL)
}
In this example, the app opens the Alexa app URL or, the LWA fallback URL if the Alexa app isn't installed.
URLs only open when there is an app (in this case, the Alexa app) that's configured to handle Universal Links with the universalLinksOnly option. You can therefore pass closure expressions in completionHandler to handle the case in which the Alexa app doesn't launch (for example, the Alexa app wasn't installed or is a version that doesn't support Universal Links).
If your app isn't able to launch the Alexa app when using the universalLinksOnly option, your app needs to get the Amazon authorization code by using LWA. For the LWA Fallback URL format, see URLs and endpoints. You can use SFAuthenticationSession on iOS 11 or ASWebAuthenticationSession on iOS 12.0 or higher. This gets the Safari user session and cookies, which avoids an extra login when the user is already logged in to Amazon.com on Safari. This will cause your app to display a window that asks the user's consent to share website information. iOS defines the content of the consent message.
For iOS versions below 11.0, this example uses SFSafariViewController to send users to the LWA page. The SFSafariViewController doesn't ask for the user's consent to share website information; the Safari browser therefore doesn't share cookies with the SFSafariViewController.
import SafariServices
// Open the Alexa URL with option universalLinksOnly
// If it doesn't open the Universal Link, open the LWA fallback
private func openUniversalLinks(companionAppURL: URL, lwaFallbackURL: URL) {
UIApplication.shared.open(companionAppURL, options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly:true]) {
companionAppLaunched in
if !companionAppLaunched {
if #available(iOS 12.0, *) {
WebSession.initWebAuthenticationSession(authURL: lwaFallbackURL)
} else if #available(iOS 11.0, *) {
WebSession.initAuthenticationSession(authURL: lwaFallbackURL)
} else {
let safariViewController = SFSafariViewController(url: lwaFallbackURL)
self.present(safariViewController, animated: true, completion: nil)}
}
}
}
// Web session
class WebSession {
@available(iOS, introduced: 11.0, deprecated: 12.0)
private static var authenticationSession: SFAuthenticationSession?
@available(iOS 12.0, *)
private static var webAuthenticationSession : ASWebAuthenticationSession?
private static let CALLBACK_URL_SCHEME = "https://yourAppsUniversalLinkURL"
@available(iOS, introduced: 11.0, deprecated: 12.0)
static func initAuthenticationSession(authURL:URL) {
if let session = authenticationSession {
session.cancel()
}
self.authenticationSession = SFAuthenticationSession.init(url: authURL, callbackURLScheme: CALLBACK_URL_SCHEME, completionHandler:{
(callBack:URL?, error:Error?) in
//Callback and error handling
})
self.authenticationSession?.start()
}
@available(iOS 12.0, *)
static func initWebAuthenticationSession(authURL:URL) {
if let session = webAuthenticationSession {
session.cancel()
}
self.webAuthenticationSession = ASWebAuthenticationSession.init(url: authURL, callbackURLScheme: CALLBACK_URL_SCHEME, completionHandler:{
(callBack:URL?, error:Error?) in
// Callback and error handling
})
self.webAuthenticationSession?.start()
}
private init(){}
}
For websites, and as a fallback for when the Alexa app isn't installed on the iOS device, open the LWA URL. In this example, the app opens LWA in a view controller.
import SafariServices
// Open Login with Amazon in Safari View controller
private func openSafariView(lwaFallbackURL: URL) {
let safariViewController = SFSafariViewController(url: lwaFallbackURL)
self.present(safariViewController, animated: true, completion: nil)
}
In this example, the app validates and extracts the parameters from the call that the Alexa app made to its Universal Link.
// Handler for Universal Links in AppDelegate
// Add this method to handle incoming Universal Links
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL else {
return false
}
let handlers : [UniversalLinkHandler] = [AuthResponseHandler(), ErrorResponseHandler()]
var validatedResponse : ValidatedResponse?
for handler in handlers {
if handler.canHandle(incomingURL: incomingURL) {
validatedResponse = handler.getValidatedResponse()
let initialViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "linkingStatusScreen") as! LinkingStatusViewController
initialViewController.response = validatedResponse
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}
}
return false
}
// Incoming Universal Links handler protocol
protocol UniversalLinkHandler {
//Validate the incoming URL scheme
func canHandle(incomingURL:URL)-> Bool
//Get the validated response
func getValidatedResponse() -> ValidatedResponse?
}
extension UniversalLinkHandler {
func validateState(state:String)->Bool {
return StateHelper.validateState(state: state)
}
}
// Successfully receiving the Amazon Authorization code
class AuthResponseHandler : UniversalLinkHandler {
private let AUTH_RESPONSE_PARAMETERS: Set = ["code", "state", "scope"]
private var validatedResponse: ValidatedResponse?
func canHandle(incomingURL: URL) -> Bool {
guard let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true),
let queryParameters = components.queryItems else {
return false
}
var validatedParameters : [String: String] = [:]
for queryParameter in queryParameters {
//duplicated parameters in URL
if validatedParameters.keys.contains(queryParameter.name) {
return false
}
//Unrecognized parameters in URL
if !AUTH_RESPONSE_PARAMETERS.contains(queryParameter.name) {
return false
}
validatedParameters[queryParameter.name] = queryParameter.value
}
guard let code = validatedParameters["code"], let state = validatedParameters["state"] else {
return false
}
//validated the state
if !validateState(state: state) {
self.validatedResponse = ErrorResponse(url: incomingURL,error: "Invalid state", errorDescription: "The request has invalid state")
return true
}
self.validatedResponse = AuthResponse(url: incomingURL,code: code, state: state, scope: validatedParameters["scope"])
return true
}
func getValidatedResponse() -> ValidatedResponse? {
return validatedResponse
}
}
// Error authorization response handler
class ErrorResponseHandler : UniversalLinkHandler {
private let ERROR_RESPONSE_PARAMETERS: Set = ["error", "error_description", "state"]
private var validatedResponse: ValidatedResponse?
func canHandle(incomingURL: URL) -> Bool {
guard let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true),
let queryParameters = components.queryItems else {
return false
}
// Validate all query parameters
var validatedParameters : [String: String] = [:]
for queryParameter in queryParameters {
// Duplicated parameters in URL
if validatedParameters.keys.contains(queryParameter.name) {
return false
}
// Unrecognized parameters in URL
if !ERROR_RESPONSE_PARAMETERS.contains(queryParameter.name) {
return false
}
validatedParameters[queryParameter.name] = queryParameter.value
}
guard let error = validatedParameters["error"], let errorDescription = validatedParameters["error_description"], let state = validatedParameters["state"] else {
return false
}
// validate the state
if !validateState(state: state) {
self.validatedResponse = ErrorResponse(url: incomingURL,error: "Invalid state", errorDescription: "The request has invalid state")
return true
}
self.validatedResponse = ErrorResponse(url: incomingURL, error: error, errorDescription: errorDescription)
return true
}
func getValidatedResponse() -> ValidatedResponse? {
return validatedResponse
}
}
// Validated response
class ValidatedResponse {
// Universal Link URL that launched the App
private(set) var url: URL
init (url: URL) {
self.url = url
}
}
// Amazon authorization code response per the OAuth2 specification
class AuthResponse : ValidatedResponse{
private(set) var code : String
private(set) var state: String
//scope will be returned in Login with Amazon, will not be present in Alexa Companion flow
private(set) var scope: String?
init(url:URL, code:String, state:String, scope: String?) {
self.code = code
self.state = state
self.scope = scope
super.init(url:url)
}
}
// Error response per the OAuth2 specification
class ErrorResponse : ValidatedResponse {
private(set) var error : String
private(set) var errorDescription: String
init(url: URL, error:String, errorDescription:String) {
self.error = error
self.errorDescription = errorDescription
super.init(url: url)
}
}
Android examples
Make a request to your backend service to get both the Alexa app URL and the LWA fallback URL. You can implement this request in any way that you prefer, such as using RetroFit or any other Android library. The following code is a simple example.
fun doAppToApp(){
val appToAppUrls: AppToAppUrls = yourBackendService.getAppToAppUrls();
// This function is shown in a different example
openAlexaAppToAppUrl(appToAppUrls.alexaAppUrl, appToAppUrls.lwaFallBackUrl)
}
data class AppToAppUrls(
@SerializedName("alexaAppUrl")
val alexaAppUrl: String,
@SerializedName("lwaFallBackUrl")
val lwaFallBackUrl : String
)
interface BackendService {
fun getAppToAppUrls() : AppToAppUrls
}
In this example, the app opens the Alexa app URL or, if the Alexa app is not installed, the LWA fallback URL.
Note the following:
This example uses the PackageManager to verify that the Alexa app is installed and to verify that the installed version of the Alexa app can handle app-to-app account linking.
If the Alexa app isn't installed or can't handle app-to-app account linking, your app must get the Amazon authorization code by using the LWA flow. For the LWA fallback URL format, see URLs and endpoints.
This example opens the URLs by using the Android ACTION_VIEW intent. The app-to-app URL will open in the Alexa app and the LWA fallback URL will open in the user's default browser.
private fun openAlexaAppToAppUrl(alexaAppUrl: String, lwaFallbackUrl: String){
if (AlexaAppUtil.isAlexaAppSupportAppLink(fragmentContext!!)) {
val alexaAppToAppIntent = getAppToAppIntent(alexaAppUrl);
startActivity(alexaAppToAppIntent)
} else {
val lwaAppToAppIntent = getAppToAppIntent(lwaFallbackUrl);
startActivity(lwaAppToAppIntent)
}
}
private fun getAppToAppIntent(appToAppUrl: String): Intent {
return Intent(Intent.ACTION_VIEW, Uri.parse(appToAppUrl))
}
/**
* Utility to check if the Alexa app is installed and supports app-to-app.
*/
object AlexaAppUtil {
private const val ALEXA_PACKAGE_NAME = "com.amazon.dee.app"
private const val ALEXA_APP_TARGET_ACTIVITY_NAME = "com.amazon.dee.app.ui.main.MainActivity"
private const val REQUIRED_MINIMUM_VERSION_CODE = 866607211
/**
* Check if the Alexa app is installed and supports App Links.
*
* @param context Application context.
*/
@JvmStatic
fun doesAlexaAppSupportAppToApp(context: Context): Boolean {
try {
val packageManager: PackageManager = context.packageManager
val packageInfo = packageManager.getPackageInfo(ALEXA_PACKAGE_NAME, 0)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
packageInfo.longVersionCode > REQUIRED_MINIMUM_VERSION_CODE
} else {
packageInfo != null
}
} catch (e: PackageManager.NameNotFoundException) {
// The Alexa App is not installed
return false
}
}
}
In this example, the app validates and extracts the parameters from the call that the Alexa app made to its App Link.
/**
* App-to-app result page landing activity.
*/
class AppToAppResultPageActivity : AppCompatActivity() {
private lateinit var statusText: TextView
private lateinit var goBackButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val appLinkData = intent.data
setContentView(R.layout.app_to_app_result)
initializeComponents()
statusText.text = "Linking your account"
// Send the data returned from the Alexa App / LWA to your backend
// to call the Skill Enablement API to link the accounts
val accountLinking = linkAccountInBackend(appLinkData)
displayAccountLinkingStatus(result);
}
private fun initializeComponents() {
statusText = findViewById(R.id.text_status)
goBackButton = findViewById(R.id.btn_goback)
goBackButton.setOnClickListener {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}
}
State generation and validation
In this example, the app generates and validates the state between the request and the response. You must validate incoming requests by using the state to prevent cross-site request forgery.
import Foundation
import Security
class StateHelper {
private static let STATE_VALID_FOR = 3600
// Base64 (iOS + TimeStamp + Secure Random UUID)
private static let NUMBER_OF_PARAMETER = 3
static func generateState(session: Session)-> String {
var buffer = Data(count: 30)
let _ = buffer.withUnsafeMutableBytes {
SecRandomCopyBytes(kSecRandomDefault, 30, $0)
}
let state = ("iOS." + String(Int64(Date().timeIntervalSince1970)) + "." + buffer.base64EncodedString()).encodeToURISafeBase64()
// store state in session manager for future validation
session.state = state
LocalSessionManager.saveSession(session: session)
return state
}
static func validateState(state: String) -> Bool {
guard let originalState = LocalSessionManager.loadSession()?.state else {
return false
}
if state != originalState {
return false
}
if let timeStampString = originalState.decodeFromURISafeBase64()?.split(separator: "."), timeStampString.count == NUMBER_OF_PARAMETER, let timeStamp = Int64(timeStampString[1]){
if Int64(Date().timeIntervalSince1970) - timeStamp <= STATE_VALID_FOR {
return true
}
}
if let session = LocalSessionManager.loadSession() {
session.state = nil
LocalSessionManager.saveSession(session: session)
}
return false
}
private init() {}
}
extension String {
func decodeFromURISafeBase64() -> String? {
let base64String = String(self.map {
// replace unsafe characters
character in
if character == "." {
return "+"
} else if character == "_" {
return "/"
} else if character == "-" {
return "="
}
return character
})
guard let data = Data(base64Encoded: base64String) else {
return nil
}
return String(data: data, encoding: .utf8)
}
func encodeToURISafeBase64() -> String {
return String(Data(self.utf8).base64EncodedString().map{
// replace back unsafe characters
character in
if character == "+" {
return "."
} else if character == "/" {
return "_"
} else if character == "=" {
return "-"
}
return character
})
}
}
Step 5: Exchange the Amazon authorization code for an Amazon access token
After your app receives the user's Amazon authorization code, you use it to get the Amazon access token. You need the access token to call the Alexa service to enable the skill and complete account linking. If your app backend supports PKCE, include the code verifier parameter in the query string.
Step 6: Enable the skill and complete account linking
Now that your app has the user's Amazon access token, your backend server can call the Alexa Skill Enablement API to enable the skill and complete account linking.
Make a request to the Alexa Skill Enablement API by using the following URL format: [BASE URL]/v1/users/~current/skills/{yourSkillId}/enablement, where [BASE URL] depends on the region where your user is located: https://api.amazonalexa.com, https://api.eu.amazonalexa.com orhttps://api.fe.amazonalexa.com.
The security provider that you use must authorize calls from the app to the app's backend server and provide a server-side API to get the authorization code for the user who is currently logged in to your app. (That is, it needs to provide the authorization code for the user's account in your service.) Alexa will exchange this code for your user's access token to access the user's resources.
• If you use LWA as your security provider, keep in mind that the LWA client ID that you use to obtain the user authCode is different than the Alexa client ID that the Alexa developer console displays when you configure your skill for app-to-app account linking. You can't use the Alexa client ID that you use to obtain the Amazon access token (Step 5) to generate an authCode for your user. To generate a new user authCode, use the LWA client ID associated with the security profile for LWA.
• If you have a smart home skill and you have enabled the Send Alexa Events permission for the skill, your Lambda function must handle the AcceptGrant directive. Otherwise, account linking fails when the user attempts to enable your skill. For details, see Request Access to the Alexa Event Gateway. Note that in general, sending events to the events gateway continues to work the way it works today. That is, after a skill is enabled, the skill can get the access token from the directive.
To enable the skill, make the following request:
Request
In the request shown next, set HOST to one of the following, depending on the user's region: api.amazonalexa.com, api.eu.amazonalexa.com, or api.fe.amazonalexa.com
Note that you can disable the skill and unlink the account for the user by making a Disable skill and unlink account request to the Alexa Skill Enablement API.
In this example, the app enables the skill and completes account linking.
const _ = require('lodash');
const axios = require('axios');
const {config} = require('../config/skillconfig');
const {getAccessTokenByAuthcode} = require('./oauthrequests');
// Request to the Alexa Skill Enablement API in the backend
const AlexaServiceRequest = (() => {
const header = (amazonAccessToken) => {
return {
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer " + amazonAccessToken
}
};
};
const body = (userAuthCode) => {
return {
"stage": config.skillConfig.stage,
"accountLinkRequest": {
"redirectUri": config.skillConfig.redirect_uri,
"authCode": userAuthCode,
"type": "AUTH_CODE"
}
}
}
return {
createEnablement: (amazonAccessToken, userAuthCode) => {
return {
header: header(amazonAccessToken),
body: body(userAuthCode)
}
}
}
})();
function createEnablementWithAccountLink(amazonAuthCode, userAuthcode, state) {
// Exchange Amazon OAuth Code for Amazon Access Token
const tokenPromise = getAccessTokenByAuthcode(amazonAuthCode, state);
return tokenPromise.then((res) => {
if (!_.has(res, 'data.access_token')) {
throw Error("Amazon AccessToken is invalid");
}
console.log(res);
const amazonAccessToken = res.data.access_token;
// Call the Alexa Skill Enablement API to create enablement
const createEnablementRequest = AlexaServiceRequest.createEnablement(amazonAccessToken, userAuthcode);
return sendPostRequests(config.endpoints.alexaServiceEndpoints, createEnablementRequest.body, createEnablementRequest.header);
});
}
// Post enablement to the Alexa Skill Enablement API
function sendPostRequests(endpoints, body, header) {
var alexaServicePromises = []
// Post for each Alexa Skill Enablement API regional endpoint for the user
endpoints.forEach((endpoint)=> {
alexaServicePromises.push(axios.post(endpoint, body, header));
});
return new Promise((resolve, reject)=> {
var failures = 0;
alexaServicePromises.forEach((promise)=> {
promise.then((res)=> {
if (res.status == 201) {
resolve(res.data);
} else {
if (++failures == alexaServicePromises.length) {
reject(res.data);
}
}
}).catch((err)=> {
if (++failures == alexaServicePromises.length) {
reject(err.data);
}
})
})
});
}
Step 7: Display the account linking status in your app
Let the user know the account linking status by displaying it in your app, such as in the screenshots in User experience and Errors.
You can use different UI components — for example, a table or page view — to show the account linking status.
In this example, the app displays the account linking status using a UIViewController.
In your Xcode IDE, go to the main storyboard to add a new view controller. The storyboard ID for this view controller is "linkingStatusScreen".
Add a new Cocoa Touch class file, select a subclass of UIViewController, and connect your class file with the UIViewController in identity inspector.
Add a header, a status UILabel to show the account linking status, a UIButton to close this page, and a UIActivityIndicatorView to show the loading animation while your app is calling the Alexa service.
import UIKit
class LinkingStatusViewController: UIViewController {
@IBOutlet weak var header: UILabel!
@IBOutlet weak var status: UILabel!
@IBOutlet weak var closeButton: UIButton!
@IBOutlet weak var indicator: UIActivityIndicatorView!
var response : ValidatedResponse?
override func viewDidLoad() {
super.viewDidLoad()
if let res = self.response {
passValidatedResponse(validatedResponse: res)
}
}
private func updateView(header:String, status:String, animating: Bool,closeButton:Bool) {
if animating {
self.indicator?.startAnimating()
} else {
self.indicator?.stopAnimating()
}
self.indicator?.isHidden = !animating
self.header?.text = header
self.status?.text = status
self.closeButton.isEnabled = closeButton
if closeButton {
self.closeButton.backgroundColor = #colorLiteral(red: 0.1960784314, green: 0.6039215686, blue: 0.8392156863, alpha: 1)
} else {
self.closeButton.backgroundColor = #colorLiteral(red: 0.6000000238, green: 0.6000000238, blue: 0.6000000238, alpha: 1)
}
}
}
extension LinkingStatusViewController {
func passValidatedResponse(validatedResponse: ValidatedResponse) {
if validatedResponse is AuthResponse {
let response: AuthResponse = validatedResponse as! AuthResponse;
updateView(header: "Status", status: "Loading", animating: true, closeButton: false)
usingAuthCodeToGetAccessToken(amazonAuthCode: response.code)
} else if (validatedResponse is ErrorResponse) {
let response: ErrorResponse = validatedResponse as! ErrorResponse;
updateView(header: response.error, status: response.errorDescription, animating: false, closeButton: true)
} else {
updateView(header: "Unknown request", status: validatedResponse.url.absoluteString, animating: false, closeButton: true)
}
}
// Complete account linking
private func usingAuthCodeToGetAccessToken(amazonAuthCode: String) {
guard let session = LocalSessionManager.loadSession(), session.sessionValid else {
updateView(header: "Status", status: "User not login", animating: false, closeButton: true)
return;
}
AlamofireHelper.completeAccountLinking(userAccessToken: LocalSessionManager.loadSession()!.accessToken, amazonAuthCode: amazonAuthCode, state: StateHelper.generateState(session: session)).responseJSON(queue: DispatchQueue.global(qos: .userInitiated), options: []) {
response in
if let status = response.response?.statusCode, 200..<300 ~= status {
DispatchQueue.main.sync {
self.updateView(header: "Status", status: "Account Linking Succeed", animating: false, closeButton: true)
}
return
} else {
DispatchQueue.main.sync {
self.updateView(header: "Failed", status: "Account Linking failed", animating: false, closeButton: true)
}
}
}
}
}
// Open the account linking status page
let initialViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "linkingStatusScreen") as! LinkingStatusViewController
initialViewController.response = validatedResponseself.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
Step 8: Use the access token in the skill
After a user successfully enables your skill and links Alexa with your service, requests sent to your skill include the user's access token. Your skill code needs to get the access token from the request, validate it, and use it to retrieve the necessary user information from your resource server.
For details about how to validate and use the access token, see the skill type:
When an error occurs during account linking or skill enablement fails, your app should show the appropriate error message to the user. The following image shows one example where the app asks the user to try again later.
Alexa app-to-app account linking error
Error descriptions
When an error occurs during app-to-app account linking, your app must inform the user that account linking failed, and provide the appropriate error message to the user. Review the following error messages and possible causes of the errors. For the best user experience, use the error messages provided.
Errors often occur during the following points in the app-to-app account linking flow:
Errors when obtaining the Amazon authorization code
If an error occurs when your app tries to get the Amazon authorization code, the redirect_uri that the Alexa app uses to redirect the user back to your app will include an error query parameter, as described by the OAuth 2.0 specification. The Alexa app and the LWA fallback both follow the OAuth 2.0 specification, so they both return the same error codes to your app. For details about why the request to get the Amazon authorization code failed, you can check the error_description query parameter and the OAuth 2.0 specification description for that error code.
Error code
Message to show to user
Possible causes
invalid_request
We are experiencing a problem connecting with Alexa to link your account. Please try again later.
Request is missing state, scope, and/or response_type
unauthorized_client
We are experiencing a problem connecting with Alexa to link your account. Please try again later.
The provided client_id doesn't have access to request consent to link the user's account
access_denied
None. (The user shouldn't see an error message because they were the one who denied the access.)
The user rejected the request to link their account
unsupported_response_type
We are experiencing a problem connecting with Alexa to link your account. Please try again later.
Parameter response_type isn't equal to code
invalid_scope
We are experiencing a problem connecting with Alexa to link your account. Please try again later.
Invalid scope parameter
server_error
Sorry, Alexa encountered an unexpected error while trying to link your account. Please try again.
Unexpected server error
temporarily_unavailable
Sorry, Alexa encountered a momentary error while trying to link your account. Please try again later.
Temporary server error
Errors when exchanging the Amazon authorization code for an Amazon access token
If an error occurs exchanging the Amazon authorization code for the Amazon access token, the Amazon token server responds with HTTP Status 400 and the JSON response body includes an error parameter, as described by the OAuth 2.0 specification. For details about why the request to obtain the Amazon access token failed, you can check the error_description parameter of the JSON response and the OAuth 2.0 specification description for that error code.
Error code
Message to show to user
Possible causes
invalid_request
We are experiencing a problem connecting with Alexa to link your account. Please try again later.
We are experiencing a problem connecting with Alexa to link your account. Please try again later.
Invalid client_id and/or client_secret
invalid_grant
We are experiencing a problem connecting with Alexa to link your account. Please try again later.
The request's code (Amazon authorization code) or the code verifier isn't valid.
unauthorized_client
We are experiencing a problem connecting with Alexa to link your account. Please try again later.
The provided client_id doesn't have permission to request consent to link the user's account
unsupported_grant_type
We are experiencing a problem connecting with Alexa to link your account. Please try again later.
The request's grant_type isn't authorization_code
Errors when calling the Alexa Skill Enablement API
If an error occurs when your app calls the Alexa Skill Enablement API to enable and link the user's account, the API will respond with a status code that says why the request failed. Otherwise, the API will respond with a HTTP 2XX status code. For details about why the skill activation request failed, you can check the message parameter of the JSON response.
HTTPS status code
Message to show to user
Possible causes
400 Bad Request
We are experiencing a problem connecting with Alexa to link your account. Please try again later.
Error requesting your app's access and refresh token pair from your app's token server
403 Forbidden
We are experiencing a problem connecting with Alexa to link your account. Please try again later.
The Amazon access token is invalid
The Amazon access token doesn't have the user's consent to enable the skill
The Amazon access token doesn't belong to the skill_id provided in the request
The user isn't entitled to enable the skill with the specified skill_id provided in the request
404 Not Found
We are experiencing a problem connecting with Alexa to link your account. Please try again later.
Invalid skill_id and/or stage
500 Server Error
Sorry, Alexa encountered an unexpected error while trying to link your account. Please try again.
Unexpected server error
Problem linking accounts with your app's OAuth server
Best practices
When you implement app-to-app account linking, keep the following best practices in mind.
Discoverability of app-to-app account linking
To streamline app-to-app account linking from your app, follow these best practices.
Use the least number of clicks
Keep the number of clicks required to start the account-linking flow as low as possible. The user shouldn't need more than three clicks to get from your app's home screen to the account linking flow. Starting from the home screen on your app, the following examples show common flows.
Example flow 1
The user:
Clicks your app's burger menu.
Clicks Link with Alexa.
Clicks Allow to start the account linking flow from your app.
Gives their account-linking consent inside the Alexa app.
Gets redirected to your app.
Example flow 2
The user:
Clicks your app's burger menu.
Clicks Third-Party Integrations. (or similar wording)
Clicks Link with Alexa.
Clicks Allow to start the account linking flow from your app.
Gives their account-linking consent inside the Alexa app.
Gets redirected to your app.
Surface the Alexa integration
Make the user aware of the Alexa integration the first time they use your app. For example, at the end of your application set-up experience, implement an additional screen, with an Alexa logo, that directs users toward your app-to-app account linking flow.
If the user clicks the Alexa logo, make sure that your app redirects them to the app-to-app account linking flow. If the user wants to skip the linking process, you can take them to a screen in the menu structure of your app where they can find the linking flow later.
Review your voice assistant settings
If your app has settings related to voice assistants, make sure that account-linking initiation is accessible from that area.
Usability
To optimize the app-to-app account linking experience, follow these best practices.
Display the correct account linking status
To display the correct Link versus Unlink button on your app, you can check for skill enablement and account linking status for a given user by making a Get account linking and skill status request to the Alexa Skill Enablement REST API. However, this operation gives you a successful response only for users who linked their account using app-to-app account linking. Therefore, Amazon recommends that you also subscribe to the Skill Disabled Event to get notified when a user disables your skill and thereby unlinks their account. You can't rely on the Alexa token pair being present in your backend to determine if a user has linked their account.
Consider the entire app-to-app account linking experience, and provide clear messaging to the user for all user flows. Gracefully handle cases in which the user cancels the account linking experience mid-flow, times out, and so on. Provide clear information about what went wrong.
Get all required information before redirecting the user
Before redirecting the user to the Alexa app or LWA to link their account, ask the user for any other information you require for account linking, such as additional details, consent, a PIN, and more.
If the user's request includes a financial transaction or involves personal information, ask the user to answer a previously defined security question before fulfilling their request.
Follow brand usage guidelines
When designing your app's user interface, follow the Alexa Brand Guidelines for Amazon Developers. This helps make it clear to users, at a glance, that they are being asked to link their account to use your service with Alexa.
Unlinking
Follow these best practices to implement accounts unlinking and user notifications.
Implement unlinking within your app
You must give your users an option to unlink their account from within your app. To do so, you must persist the Amazon access and refresh token pair that your app received during the app-to-app account linking flow to enable future unlinking requests within your app. You can disable the skill and unlink the account for the user by making a Disable skill and unlink account request to the Alexa Skill Enablement API.
Note: For the Disable skill and unlink account operation, smart home skills can use the token that they received from the AcceptGrant directive.
For previously linked users who didn't use app-to-app account linking, use the Unlink button to send the user to your skill's page with a Disable skill option within the Alexa app. The link on your skill's page in the Alexa app will look similar to the following examples:
Fallback (if the Alexa app is not installed): Redirect your users to https://www.amazon.com/dp/{skillAsin}
To find the Amazon Standard Identification Number (ASIN) for your skill, browse to your skill in the Alexa Skills Store. The ASIN is the 10-digit alphanumeric ID in the URL. For example, in the URL www.amazon.com/gp/product/A1A1A1A1A1, the ASIN is A1A1A1A1A1.
Notify unlinked users
Inform unlinked users about your account linking feature. There are a number of options for communicating with your user base, including push notifications, "what's new" pop-up
screens after updating the app to a new version, release notes in app stores, blog posts, or
emails.
Back-end code
Follow these best practices for server-side code to support app-to-app account linking.
Assemble the URLs in the backend
In your code to Get the user's Amazon authorization code, Amazon recommends that you assemble the Alexa app URL and LWA fallback URL in your back-end server, and then pass the assembled URLs to your app. This implementation enables you to quickly change parameters, such as the stage, without rebuilding your app.
Verify that your redirect URI works
Sometimes, LWA doesn't trigger Universal Links and App Links when it sends back the LWA auth_code in Step 4. To avoid losing the user's intent to link their account, make sure that your redirect_uri is able to fulfill the account linking request when it's opened inside the same web view as the LWA authorization page. To achieve this functionality, send the auth_code that your web page receives in the redirect_uri from Step 4 to your backend server to perform Step 5 and Step 6. Finally, display a success page (Step 7) directly on your web page.
Testing guidelines
In addition to making sure that your skill meets the certification requirements that apply to all skills, test the following account linking flows:
When the Alexa app is installed, you're able to complete account linking when initiated from your app using the Alexa app flow. On Android, if you see a Use a different app selector when opening the Alexa app URL, it means that your version of the Alexa app has invalid App Links. To fix this, delete and re-install the Alexa app.
When the Alexa app isn't installed, you're able to complete account linking when initiated from your app or website using the LWA flow. If your implementation doesn't support the LWA flow, your app or website provides a graceful error message to the user.
When account linking fails, your app or website provides a graceful error message to the user.
After you complete account linking, you should be able to unlink the account using your app. You should also see the correct button (Link versus Unlink) depending upon the status of your account linking.
Until your skill is published, you must set the stage value to development when you use the Alexa app URL and the Alexa Skill Enablement API. When your skill is published, set the stage to live.
Q: I own more than one skill. Can I link accounts for all the skills at one time?
To protect user trust, app-to-app account linking requires user consent for a specific skill. If you want to link a user's account for more than one Alexa skill, you must take the user through the linking flow for each skill.
Q: How do I check if the skill is already enabled and the account is already linked?
You make a Get account linking and skill status request to the Alexa Skill Enablement API. When you call this API, you must use one of the following access tokens:
The token that you received when you set up app-to-app account linking for the customer.
(Smart home skills only) The token that you received from the AcceptGrant directive after mutual authentication.
Q: What if the skill is already enabled and the account is already linked?
When you make an Enable skill and account link request to the Alexa Skill Enablement API, the user's account is relinked and you receive a HTTP 201 Created response.
Q: How can I get notified when account linking is complete?
Q: What if a user wants to link Alexa with a different user account than the account that they are currently logged in with in your app?
If a user wants to link Alexa with a different account, they should first log in to your app with the other account and then initiate account linking from within your app. Your app can choose to provide an option to switch accounts within the account-linking flow.
Q: What if a user isn't an existing Alexa user and they don't have the Alexa app installed?
If a user has an Amazon account but isn't an Alexa user, they can sign in to the LWA page with their Amazon credentials and then acknowledge the linking request. If a user doesn't have an Amazon account, they can create a new Amazon account on the LWA screen and complete account linking for the skill. To use Alexa features, the user needs an Alexa-enabled device or Alexa app.
Q: How does a user revoke account linking?
Disabling the skill unlinks the user's account. As with any skill, a user can say, "Alexa, disable {skill name}" or disable the skill from the skill's detail page. You can disable the skill and unlink the account for the user by making a Disable skill and unlink account request to the Alexa Skill Enablement API.
Q: What happens when a user disables my skill? Am I notified?
To get notified when a user disables your skill, you can subscribe to skill disabled events. When the skill is disabled, you should revoke your access token and delete the Amazon access token for the user. Alexa also deletes the access token that your service provided, and revokes the Amazon access token.
Q: What if my skill requires permissions? Can I get these during app-to-app account linking?
Permissions (such as device address and user profile information) are currently not part of this account linking flow. When the user invokes the skill with an intent that needs permissions, you must prompt the user, through a permissions card, for the respective permissions during runtime.
However, when a user links their account through the other app-to-app account linking flow, App-to-App Account Linking (Starting From the Alexa App), they are prompted to grant permissions. You can grant the required permissions by using that flow.
Q: How do I know if the Alexa app isn't installed on a mobile device?
Q: How do I retrieve a user's authorization code for their account in my service?
You're required to generate the user's authorization code by using your authorization server. In the traditional account linking flow, the OAuth server generates the authorization code when the user logs in to your service and provides consent to link with Alexa. In app-to-app account linking (starting from your app) where the user is already logged in, you programmatically generate a new authorization code for the user from your OAuth provider. You must not require the user to log in and provide consent again.