This topic describes how to develop an account linking service that allows users to navigate from your app to the Alexa app to link their accounts and enable skills.
Background information
To use an Alexa-enabled device to control devices in your app, users must link their accounts. They can navigate from your app to the Amazon Alexa voice platform to log on. You can develop an App-to-App Account Linking service to link accounts, retrieve skills, and enable them directly from your app. This service saves users from the need to manually find the skill in the Alexa app and enter their account credentials.
Overview
You can develop an App-to-App Account Linking service to allow users to link to their Alexa account and obtain skills directly from your app. You can open the link in one of two ways:
Start the Alexa app from your iOS or Android application.
Use Login with Amazon as a fallback for iOS and Android.
App-to-App Account Linking allows a user to start from your app or website to link their Alexa user identity with their identity in another service. When you start App-to-App Account Linking from your app, your users can:
Discover your Alexa skill through your app.
Enable the skill and link their account from your app.
If they are logged on to both your app and the Alexa app on their mobile device, they can link their accounts directly without entering their passwords.
If the Alexa app is not installed on their mobile device, they can use Login with Amazon (LWA) to link their accounts from your app. For more information, see Login with Amazon (LWA).
There are two other workflows to implement App-to-App Account Linking:
App-to-App Account Linking starting from the Alexa app: The user starts linking their account from the Alexa app instead of your app. For more information, see App-to-App Account Linking (Starting From the Alexa App).
Alexa app only (browser opens within the app): The user completes the account linking entirely within the Alexa app. This is the most common flow. For more information, see Add Account Linking to Your Alexa Skill.
If you have an application or website, we recommend that you implement an App-to-App Account Linking service that does not use the Alexa app browser. This topic describes this type of account linking, which starts from your app.
Target platforms
The Alexa app workflow is available for iOS and Android. The process you implement depends on whether you are developing for an iOS application, an Android application, or a website:
iOS and Android
Use the Alexa app flow as the primary flow. Use the LWA flow as a fallback if the Alexa app is not installed.
Websites
Implement the LWA flow.
User flow
During App-to-App Account Linking, the user follows this workflow.
Your app offers the user the option to enable your skill and link their account to Alexa. If the user chooses to link their account, one of two things happens. The outcome depends on whether the Alexa app is installed on the mobile device.
Alexa app
If the Alexa app is installed on the device, the Alexa app starts. It asks the user to confirm the account linking request. After confirming the request, the user returns to your application.
LWA
If the Alexa app is not installed on the device, a browser window opens with LWA. The user can enter their Amazon account information or create an Amazon account. The user is then asked to grant your skill permission to link accounts. After confirming the request, the user returns to your app.
After the accounts are linked and the user uses your skill, the skill uses the same workflow as the Alexa-app-only flow. For more information, see Alexa app only. In any case, disabling the skill unlinks the accounts.
The following are example screenshots of App-to-App Account Linking. As described, the App-to-App Account Linking workflow depends on whether the user has the Alexa app installed.
Flow design
App-to-App Account Linking works using OAuth 2.0.
The underlying interaction flow is as follows:
The user installs the app on their mobile device and logs in.
Your app offers the user the option to enable your skill and link their account to Alexa. It provides example information, such as "You can now order smart light bulbs through the Cloud Intelligence App with Alexa voice commands." The user confirms the linking request.
What happens next depends on whether the Alexa app is installed on the user's device.
If the Alexa app is installed:
Your app starts the Alexa app using the Alexa app URL and authorization request parameters.
The Alexa app starts and asks the user if they want to link Alexa to your service.
The user confirms the linking request.
The Alexa app uses a redirect URL to return the user to your app. It sends the user's Amazon authorization code as part of the redirect URL.
If the Alexa app is not installed:
Your app starts LWA in an in-app browser tab, not the native browser app. It uses the LWA fallback URL and the relevant authorization request parameters.
LWA starts and asks the user to log on to their Amazon account.
LWA asks the user if they want to link Alexa to your service.
The user confirms the linking request.
LWA uses your redirect URL to send the user back to your application. It sends the user's Amazon authorization code as part of the redirect URL.
Your backend server calls the LWA authorization service URL. It exchanges the Amazon authorization code retrieved in the previous step for an Amazon access token.
Your backend server calls your authorization server to obtain the user's authorization code for their account in your service.
The backend server calls the Alexa Skill Activation API with the user's Amazon access token and the user's service authorization code. This enables the skill and links the accounts.
Alexa goes to your app's access token URL. It exchanges your service's user authorization code for your service's access token. This completes the App-to-App Account Linking.
Development steps
We recommend that you use the following libraries and languages to provide code samples for iOS apps (10.0 or later) and Android apps:
iOS app
Swift 4, Alamofire, and SwiftyJSON
Android app
Backend server
Display the account linking option in your app.
Your app needs a start page from which the user can start skill enablement and account linking. This page should show what the user can do by linking their account to Alexa, such as ordering smart lights by voice. How you allow the user to start account linking depends on the experience you want to create. You can offer account linking in multiple places. For example, you can offer the option after the user registers for an account in your app. You can provide a button on a "Link to Alexa" page in the app's settings. In any case, explain the convenience and benefits of linking their account to Alexa.
To add an App-to-App Account Linking button to your iOS app:
Open the storyboard in the Xcode integrated development environment (IDE).
Add a UIButton to the view controller and name it "Link your account with Alexa". Consider a name that shows the benefit to the user, such as "Listen to your music with Alexa" or "Order a smart oven that understands you".
In the upper-left corner of the Xcode IDE, click "Show the assistant editor".
Create an action connection for the button by dragging the button to the code editor.
To add an App-to-App Account Linking button to your Android app:
In Android Studio, in Design Mode, open the page layout where you want to add the App-to-App Account Linking button.
From the palette, drag a new button to the layout.
Define an ID for the button.
In the page's code, initialize the App-to-App Account Linking button. Define its
onClicklistener behavior to start the process, as shown 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) } }
Enable Universal Links (iOS) or App Links (Android) for your app. To allow the Alexa app to redirect users back to your app, you must enable Universal Links for your iOS application or App Links for your Android application.
NoteUniversal Links and App Links must comply with the specified URI syntax. Remember the domain and path you add. You will need them when you add the redirect URL to the skill details in the Alexa developer console.
To enable Universal Links, see the examples and instructions in the iOS linking development documentation.
To enable Android App Links, see the instructions for handling Android App Links in Android. For related examples, see Create deep links to app content and Verify app links in the Android documentation.
Configure your skill to use App-to-App Account Linking.
You can use the Alexa developer console, the Alexa Skills Kit command-line interface (ASK CLI), or the Alexa Skill Management API (SMAPI) to configure your skill. During this configuration, you can specify your app's redirect URL, access token URL, and other details.
Configure using the Alexa developer console.
Log on to the Alexa developer console.
Find your skill in the list. Under your skill, select Edit.
On the left, click
TOOLS, and then clickAccount Linking.If you have previously configured Account Linking for your skill, enable "Allow users to link their account to your skill from within your application or website".
For Authorization Grant Type, select Authorization Code Grant if it is not already selected. App-to-App Account Linking only supports the authorization code grant type.
For the other settings, use the settings described in Configure Account Linking. Note the following additional instructions when you fill in the Redirect URL field:
For Redirect URL, add the redirect URL described in URLs and parameters.
The redirect URL must comply with the specified URI syntax. The Alexa app will open this Universal Link or App Link URL to start your app.
You can add multiple redirect URLs to handle different scenarios.
If your app does not support Universal Links and App Links, you should have a valid web page that the redirect URL points to. This should support the same process for receiving the user's Amazon authorization code, as described in Flow design.
Remember to add redirect URLs for your iOS application, Android application, and website.
Configure using the ASK CLI.
To enable App-to-App Account Linking using the ASK CLI, use the subcommand in Update account linking info and follow these steps:
To allow users to enable the skill without account linking, select
YorN.For Authorization URL, enter the URL of your authorization server. This URL is used for regular account linking. The authorization server must accept the user's credentials, authenticate the user, and generate an authorization code. The Alexa app can later pass this code to your authorization server to retrieve an access token that uniquely identifies the user with your service.
For Client ID, enter the identifier on your logon page that will be used to identify that the request is from your skill.
For Scopes (comma-separated), enter the characters that represent the required access permissions for the user's account, such as the user ID. This is required for smart home skills. You can specify up to 15
scopes.For Domains (comma-separated), enter a list of additional domains from which the logon page retrieves content. You can specify up to 15 domains.
For Authorization Grant Type, select
AUTH_CODE.For Access Token URI, enter the access token URL. For more information, see URLs and parameters.
For Client Secret, enter a credential that allows the Alexa service to authenticate with the access token URI. This is combined with the Client ID to identify requests from Alexa.
For Client Authentication Scheme, select
HTTP_BASICorREQUEST_BODY_CREDENTIALS.(Optional) For Default access token expiration time in seconds, enter the time in seconds that the access token is valid.
(Optional) For Interactive access token URL, enter the URI that will be called with authorization codes that can be exchanged for Alexa access tokens.
(Optional) For Redirect URLs for App-to-App Account Linking, enter the app redirect URL described in URLs and parameters. This setting is required for App-to-App Account Linking.
Configure using the Alexa Skill Management API (SMAPI).
To configure App-to-App Account Linking using the SMAPI, use the structure in this Account linking request.
The following is an example request to add a redirect URL using SMAPI.
Name
Description
Type
skipOnEnablementSet to true to allow users to enable the skill without starting the account linking flow. Set to false to require the normal account linking flow when a user enables the skill. For more information, see Let users enable your skill without linking an account.
Boolean
typeSpecifies the OAuth authorization grant type. For App-to-App Account Linking, you must use
AUTH_CODE.String
authorizationUrlThe URL of your authorization server. It must accept the user's credentials, authenticate the user, and generate an authorization code. The Alexa app can later pass this code to your authorization server to retrieve an access token that uniquely identifies the user with your service.
String
domainsA list of additional domains from which your logon page gets content. You can specify up to 15 domains.
Array of String
clientIdThe identifier your logon page uses to identify that the request is from your skill.
String
scopesA string that indicates the required access permissions for the user's account, such as
user_id. This field is required for smart home skills. You can specify up to 15scopes.Array of String
accessTokenUrlThe URI used to request an authorization token. This is required only when
typeisAUTH_CODE.String
clientSecretThe credential you provide that allows the Alexa service to authenticate with the access token URI. This is combined with the
clientIdto identify requests from Alexa.String
accessTokenSchemeThe type of authentication used. For example,
HTTP_BASICorREQUEST_BODY_CREDENTIALS. This is required for App-to-App Account Linking because the authorization grant type isAUTH_CODE.String
redirectUrlsTo start your app with a Universal Link or App Link, the redirectURL must comply with the specified URI syntax.
Array of String
{ "accountLinkingRequest": { "accessTokenScheme": "HTTP_BASIC", "accessTokenUrl": "https://api.amazon.com/auth/o2/token/", "authorizationUrl": "https://www.amazon.com/ap/oa/", "clientId": "yourSMAPIClientId", "clientSecret": "yourSMAPIClientSecret", "domains": [], "redirectUrls": ["yourRedirectURL1","yourRedirectURL2"], "scopes": ["profile"], "skipOnEnablement": true, "type": "AUTH_CODE" } }
Obtain the user's Amazon authorization code.
Your app needs to obtain the user's Amazon authorization code so that it can be exchanged for an Amazon access token later. The Amazon access token will enable your app to enable the skill and complete the account linking. The Amazon authorization code is valid for 5 minutes.
You can obtain the user's Amazon authorization code by opening the Alexa app URL (for the Alexa app flow) or by making an
HTTP GETrequest to the LWA authorization server (for the LWA flow). For iOS and Android, we strongly recommend that you implement the Alexa app flow and use LWA only as a fallback.To obtain the user's Amazon authorization code, assemble the authorization request. Then, open the Alexa app URL (or the LWA fallback URL if the Alexa app is not installed) with the parameters described in URLs and parameters. We strongly recommend that you assemble these URLs on your backend server and pass the assembled URLs to your application. This way, you can quickly change parameters, such as the stage, without rebuilding your application.
When the user confirms the linking request, the Alexa app redirects the user to your application's redirect URL, which is a parameter in the Alexa or LWA fallback URL. The redirect URL includes the success or error parameters described next. If the user's device cannot start your application using the redirect URL you provided, the user is sent to the same redirect URL in their default browser.
For example, this can happen in the following situations:
Universal Links or App Links are not enabled in your application.
Universal Links or App Links are not enabled in your application for the redirect URL you provided.
The application version on the user's device is an older version that does not support Universal Links or App Links.
In these cases, you should have a web page (with the same URL as the Universal Link or App Link) to receive the authorization code. When the user goes to your web page, they may need to log on so that you can obtain an authorization code from your service.
For possible errors and the corresponding messages to show the user, see Errors when obtaining the Amazon authorization code.
Successful response to the HTTP GET request for the Alexa app URLIf successful, the response is a redirect URL that includes the user's Amazon authorization code and other parameters shown next. To handle this, see Apple's guidelines on supporting Universal Links in your app and the Android guide for 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}
Address
code: The Amazon authorization code returned by the Alexa app or LWA.
state: The state you passed in the authorization request. You should validate the state to prevent cross-site request forgery.
scope: The permission scope. LWA returns the alexa::skills:account_linking scope. The Alexa app does not return a scope parameter.
For more details on OAuth 2.0 authorization responses, see Authorization Response.
Error response to the HTTP GET request for the Alexa app URL
If an error occurs, the response is a redirect URL with an error and other parameters, as shown next. Your application must display an appropriate error message (error page, UIAlertController, etc.). For the error messages to display, see Error codes when obtaining the Amazon authorization code.
// Example error response
HTTP 200
https://yourRedirectURL?error_description={Error description}&state={your state}&error={OAuth Error Code}
Additional information
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 more details on OAuth 2.0 error responses, see Error Response.
iOS example
Example: Obtain URL (iOS)
In this example, the application obtains 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 the 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 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)
}
Example: Open URL (iOS)
In this example, the application opens the Alexa app URL, or the LWA fallback URL if the Alexa app is not installed.
Note the following:
This example uses the UIApplication.shared.open API with the universalLinksOnly option to open the URL.
The URL opens only if the application (in this case, the Alexa app) is configured to handle Universal Links with the universalLinksOnly option. Therefore, you can handle the case where the Alexa app fails to start using the
completionHandlerclosure expression. For more information about closure expressions, see Closure Expressions. For example, the Alexa app is not installed or the version does not support Universal Links.
If your application cannot start the Alexa app when using the universalLinksOnly option, your application needs to use LWA to obtain the Amazon authorization code. You can use SFAuthenticationSession on iOS 11 or ASWebAuthenticationSession on iOS 12.0 or later. This retrieves the Safari user session and cookies, which avoids an extra logon when the user is already logged on to Amazon.com in Safari. This causes your application to display a window asking the user to consent to sharing website information. The content of this message is defined by iOS.
For iOS versions earlier than 11.0, this example uses SFSafariViewController to send the user to the LWA page. SFSafariViewController does not require the user to consent to sharing website information. Therefore, the Safari browser does not share cookies with SFSafariViewController.
import SafariServices
// Open the Alexa URL with the universalLinksOnly option.
// 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(){}
}
Example: Open LWA URL (iOS)
Opens the LWA URL in the case of a website, as a fallback for when the Alexa app is not installed on an iOS device.
In this example, the application opens LWA in a view controller.
import SafariServices
// Open Login with Amazon in a Safari View controller
private func openSafariView(lwaFallbackURL: URL) {
let safariViewController = SFSafariViewController(url: lwaFallbackURL)
self.present(safariViewController, animated: true, completion: nil)}
}
Example: Handle the Universal Link response (iOS)
In this example, the application validates and extracts the parameters from the Alexa app's call 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
}
//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 = 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 OAuth 2.0 specification
class AuthResponse : ValidatedResponse{
private(set) var code : String
private(set) var state: String
// The scope will be returned in Login with Amazon, but will not be present in the 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 OAuth 2.0 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 example
Example: Obtain URL (Android)
Make a request to your backend service to obtain the Alexa app URL and the LWA fallback URL. You can implement this request in any way you like, for example, 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
}
Example: Open URL (Android)
In this example, the application opens the Alexa app URL, or the LWA fallback URL if the Alexa app is not installed.
Note the following:
This example uses PackageManager to verify that the Alexa app is installed and that the installed version can handle App-to-App Account Linking.
If the Alexa app is not installed or cannot handle App-to-App Account Linking, your application must use the LWA flow to obtain the Amazon authorization code. For the LWA fallback URL format, see URLs and parameters below.
This example uses the Android ACTION_VIEW intent to open the URL. 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
}
}
}
Example: Handle the App Link response (Android)
In this example, the application validates and extracts the parameters from the Alexa app's call 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
Example: State Helper
In this example, the application generates and validates the state between the request and the response. You must use the state to validate incoming requests to prevent cross-site request forgery. For more information, see 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 the state in the 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
})
}
}
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 obtain an Amazon access token. You need the access token to call the Alexa service to enable the skill and complete the account linking.
Make an HTTP POST request in the format shown below. For possible errors and the corresponding messages to show the user, see Error codes when exchanging the Amazon authorization code for an Amazon access token.
Request parameters
POST /auth/o2/token HTTP/1.1 Host: api.amazon.com Content-Type: application/x-www-form-urlencoded;charset=UTF-8 grant_type=authorization_code &code=amazon authorization code &client_id=yourClientId &client_secret=yourClientSecret &redirect_uri=yourRedirectUriResponse parameters
HTTP 200 Content-Type: application/json { "access_token": "AmazonAccessToken", "expires_in": "3600", "refresh_token": "YourRefreshToken", "token_type": "bearer" }For more details on obtaining an access token in the LWA case, see Access Token Request, Access Token Response, and Access Token Errors.
Example: Obtain the user's Amazon access token
In this example, the application exchanges the user's Amazon authorization code for an Amazon access token.
const axios = require('axios'); // Amazon OAuth token request in the backend const OAuthRequests = (function () { const header = { "headers": { "Content-Type": "application/json" } }; const accessTokenBody = function (amazonAuthCode, state) { return { "grant_type": "authorization_code", "code": amazonAuthCode, "redirect_uri": config.skillConfig.redirect_uri, "client_id": config.skillConfig.client_id, "client_secret": config.skillConfig.client_secret }; }; return { accessTokenRequest: (authcode, state) => { return { header: header, body: accessTokenBody(authcode, state) } } } })(); // Request the Amazon access/refresh tokens from the Amazon OAuth token server function getAccessTokenByAuthcode(amazonAuthCode, state) { const accessTokenRequest = OAuthRequests.accessTokenRequest(amazonAuthCode, state); return axios.post(config.endpoints.oauthEndpoint, accessTokenRequest.body, accessTokenRequest.header); }Enable the skill and complete account linking.
When 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 the account linking.
Make a request to the Alexa Skill Activation API using the following URL format:
[BASEURL]/v1/users/~current/skills/{yourSkillId}/enablement, where[BASE URL]depends on the user's region:https://api.amazonalexa.com,https://api.eu.amazonalexa.com, orhttps://api.fe.amazonalexa.com. The security provider you use must authorize calls from your application to the application's backend server. It must also provide a server-side API to obtain the authorization code for the user currently logged on to your application. That is, it needs to provide an authorization code for the user's account in your service. Alexa will use this code to exchange for a user access token to access the user's resources. For possible errors and the corresponding messages to show the user, see Errors when calling the Alexa Skill Activation API. For more details on the Alexa Skill Activation API, see Alexa Skill Activation API.NoteIf you use LWA as your security provider, remember that the LWA Client ID you use to obtain the userAuthCode is different from the Alexa Client ID shown in the Alexa developer console when configuring the App-to-App Account Linking skill. You cannot use the Alexa Client ID used to obtain the Amazon access token (Step 5) to generate an authCode for your user. To generate a new userAuthCode, use the LWA Client ID associated with your LWA security profile.
If you have a smart home skill and have enabled the Send Alexa Events permission for it, your Lambda function must handle the AcceptGrant directive. Otherwise, account linking fails when the user tries to enable your skill. For more information, see Authenticate a Customer to Alexa with Permissions. Note that, in general, sending events to the event gateway still works as it does today. That is, after the skill is enabled, it can obtain an access token from the directive.
To enable the skill, make the following request:
Request parameters
POST /v1/users/~current/skills/{skillId}/enablement HTTP/1.1 Host: api.amazonalexa.com, api.eu.amazonalexa.com, api.fe.amazonalexa.com Content-Type: application/json Authorization: "Bearer {Amazon Access Token}" { "stage": "skill stage", "accountLinkRequest": { "redirectUri": "https://yourRedirectURI", "authCode": "Your user's authorization code from your authorization server", "type": "AUTH_CODE" } }In the next request shown, 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
Successful response
HTTP 201 Content-Type: application/json { "skill": { "stage": "your skill stage", "id": "your skill id" }, "user": { "id": "User ID" }, "accountLink": { "status": "LINKED" }, "status": "ENABLED" }Note that you can disable the skill and unlink the user's account by making a DELETE request to the Alexa Skill Activation API.
Example: Enable the skill and complete account linking
In this example, the application enables the skill and completes the 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 the Amazon OAuth Code for an 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 the enablement. const createEnablementRequest = AlexaServiceRequest.createEnablement(amazonAccessToken, userAuthcode); return sendPostRequests(config.endpoints.alexaServiceEndpoints, createEnablementRequest.body, createEnablementRequest.header); }); } // Post the 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); } }) }) }); }Display the account linking status in your app.
Keep the user informed of the account linking status by displaying it in your app, for example, in the screenshots in "User experience and errors". You can use different user interface components, such as a table or a page view, to display the account linking status.
Example: Display account linking status
In this example, the application uses a UIViewController to display the account linking status.
In the Xcode IDE, go to the main storyboard and 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 the Identity Inspector.
Add a title, a UILabel to display the account linking status, a UIButton to close this page, and a UIActivityIndicatorView to show a loading animation while the application calls 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 logged in", 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 Succeeded", 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()
Use the access token in your skill.
After the user successfully enables your skill and links Alexa to your service, requests sent to your skill include the user's access token. Your skill code needs to retrieve the access token from the request, validate it, and use it to retrieve the necessary user information from the resource server. For more details on how to validate and use the access token, see the documentation for your skill type:
Example: Cloud Intelligence app (Android and iOS)
Cloud Intelligence Android app
App-Alexa-App
Obtain Alexa App-to-App parameters.
Call the cloud API /living/voice/alexa/apptoapp/config/get to obtain the Alexa configuration.
Returned entity object:
public class AlexaApptoappConfigGetDTO { private String alexaAppUrl; private String lwaFallbackUrl;Launch Alexa.
private void openAlexaAppToAppUrl(String alexaAppUrl, String lwaFallbackUrl) { Intent alexaAppToAppIntent; if (doesAlexaAppSupportAppToApp(getApplicationContext())) { alexaAppToAppIntent = getAppToAppIntent(alexaAppUrl); } else { alexaAppToAppIntent = getAppToAppIntent(lwaFallbackUrl); } startActivity(alexaAppToAppIntent); finish(); }private Intent getAppToAppIntent(String appToAppUrl) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(appToAppUrl)); return intent; }Receive the skill binding result from Alexa.
<activity android:name=".activity.AuthActivity" android:launchMode="singleTask" android:screenOrientation="portrait"> <!--Amazon Alexa App--> <intent-filter android:autoVerify="true" tools:ignore="UnusedAttribute"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:host="yourHost" android:scheme="yourScheme" android:pathPrefix="yourPathPrefix" /> </intent-filter> </activity>private void handleIntent(@NonNull Intent intent) { ALog.d(TAG, "handleIntent: "); // Callback from Amazon Alexa if (intent.getData() == null) return; String appLinkData = intent.getData().toString(); ILog.d(TAG, "appLinkData:" + appLinkData); // After the callback from Alexa, authenticate with the cloud. Set a tag to prevent the current Cloud Intelligence account from being logged out. String domain = "https://open-living.iot.aliyun.com"; if (GlobalConfig.API_ENV_PRE.equals(GlobalConfig.getInstance().getApiEnv())) { domain = "https://open-living-beta.iot.aliyun.com"; } if (appLinkData.startsWith(domain) && !appLinkData.contains("error=") && !appLinkData.contains("error_description=")) { if (null == mHandler) { mHandler = new TmallGenieHandler(this); } // Link callback parsingUrl(appLinkData); } }private void parsingUrl(String url) { String redirect_uri; String code = ""; String state = ""; url = url.trim(); String[] urlParts = url.split("\\?"); redirect_uri = urlParts[0]; if (urlParts.length > 1) { String[] params = urlParts[1].split("&"); Map<String, String> map = new HashMap<>(); for (String param : params) { String[] keyValue = param.split("="); if (keyValue.length > 1) { map.put(keyValue[0], keyValue[1]); } } state = map.get("state"); code = map.get("code"); } ILog.e(TAG, " redirect_uri = " + redirect_uri + ",state = " + state, ",code = " + code); }Query the cloud to check if the skill was bound successfully.
public void queryAlexaApptoappLaunchGet(String state, String redirectUri, String code) { ILog.d("TmallGenieBusiness", "queryAlexaApptoappLaunchGet"); if (null == mIoTAPIClient) { return; } Map<String, Object> params = new HashMap<>(); params.put("state", state); params.put("redirectUri", redirectUri); params.put("code", code); IoTRequest request = buildRequest(params, "/living/voice/alexa/apptoapp/launch"); ILog.d("TmallGenieBusiness", "requestMap:" + JSON.toJSONString(params)); IoTAPIClient mIoTAPIClient = new IoTAPIClientFactory().getClient(); mIoTAPIClient.send(request, mListener); }
Alexa-to-App-to-Alexa
Receive the authentication request from Alexa.
<activity android:name=".activity.AuthActivity" android:launchMode="singleTask" android:screenOrientation="portrait"> <!--Amazon Alexa App--> <intent-filter android:autoVerify="true" tools:ignore="UnusedAttribute"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:host="yourHost" android:scheme="yourScheme" android:pathPrefix="yourPathPrefix" /> </intent-filter> </activity>private void handleIntent(@NonNull Intent intent) { needKeepUserClick = false; ALog.d(TAG, "action: " + intent.getAction()); ALog.d(TAG, "data: " + intent.getData()); String clientId = null; String state = null; final ArrayList<String> scopeList = new ArrayList<>(); String redirectUri = null; Uri appLinkData = intent.getData(); if (appLinkData == null) { Log.w(TAG, "handleIntent: ", new Throwable("Cloud Intelligence: empty app link data")); finish(); return; } if (appLinkData.getPathSegments().contains("oauth2") || appLinkData.getPathSegments().contains("alexa")) { if (appLinkData.getPathSegments().contains("alexa")) { needKeepUserClick = true; } mScope = Scope.Alexa; clientId = appLinkData.getQueryParameter("client_id"); state = appLinkData.getQueryParameter("state"); String _scope = appLinkData.getQueryParameter("scope"); if (_scope != null) { scopeList.add(_scope); } redirectUri = appLinkData.getQueryParameter("redirect_uri"); } else { Log.w(TAG, "handleIntent: ", new Throwable("Cloud Intelligence: unknown path segments")); } if (mScope == null) { Log.w(TAG, "handleIntent: ", new Throwable("Cloud Intelligence: wrong scope")); finish(); return; } String finalClientId = clientId; String finalState = state; String finalRedirectUri = redirectUri;Obtain the code from the cloud.
private void accountAuth2CodeGet(@NonNull String clientId, @NonNull String state, @NonNull String redirectUri, @Nullable List<String> scope) { showLoading(); JSONObject params = new JSONObject(); params.put("clientId", clientId); params.put("state", state); params.put("redirectUri", redirectUri); if (scope != null) { params.put("scope", scope); } IoTRequestBuilder builder = new IoTRequestBuilder().setAuthType(MineConstants.APICLIENT_IOTAUTH).setApiVersion(LIVING_ACCOUNT_OAUTH2_CODE_GET_VERSION); IoTRequest ioTRequest = builder .setPath("/living/account/oauth2/code/get") .setParams(params.getInnerMap()) .setScheme(Scheme.HTTPS) .build(); new IoTAPIClientFactory().getClient().send(ioTRequest, new IoTCallback() { @Override public void onFailure(IoTRequest request, Exception e) { ALog.w(TAG, "onFailure: " + (e == null ? "" : e.getMessage())); String message = e != null ? e.getMessage() : null; if (message == null) { message = e != null ? e.getLocalizedMessage() : null; } onAuthFailed(message, redirectUri, state); } @Override public void onResponse(IoTRequest request, IoTResponse response) { if (response != null && response.getData() != null && response.getCode() == 200) { ALog.d(TAG, "onResponse succeed"); onAuthSucceed(response.getData().toString(), redirectUri, state); } else { String message = response != null ? response.getMessage() : null; if (message == null) { message = response != null ? response.getLocalizedMsg() : null; } ALog.d(TAG, "onResponse error; " + message); onAuthFailed(message, redirectUri, state); } } }); }Cloud authentication successful.
private void onAuthSucceed(@NonNull String code, @NonNull String redirectUri, @NonNull String state) { Intent intent = new Intent(); ThreadPool.MainThreadHandler.getInstance().post(() -> { if (isFinishing() || isDestroyed()) return; dismissLoading(); switch (mScope) { case Alexa: intent.setAction(Intent.ACTION_VIEW); intent.setData(getAlexaSuccessfulUri(redirectUri, code, state)); try { if (intent.resolveActivity(AApplication.getInstance().getPackageManager()) != null) { startActivity(intent); } else { ALog.d(TAG, "not systemAction->" + intent.getAction()); } } catch (Exception e) { e.printStackTrace(); } break; default: break; } finish(); }); }Cloud authentication failed.
private void onAuthSucceed(@NonNull String code, @NonNull String redirectUri, @NonNull String state) { Intent intent = new Intent(); ThreadPool.MainThreadHandler.getInstance().post(() -> { if (isFinishing() || isDestroyed()) return; dismissLoading(); switch (mScope) { case Alexa: intent.setAction(Intent.ACTION_VIEW); intent.setData(getAlexaSuccessfulUri(redirectUri, code, state)); try { if (intent.resolveActivity(AApplication.getInstance().getPackageManager()) != null) { startActivity(intent); } else { ALog.d(TAG, "not systemAction->" + intent.getAction()); } } catch (Exception e) { e.printStackTrace(); } break; default: break; } finish(); }); }
Cloud Intelligence iOS app
Development steps - Cloud Intelligence iOS app
We recommend that you use the following libraries and languages to provide iOS apps (9.0 or later):
iOS app
Swift 4, Alamofire, and SwiftyJSON
Backend server
Display the account linking option in the Cloud Intelligence iOS app.
The Cloud Intelligence iOS app needs a start page from which the user can start skill enablement and account linking. This page should show what the user can do by linking their account to Alexa, such as ordering smart lights by voice.
How you allow the user to start account linking depends on the experience you want to create. You can offer account linking in multiple places. For example, you can offer the option after the user registers for a Cloud Intelligence iOS app account. You can provide a button on a "Link to Alexa" page in the app's settings. In any case, explain the convenience and benefits of linking their account to Alexa.
To add an App-to-App Account Linking button to your iOS app:
Open the storyboard in the Xcode IDE.
Add a UIButton to the view controller and name it "Link your account with Alexa". Consider a name that shows the benefit to the user, such as "Listen to your music with Alexa" or "Order a smart oven that understands you".
In the upper-left corner of the Xcode IDE, click "Show the assistant editor".
Create an action connection for the button by dragging the button to the code editor.
Enable Universal Links for the Cloud Intelligence iOS app.
To allow the Alexa app to redirect users back to the Cloud Intelligence iOS app, you must enable Universal Links for your iOS application.
NoteUniversal Links and App Links must comply with the specified URI syntax. Remember the domain and path you add. You will need them when you add the redirect URL to the skill details in the Alexa developer console.
To enable Universal Links, see the examples and instructions in the iOS linking development documentation.
Configure your skill to use App-to-App Account Linking.
You can use the Alexa developer console, the ASK CLI, or the SMAPI to configure your skill. During this configuration, you can specify the Cloud Intelligence iOS app's redirect URL, access token URL, and other details.
Configure using the Alexa developer console.
Log on to the Alexa developer console.
Find your skill in the list. Under your skill, select Edit.
On the left, click
TOOLS, and then clickAccount Linking.If you have previously configured Account Linking for your skill, enable "Allow users to link their account to your skill from within your application or website".
For Authorization Grant Type, select Authorization Code Grant if it is not already selected. App-to-App Account Linking only supports the authorization code grant type.
For the other settings, use the settings described in Configure Account Linking. Note the following additional instructions when you fill in the Redirect URL field:
For Redirect URL, add the redirect URL described in URLs and parameters.
The redirect URL must comply with the specified URI syntax. The Alexa app will open this Universal Link or App Link URL to start the Cloud Intelligence iOS app.
You can add multiple redirect URLs to handle different scenarios.
If your app does not support Universal Links and App Links, you should have a valid web page that the redirect URL points to. This should support the same process for receiving the user's Amazon authorization code, as described in Flow design.
Remember to add redirect URLs for your iOS application, Android application, and website.
Configure using the ASK CLI.
To enable App-to-App Account Linking using the ASK CLI, use the subcommand in Update account linking info and follow these steps:
To allow users to enable the skill without account linking, select
YorN.For Authorization URL, enter the URL of your authorization server. This URL is used for regular account linking. The authorization server must accept the user's credentials, authenticate the user, and generate an authorization code. The Alexa app can later pass this code to your authorization server to retrieve an access token that uniquely identifies the user with your service.
For Client ID, enter the identifier on your logon page that will be used to identify that the request is from your skill.
For Scopes (comma-separated), enter the characters that represent the required access permissions for the user's account, such as the user ID. This is required for smart home skills. You can specify up to 15
scopes.For Domains (comma-separated), enter a list of additional domains from which the logon page retrieves content. You can specify up to 15 domains.
For Authorization Grant Type, select
AUTH_CODE.For Access Token URI, enter the access token URL. For more information, see URLs and parameters.
For Client Secret, enter a credential that allows the Alexa service to authenticate with the access token URI. This is combined with the Client ID to identify requests from Alexa.
For Client Authentication Scheme, select
HTTP_BASICorREQUEST_BODY_CREDENTIALS.(Optional) For Default access token expiration time in seconds, enter the time in seconds that the access token is valid.
(Optional) For Interactive access token URL, enter the URI that will be called with authorization codes that can be exchanged for Alexa access tokens.
(Optional) For Redirect URLs for App-to-App Account Linking, enter the app redirect URL described in URLs and parameters. This setting is required for App-to-App Account Linking.
Configure using the Alexa Skill Management API (SMAPI).
To configure App-to-App Account Linking using the SMAPI, use the structure in this Account linking request.
The following is an example request to add a redirect URL using SMAPI.
Name
Description
Type
skipOnEnablementSet to true to allow users to enable the skill without starting the account linking flow. Set to false to require the normal account linking flow when a user enables the skill. For more information, see Let users enable your skill without linking an account.
Boolean
typeSpecifies the OAuth authorization grant type. For App-to-App Account Linking, you must use
AUTH_CODE.String
authorizationUrlThe URL of your authorization server. It must accept the user's credentials, authenticate the user, and generate an authorization code. The Alexa app can later pass this code to your authorization server to retrieve an access token that uniquely identifies the user with your service.
String
domainsA list of additional domains from which your logon page gets content. You can specify up to 15 domains.
Array of String
clientIdThe identifier your logon page uses to identify that the request is from your skill.
String
scopesA string that indicates the required access permissions for the user's account, such as
user_id. This field is required for smart home skills. You can specify up to 15scopes.Array of String
accessTokenUrlThe URI used to request an authorization token. This is required only when
typeisAUTH_CODE.String
clientSecretThe credential you provide that allows the Alexa service to authenticate with the access token URI. This is combined with the
clientIdto identify requests from Alexa.String
accessTokenSchemeThe type of authentication used. For example,
HTTP_BASICorREQUEST_BODY_CREDENTIALS. This is required for App-to-App Account Linking because the authorization grant type isAUTH_CODE.String
redirectUrlsTo start the Cloud Intelligence iOS app with a Universal Link or App Link, the redirectURL must comply with the specified URI syntax.
Array of String
{ "accountLinkingRequest": { "accessTokenScheme": "HTTP_BASIC", "accessTokenUrl": "https://api.amazon.com/auth/o2/token/", "authorizationUrl": "https://www.amazon.com/ap/oa/", "clientId": "yourSMAPIClientId", "clientSecret": "yourSMAPIClientSecret", "domains": [], "redirectUrls": ["yourRedirectURL1","yourRedirectURL2"], "scopes": ["profile"], "skipOnEnablement": true, "type": "AUTH_CODE" } }
Obtain the user's Amazon authorization code.
The Cloud Intelligence iOS app needs to obtain the user's Amazon authorization code so that it can be exchanged for an Amazon access token later. The Amazon access token will enable the Cloud Intelligence iOS app to enable the skill and complete the account linking. The Amazon authorization code is valid for 5 minutes.
You can obtain the user's Amazon authorization code by opening the Alexa app URL (for the Alexa app flow) or by making an
HTTP GETrequest to the LWA authorization server (for the LWA flow). For iOS and Android, we strongly recommend that you implement the Alexa app flow and use LWA only as a fallback.To obtain the user's Amazon authorization code, assemble the authorization request. Then, open the Alexa app URL (or the LWA fallback URL if the Alexa app is not installed) with the parameters described in URLs and parameters. We strongly recommend that you assemble these URLs on your backend server and pass the assembled URLs to your application. This way, you can quickly change parameters, such as the stage, without rebuilding your application.
When the user confirms the linking request, the Alexa app redirects the user to your application's redirect URL, which is a parameter in the Alexa or LWA fallback URL. The redirect URL includes the success or error parameters described next.
If the user's device cannot start your application using the redirect URL you provided, the user is sent to the same redirect URL in their default browser. For example, this can happen in the following situations:
Universal Links or App Links are not enabled in your application.
Universal Links or App Links are not enabled in your application for the redirect URL you provided.
The application version on the user's device is an older version that does not support Universal Links or App Links.
In these cases, you should have a web page (with the same URL as the Universal Link or App Link) to receive the authorization code. When the user goes to your web page, they may need to log on so that you can obtain an authorization code from your service.
For possible errors and the corresponding messages to show the user, see Errors when obtaining the Amazon authorization code.
Successful response to the HTTP GET request for the Alexa app URLIf successful, the response is a redirect URL that includes the user's Amazon authorization code and other parameters shown next. To handle this, see Apple's guidelines on supporting Universal Links in your app and the Android guide for 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}Address
code: The Amazon authorization code returned by the Alexa app or LWA.
state: The state you passed in the authorization request. You should validate the state to prevent cross-site request forgery.
scope: The permission scope. LWA returns the
alexa::skills:account_linkingscope. The Alexa app does not return a scope parameter.
For more details on OAuth 2.0 authorization responses, see Authorization Response.Error response to the HTTP GET request for the Alexa app URLIf an error occurs, the response is a redirect URL with an error and other parameters, as shown next. Your application must display an appropriate error message (error page, UIAlertController, etc.). For the error messages to display, see Error codes when obtaining the Amazon authorization code.
// Example error response HTTP 200 https://yourRedirectURL?error_description={Error description}&state={your state}&error={OAuth Error Code}Additional information
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 more details on OAuth 2.0 error responses, see Authorization Response.
Example: Obtain URL (iOS)
In this example, the application obtains 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 the 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 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) }Example: Open URL (iOS)
In this example, the application opens the Alexa app URL, or the LWA fallback URL if the Alexa app is not installed.
Note the following:
This example uses the UIApplication.shared.open API with the universalLinksOnly option to open the URL.
The URL opens only if the application (in this case, the Alexa app) is configured to handle Universal Links with the universalLinksOnly option. Therefore, you can handle the case where the Alexa app fails to start using the
completionHandlerclosure expression. For more information about closure expressions, see Closure Expressions. For example, the Alexa app is not installed or the version does not support Universal Links.If your application cannot start the Alexa app when using the universalLinksOnly option, your application needs to use LWA to obtain the Amazon authorization code. You can use SFAuthenticationSession on iOS 11 or ASWebAuthenticationSession on iOS 12.0 or later. This retrieves the Safari user session and cookies, which avoids an extra logon when the user is already logged on to Amazon.com in Safari. This causes your application to display a window asking the user to consent to sharing website information. The content of this message is defined by iOS.
For iOS versions earlier than 11.0, this example uses SFSafariViewController to send the user to the LWA page. SFSafariViewController does not require the user to consent to sharing website information. Therefore, the Safari browser does not share cookies with SFSafariViewController.
You can also open the LWA fallback URL through WKWebView. After authorization is complete in the WebView, your app will be opened via a Universal Link. Intercept this URL to obtain the authorization parameters.
import SafariServices // Open the Alexa URL with the universalLinksOnly option. // 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(){} }Example: Open LWA URL (iOS)
Opens the LWA URL in the case of a website, as a fallback for when the Alexa app is not installed on an iOS device.
In this example, the application opens LWA in a view controller.
import SafariServices // Open Login with Amazon in a Safari View controller. private func openSafariView(lwaFallbackURL: URL) { let safariViewController = SFSafariViewController(url: lwaFallbackURL) self.present(safariViewController, animated: true, completion: nil)} }
Example: Handle the Universal Link response (iOS)
In this example, the application validates and extracts the parameters from the Alexa app's call 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 } //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 = 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 OAuth 2.0 specification class AuthResponse : ValidatedResponse{ private(set) var code : String private(set) var state: String // The scope will be returned in Login with Amazon, but will not be present in the 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 OAuth 2.0 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) } }State generation and validation
Example: State Helper
In this example, the application generates and validates the state between the request and the response. You must use the state to validate incoming requests to prevent cross-site request forgery. For more information, see 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 the state in the 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 }) } }
Exchange the Amazon authorization code for an Amazon access token, enable the skill, and complete account linking.
Pass the
code,redirectUri, andstateto the cloud through the/living/voice/alexa/apptoapp/launchAPI to complete the account linking.Display the account linking status in the Cloud Intelligence iOS app. Keep the user informed of the account linking status by displaying it in the Cloud Intelligence iOS app, for example, in the screenshots in "User experience and errors". You can use different user interface components, such as a table or a page view, to display the account linking status.
Example: Display account linking status.
In this example, the application uses a
UIViewControllerto display the account linking status.In the Xcode IDE, go to the main storyboard and add a new view controller. The storyboard ID for this view controller is
linkingStatusScreen.Add a new
Cocoa Touchclass file, select a subclass ofUIViewController, and connect your class file with theUIViewControllerin the Identity Inspector.Add a title, a UILabel to display the account linking status, a
UIButtonto close this page, and aUIActivityIndicatorViewto show a loading animation while the application calls 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 logged in", 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 Succeeded", 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()
Use the access token in your skill.
After the user successfully enables your skill and links Alexa to your service, requests sent to your skill include the user's access token. Your skill code needs to retrieve the access token from the request, validate it, and use it to retrieve the necessary user information from the resource server. For more details on how to validate and use the access token, see the documentation for your skill type:
Cloud API reference
/living/voice/alexa/apptoapp/config/get
Obtains the redirect parameters for Alexa App-to-App.
Version: 1.0.0
Request parameters
No request parameters.
Parameter |
Data Type |
Structure |
Required |
Default Value |
Example input parameters |
Description |
Child Parameter Type |
No data |
|||||||
Response parameters
Standard:
Parameter Name |
Data Type |
Structure |
Backend Parameter Name |
Description |
Child Parameter Type |
code |
Integer |
code |
The response code. A value of 200 indicates success. |
||
message |
String |
message |
The error message. |
||
localizedMsg |
String |
localizedMsg |
The localized error message. |
||
data |
JSON |
Struct |
data |
The response data. |
|
lwaFallbackUrl |
String |
lwaFallbackUrl |
The LWA fallback URL. If the user does not have the Alexa app installed, a webview opens this URL for LWA authorization. |
||
alexaAppUrl |
String |
alexaAppUrl |
The URL to open when the user has the Alexa app installed. |
/living/account/oauth2/code/get
Generates an external authorization AuthCode for the current logon account based on OAuth 2.0 rules.
Version: 1.0.0
Request parameters
Parameter |
Data Type |
Structure Type |
Required |
Default Value |
Sample input parameters |
Description |
Child Parameter Type |
redirectUri |
String |
Yes |
The callback URL for the third party that requested authorization after the OAuth 2.0 authentication process is complete. |
||||
state |
String |
No |
The state carried by the third party that requested authorization during the OAuth 2.0 authentication process. |
||||
clientId |
String |
Yes |
The client_id of the third-party service provider that requested authorization during the OAuth 2.0 authentication process. |
||||
scope |
JSON |
Array |
No |
The scope of permissions requested by the third-party service provider during the OAuth 2.0 authorization process. |
String |
Response parameters
Standard:
Parameter name |
Data Type |
Structure |
Backend Parameter Name |
Description |
Child Parameter Type |
code |
Integer |
code |
The response code. A value of 200 indicates success. |
||
message |
String |
message |
The error message. |
||
localizedMsg |
String |
localizedMsg |
The localized error message. |
||
data |
String |
data |
The generated AuthCode. |
/living/voice/alexa/apptoapp/launch
After the Alexa App-to-App flow obtains the configuration and redirects to the Alexa app or LWA page to obtain the AuthCode issued by Alexa, this API is called to submit the code to the cloud and complete the binding process.
Version: 1.0.1
Request parameters
Parameter |
Data Type |
Structure |
Required |
Default Value |
Sample input parameters |
Description |
Child Parameter Type |
code |
String |
Yes |
The Alexa LWA authorization authCode. |
||||
state |
String |
Yes |
The OAuth 2.0 authentication parameter state. |
Response parameters
Standard:
Parameter name |
Data Type |
Structure |
Backend Parameter Name |
Description |
Child Parameter Type |
code |
Integer |
code |
The response code. A value of 200 indicates success. |
||
message |
String |
message |
The error message. |
||
localizedMsg |
String |
localizedMsg |
The localized error message. |
Appendix: Glossary
Terms
Service
The service you provide. For example, you might have a web-based service "Ride Hailer" that allows users to order a taxi.
App
The application your users use to interact with your service. Continuing the previous example, you might have a "Ride Hailer" app.
Skill
An Alexa skill that enables users to interact with your service using Alexa.
Alexa app
The Amazon Alexa app that users can download for their mobile devices. To download, see Alexa App.
Login with Amazon (LWA)
An authentication system that allows users to log on and grant access to their user profile data. For App-to-App Account Linking, LWA is a fallback that you can implement to handle cases where the user does not have the Alexa app installed on their mobile device. For general information about LWA, see Login with Amazon (LWA).
OAuth 2.0
An authentication standard through which your service can allow Alexa to access information in the user's account with your service, with the user's permission. For the OAuth 2.0 standard, see OAuth 2.0.
App Link
A deep link on Android that a user clicks to start an application. For more details on App Links, see the Android documentation.
Universal Link
A deep link on iOS that a user clicks to start an application. For more details on Universal Links, see the iOS documentation.
Name |
Explanation |
Example |
Alexa App URL |
Your app uses this Universal Link (iOS) or App Link (Android) to redirect the user to the Alexa app to confirm the linking request. For general information about Universal Links (iOS), see Universal Link Content . You can store the Alexa app URL and LWA fallback URL in your backend and retrieve them with a You must send a request to retrieve them. You can store the URL in local storage to avoid additional network calls. If you modify the skill security configuration file, a rebuild may be required. |
|
LWA fallback URL |
A link that redirects the user to LWA to enter their Amazon account information. It works for iOS, Android, and websites. Your app uses this option if the Alexa app is not installed on the user's device. |
|
App redirect URLs |
The Alexa app (or LWA, if the Alexa app is not installed) uses this Universal Link (iOS) or App Link (Android) to redirect the user back to your app after the user confirms the linking request in the Alexa app or LWA. The returns a parameter that provides your app with the user's Amazon authorization code, which is valid for 5 minutes. |
You can specify one or more of these using the Alexa developer console, ASK CLI, or SMAPI. For more information, see 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. The Alexa app can later pass this code to the authorization server to retrieve an access token that uniquely identifies the user with your service. |
This link is only for regular (non-App-to-App) account linking. You can specify this using the developer console, ASK CLI, or SMAPI. |
Access token URL |
Alexa uses your token server to exchange the user's authorization code (for your service) for an access token to complete the account linking. |
You can specify this using the Alexa developer console, 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: |
Alexa Skill Activation API |
Your backend server calls this function to enable the user's skill. For more details on the Alexa Skill Activation API, see Alexa Skill Activation API. |
Depending on whether the user activation is local, the original URL can be https://api.amazonalexa.com, https://api.eu.amazonalexa.com, or https://api.fe.amazonalexa.com |
URLs and parameters
Parameters for the Alexa app URL and LWA fallback URL
Field |
Description |
Client ID |
The client ID provided by the Alexa developer console when you enable App-to-App Account Linking. |
Client secret |
The client secret provided by the Alexa developer console when you enable App-to-App Account Linking. |
Redirect URL |
The Universal Link or App Link specified when you enable App-to-App Account Linking in the Alexa developer console. The Alexa app and LWA redirect the user back to this URL after they confirm the account linking request. |
Scope |
The scope. Your app must set this to |
Skill ID |
The unique identifier for your skill. You can find it in the developer console. |
Stage |
The skill stage. This depends on whether your skill has been published. Before your skill is published, set |
response type |
The response type must be |
state |
An opaque value used by the application to maintain the state between the current request and response. The state value is required for the Alexa app URL. Alexa includes this state in the response when it redirects the user back to your app using the redirect URL. You must use the state to validate incoming requests to prevent cross-site request forgery. For more information, see Cross-Site Request Forgery. Make sure the generated state does not contain the following characters: |