App-to-App Account Linking (Starting From Your App)
Note: Feature support for this account linking flow is as follows: • Launching the Alexa app from your app – iOS and Android • Login with Amazon – Web and as a fallback for iOS and Android
This account linking flow enables users to link their Alexa user identity with their identity in another service by starting from your app or website. When you start the account linking flow from your app, your users can:
Discover your Alexa skill through your app
Initiate 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 apps on their mobile device
Link their account from your app using Login with Amazon (LWA), when the Alexa app isn't installed on their mobile device
Your other options for implementing the account linking flow are the following:
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.
If you have an app or website, we encourage you to 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 discussion assumes that you are 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.
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 Alexa app is installed on the mobile device.
(Alexa app flow) If the Alexa app is installed on the 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 Alexa app isn't installed on the 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 are example screenshots for app-to-app account linking. As mentioned previously, the flow depends on whether the user has the Alexa app installed.
The following is an example of the Alexa app flow.
Alexa app isn't installed
The following is an example of the LWA flow.
Which flow to implement
The Alexa app flow is available for iOS and Android. As such, the flow that you implement depends on whether you are developing an iOS app, an Android app, or a website:
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 figure shows what goes on behind the scenes when the user starts account linking from your app on their mobile device. (Click image to enlarge.)
The steps are as follows. For details about the URLs mentioned, see URLs and endpoints.
The user installs your app on their mobile device, and logs in to your app.
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"). The user acknowledges the linking request.
What happens next depends on whether the Alexa app is installed on the user's device.
The Alexa app launches and asks the user if they want to link Alexa with your service.
The user acknowledges the linking request.
The Alexa app sends the user back to your app using your redirect URLs, and sends the user's Amazon authorization code as a part of that redirect.
If the Alexa app isn't installed:
Your app launches LWA using the LWA fallback URL in an in-app browser tab (not a native browser app) with the authorization request parameters described later.
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.
LWA sends the user back to your app using your redirect URLs, and sends the user's Amazon authorization code as a part of that redirect.
Your backend server calls the LWA authorization service URL and exchanges the Amazon authorization code it retrieved in the previous step for an Amazon access token.
Your backend server calls your authorization server to get the user's authorization code (for their account in your service).
Alexa goes 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.
URLs and endpoints
This section describes the URLs and endpoints that relate to app-to-app account linking. A description of the parameters follows.
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.
A link that sends the user to LWA to enter their Amazon credentials. It works for iOS, Android, and websites. Your app uses this if 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.
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 of these using the developer console, the ASK CLI, or SMAPI. See the required syntax in the 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 using the developer console, the ASK CLI, or SMAPI.
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 this using the developer console, the ASK CLI, or SMAPI.
LWA authorization service
Your backend server uses this to exchange the user's Amazon authorization code for an Amazon access token. This 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 Activation API endpoint
Your backend server calls this to enable the skill for the user. For details about the Alexa Skill Activation API, see Alexa Skill Activation API.
Format: https://api.amazonalexa.com/v1/users/~current/skills/{yourSkillId}/enablement
Depending on where the user is located, the base URL could be https://api.amazonalexa.com, https://api.eu.amazonalexa.com, or https://api.fe.amazonalexa.com.
Parameters for the Alexa app URL and the LWA fallback URL
Field
Description
Client ID
The client ID that the developer console provides when you enable app-to-app account linking in the developer console.
Client secret
The client secret that the developer console provides when you enable app-to-app account linking in the developer console.
Redirect URL
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 they acknowledge the account linking request.
Scope
Your app must set this to alexa::skills:account_linking to have 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, which depends on whether your skill is published. Until your skill is published, set stage to development. When your skill is published, use live.
response type
The response type must be code, because app-to-app account linking currently supports authorization code grants only.
state
An opaque value that your app uses to maintain state between the current 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 using your redirect URLs. You must validate the 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 secure random number generator, and save it to your user session for validation. Alternatively, you can generate the state from your backend server and using a key to encrypt and decrypt it.
Key steps
This section describes key steps, and provides code examples for an iOS app (10.0 or higher) and an Android app by using the following libraries and languages:
Step 1: Display an account linking option in your app
Your app needs to have a screen from which a user can initiate skill enablement and account linking. This page 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 initiate 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, you can provide them with a button on a "Link to Alexa" page in your app's settings page, and so on. In any case, 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 will get 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.
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 SMAPI as described in the examples next. If you are just getting started with account linking for Alexa skills, see Understand Account Linking.
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.
If you have previously configured account linking for your skill, 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 it is 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 notes when you fill in the field called Your Redirect URLs:
For Your Redirect URLs, add the redirect URLS described in URLs and endpoints.
The redirect URLs must comply with the specified URI syntax. The Alexa app will open 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 does not 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.
Allow users to enable skill without account linking – Choose Y or N.
Authorization URL – Enter the URL of your authorization server, which is used in regular account linking. 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.
Client ID – Enter the identifier that your log-in page will use to recognize that the request came from your skill.
Scopes (separated by commas) – Enter strings that indicate the access that you need for the user account, such as the user ID. For smart home skills, this field is required. You can specify up to 15 scopes.
Domains (separated by commas) – Enter a list of additional domains that your log-in page gets content from. You can specify up to 15 domains.
Authorization Grant Type – Choose AUTH_CODE.
Access Token URI – Enter the Access token URL described in URLs and endpoints.
Client Secret – Enter a credential that lets the Alexa service authenticate with the Access Token URI. This is combined with the Client ID to identify the request as coming from Alexa.
Client Authentication Scheme – Choose HTTP_BASIC or REQUEST_BODY_CREDENTIALS.
(Optional) Default Access Token Expiration Time in Seconds – Optionally enter the time in seconds for which the access token is valid.
(Optional) Reciprocal Access Token URL – Optionally enter the URI that will be invoked with authorization codes that can be exchanged for Alexa access tokens.
(Optional) RedirectUrls for App-to-App Account Linking – Enter Your app's redirect URLs described in URLs and endpoints. This setting is required for app-to-app account linking. Remember to add redirect URLs for your iOS app, Android app, and website.
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 information, 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.
String
authorizationUrl
The URL of your authorization server, which 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.
String
domains
A 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 your 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 required. You can specify up to 15 scopes.
Array of String
accessTokenUrl
The URI for requesting authorization tokens. Required only when type is AUTH_CODE.
String
clientSecret
A credential that you provide that lets the Alexa service authenticate with the Access Token URI. This is combined with clientId to identify the request as coming from Alexa.
String
accessTokenScheme
The type of authentication used. Examples are HTTP_BASIC, or REQUEST_BODY_CREDENTIALS. For app-to-app account linking this is required, because the authorization grant type is AUTH_CODE.
String
redirectUrls
Universal Links or App Links to launch your app. Redirect URLs must comply with the specified URI syntax.
Array of String
The following is an example of a request to add redirect URLs using SMAPI.
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 will enable the app to enable the skill and complete account linking. The Amazon authorization code is valid for 5 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, we strongly encourage 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. We highly recommend 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 will be sent 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 goes to your web page, they might have to log in so that you can get the authorization code from your service.
Success response of the HTTP GET request for the Alexa app URL
In the case of success, the response is a redirect URL that includes the user's Amazon authorization code, and other parameters shown next. To handle this, see the Apple guidelines on Supporting Universal Links in Your App and Android guidelines in Handling Android App Links.
// 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
In the case of an 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, if the Alexa app is not installed, the LWA fallback URL.
URLs will only open when there is an app (in this case, the Alexa app) that is 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 does not launch (for example, the Alexa app was not installed or is a version that doesn't support Universal Links).
If your app is not 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. The content of that message is defined by iOS.
For iOS versions below 11.0, this example uses SFSafariViewController to send users to the LWA page. The SFSafariViewController does not ask for the user's consent to share website information; the Safari browser therefore does not 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(){}
}
Open the LWA URL in the case of websites, and as a fallback for when the Alexa app isn't installed on the iOS device. 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 Activation 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 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 need to 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.
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 Activation API to enable the skill and complete account linking.
Make a request to the Alexa Skill Activation API 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 Authenticate a Customer to Alexa with Permissions. 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 DELETE request to the Alexa Skill Activation 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 Activation 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 Activation 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 Activation API
function sendPostRequests(endpoints, body, header) {
var alexaServicePromises = []
// Post for each Alexa Skill Activation 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:
This section describes possible errors that can occur when account linking or skill enablement fails. When an error occurs, your app should show the appropriate error message to the user. The following image shows one example; this section lists others as well.
Error descriptions
This section describes error scenarios that can occur when a user tries to initiate app-to-app account linking. Your app must inform the user that account linking failed, and provide the appropriate error message to the user. This section provides error messages and gives possible causes of the errors. For the best user experience, we encourage you to use the error messages we provide here.
There are three points in the app-to-app account linking flow where your app might receive an error from Alexa:
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 will respond with a 400 Bad Request status code and the body's JSON response will include 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) is invalid
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 Activation API
If an error occurs when your app calls the Alexa Skill Activation 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 200 OK. 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.
Prior to account linking – Before redirecting the user to the Alexa app or LWA, ask the user for any other information you require for account linking (additional details, consent, a PIN, and so on).
Security – 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.
URL generation – In your code to Get the user's Amazon authorization code, we highly recommend that you assemble the Alexa app URL and LWA fallback URL in your backend server, and then pass the assembled URLs to your app. This enables you to quickly change parameters (such as the stage) without rebuilding your app.
Resilience – On some occasions, 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, we recommend that you ensure that your redirect_uri is able to fulfill the account linking request if 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, we recommend that you test the following aspects:
When the Alexa app is installed, you are able to complete account linking when it is initiated from your app using the Alexa app flow.
When the Alexa app isn't installed, you are able to complete account linking when it is 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.
Note that, until your skill is published, you must set the stage value to development when you use the Alexa app URL and the Alexa Skill Activation API. When your skill is published, use live.
Frequently asked questions
The following are frequently asked questions about the app-to-app account linking flow that starts from your app.
Q: How do I know if the Alexa app isn't installed on the user's mobile device?
Q: I own more than one skill. Can I link accounts for all of them at once?
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 more than once.
Q: How do I check if the skill is already enabled and the account is already linked?
You make a GET request to the Alexa Skill Activation API.
Q: What if the skill is already enabled and the account is already linked?
In this case, when you make a POST request to the Alexa Skill Activation API, the user's account will be relinked and you will receive a successful 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 simply 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. The user, however, will need an Alexa-enabled device or Alexa app in order to use Alexa features.
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 DELETE request to the Alexa Skill Activation 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'll need to prompt users, 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?