Enable Deep Linking
Step 3: Handle the Authorization Request in Your App
To start account linking, the Alexa service opens the configured Universal Link or App Link and makes an authorization request. In response, your app must show a consent screen to the user and get their consent to link their account. The authorization request follows the OAuth 2.0 specification for the authorization code grant type or the implicit grant type.
Authorization request to your app
The authorization request that opens your app looks like the following example. For a description of the parameters, see Query parameters.
https://yourAppAuthUrl?
response_type={Response Type}&
client_id={Client ID}&
scope={Scope}&
state={State}&
code_challenge={unique-id}&
code_challenge_method=S256&
redirect_uri={Redirect URI}
code_challenge and code_challenge_method parameters. Your authorization server must handle these parameters as defined in RFC 7636, and store them with the authorization code for token endpoint verification. PKCE isn't supported for the implicit grant type.Obtain the user's consent
On receipt of the authorization request to your app, obtain the user's consent for account linking and assemble the authorization response parameters.
To handle the authorization request in your app
- Extract the authorization request parameters from the Universal Link or App Link request.
- Present a screen in your app where the user can grant or deny access to the specific scopes requested.
- When the user accepts the authorization request, make a request to your authorization server to generate an authorization code (for the authorization code grant type) or an access token (for the implicit grant type).
- Include the authorization code or access token in the redirect that you use to send the user back to the Alexa app in Step 4: Redirect to the Alexa App.
- If the user denies the request, include the error in the redirect.
If the user doesn't have your app installed on their device, the authorization request URI opens in their browser. Here, use the same log-in and consent flow that you set up for Standard Account Linking.
iOS example
// Handler for Universal Links in AppDelegate
// Add this method to handle incoming Universal Links
static let EXPECTED_CODE_CHALLENGE_METHOD = "S256"
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL else {
return false
}
guard let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true),
let queryParameters = components.queryItems else {
return false
}
// If your skill opted-in to PKCE for an Authorization Code Grant,
// Alexa WILL include `code_challenge` and `code_challenge_method` in the `incomingURL`.
let AUTH_REQUEST_PARAMETERS: Set<String> = [
"redirect_uri", "state", "scope", "client_id", "response_type",
"code_challenge", "code_challenge_method"]
var validatedParameters : [String: String] = [:]
for queryParameter in queryParameters {
if AUTH_REQUEST_PARAMETERS.contains(queryParameter.name) {
validatedParameters[queryParameter.name] = queryParameter.value
}
}
// Validate PKCE parameters if Authorization Code Grant
if validatedParameters["response_type"] == "code" {
guard validatePkceParameters(parameters: validatedParameters) else {
Logger.error("Invalid PKCE parameters")
// Fail request since Alexa only supports S256 as code_challenge_method
}
}
let initialViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "appToAppConsentScreen") as! AppToAppScreenViewController
initialViewController.authorizationRequest = validatedParameters
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}
private func validatePkceParameters(parameters: [String: String]) -> Bool {
// If code challenge is present, validate the method
if let codeChallenge = parameters["code_challenge"],
!codeChallenge.isEmpty {
guard let codeChallengeMethod = parameters["code_challenge_method"],
codeChallengeMethod == EXPECTED_CODE_CHALLENGE_METHOD else {
logger.warning("Invalid code_challenge_method")
return false
}
}
return true
}
// Create this controller for the consent screen
class AppToAppScreenViewController: UIViewController {
var authorizationRequest : [String: String]?
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func acceptButtonClicked(_ sender: UIButton) {
// ** GUIDANCE FOR YOUR AUTHORIZATION SERVER (on user ACCEPT): **
// This applies if `response_type` indicates an Authorization Code Grant (e.g., "code").
// PKCE (RFC 7636) protects the Authorization Code Grant; it's NOT used for Implicit Grant.
//
// 1. If PKCE Opted-In (for Auth Code Grant): Alexa sends `code_challenge` & `code_challenge_method`.
// Your server MUST handle them as defined in RFC 7636 (store with auth code for token endpoint verification).
// 2. If PKCE Not Opted-In OR Implicit Grant: Proceed with standard grant flow (RFC 6749).
// 3. On Success: Issue auth code (for Auth Code Grant) or access token (for Implicit Grant).
// Then, redirect to `redirect_uri` with appropriate params & original `state`.
// See RFC 6749 & RFC 7636.
guard let responseUri =
callBackendForAuthorization(authorizationRequest: authorizationRequest, "ACCEPT") else {
return
}
// implementation of this method shown in the following step
openUrl(alexaUrl: responseUri)
}
@IBAction func denyButtonClicked(_ sender: UIButton) {
guard let responseUri =
callBackendForAuthorization(authorizationRequest: authorizationRequest, "DENY") else {
return
}
// implementation of this method shown in the following step
openUrl(alexaUrl: responseUri)
}
}
Android example
class AppToAppConsent : AppCompatActivity() {
// Constants
private val QUERY_PARAMETER_KEY_CLIENT_ID = "client_id"
private val QUERY_PARAMETER_KEY_RESPONSE_TYPE = "response_type"
private val QUERY_PARAMETER_KEY_STATE = "state"
private val QUERY_PARAMETER_KEY_SCOPE = "scope"
private val QUERY_PARAMETER_KEY_REDIRECT_URI = "redirect_uri"
private val QUERY_PARAMETER_KEY_CODE_CHALLENGE = "code_challenge";
private val QUERY_PARAMETER_KEY_CODE_CHALLENGE_METHOD = "code_challenge_method";
private val EXPECTED_CODE_CHALLENGE_METHOD = "S256";
private val USER_ACCEPT = "ACCEPT";
private val USER_DENY = "DENY";
// Incoming Query Parameters
private var clientId: String? = null
private var responseType: String? = null
private var state: String? = null
private var scope: String? = null
private var redirectUri: String? = null
private var url: String? = null
private var codeChallenge: String? = null
private var codeChallengeMethod: String? = null
private lateinit var acceptButton: Button
private lateinit var denyButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val appLinkData = intent.data
// Get values from App Link
clientId = appLinkData.getQueryParameter(QUERY_PARAMETER_KEY_CLIENT_ID)
responseType = appLinkData.getQueryParameter(QUERY_PARAMETER_KEY_RESPONSE_TYPE)
state = appLinkData.getQueryParameter(QUERY_PARAMETER_KEY_STATE)
scope = appLinkData.getQueryParameter(QUERY_PARAMETER_KEY_SCOPE)
redirectUri = appLinkData.getQueryParameter(QUERY_PARAMETER_KEY_REDIRECT_URI)
// Get PKCE (RFC 7636) values from App Link data if present.
// Alexa will send these if your skill opted-in to PKCE for an Authorization Code Grant.
codeChallenge = appLinkData.getQueryParameter(QUERY_PARAMETER_KEY_CODE_CHALLENGE);
codeChallengeMethod = appLinkData.getQueryParameter(QUERY_PARAMETER_KEY_CODE_CHALLENGE_METHOD);
// Validate PKCE parameters if Authorization Code Grant
if (responseType == "code" && !validatePkceParameters()) {
// Fail request since Alexa only uses S256 as code_challenge_method
}
setContentView(R.layout.activity_consent_screen)
initializeComponents()
}
private fun validatePkceParameters(): Boolean {
// If code challenge is present, validate the method
if (!codeChallenge.isNullOrEmpty()) {
if (codeChallengeMethod != EXPECTED_CODE_CHALLENGE_METHOD) {
logger.warning("Invalid code_challenge_method: $codeChallengeMethod")
return false
}
}
return true
}
private fun initializeComponents() {
acceptButton = findViewById(R.id.btn_accept_linking)
acceptButton.setOnClickListener {
// ** GUIDANCE FOR YOUR AUTHORIZATION SERVER (on user ACCEPT): **
// Applies if `response_type` is for Authorization Code Grant (e.g., "code").
// PKCE (RFC 7636) is for Auth Code Grant; NOT for Implicit Grant.
//
// 1. If PKCE Opted-In (for Auth Code Grant): Alexa sends `code_challenge` & `code_challenge_method`.
// Your server MUST handle as defined in RFC 7636 (store with auth code for token endpoint verification).
// 2. If PKCE Not Opted-In OR Implicit Grant: Standard grant flow (RFC 6749).
// 3. On Success: Issue auth code (Auth Code Grant) or access token (Implicit Grant).
// Redirect to `redirect_uri` with params & original `state`.
// See RFC 6749 & RFC 7636.
val responseUri = callBackendForAuthorization(clientId, responseType, state, scope, redirectUri, codeChallenge, codeChallengeMethod, USER_ACCEPT)
// implementation of this method shown in the following step
openUrl(responseUri)
}
denyButton = findViewById(R.id.btn_decline_linking)
denyButton.setOnClickListener {
val responseUri = callBackendForAuthorization(clientId, responseType, state, scope, redirectUri, USER_DENY)
// implementation of this method shown in the following step
openUrl(responseUri)
}
}
}
Last updated: frontmatter-missing