Cotter
  • 🚀Getting Started
  • Features & Concepts
    • 💬Sign In with Email/Phone Number
    • 🔐Sign In with Device
      • How it works
    • 🧬Sign In with WebAuthn
  • 📌Quickstart Guides
    • All Guides & Tutorials
    • HTML – Sign in with Email/Phone
    • React – Sign in with Email/Phone
    • React – WebAuthn
    • ▲ Next.js
    • Angular
    • Webflow
    • Bubble.io
    • Python SDK for a CLI
    • React Native – Sign in with Device
    • iOS – Sign in with Device
    • Flutter – Sign in with Device
  • 📘SDK Reference
    • Web
      • Sign In with Email/Phone Number
        • Customize the Form
        • Checking the email or phone before sending a verification code
        • Sending Code or Link via WhatsApp
        • Styling
        • Older SDK
          • Customize the Form
      • Sign in with Social Login
        • Getting Access Tokens from Social Login Providers
        • Github Instructions
        • Google Instructions
      • Sign In with WebAuthn
        • Register WebAuthn for a logged-in user
      • Sign In with Device
        • Steps for Pop Up Authentication Prompt
        • Advanced Customization for Login Form
        • Advanced Customization for Pop Up Authentication Prompt
      • Getting Access Token and Logged-In User Info
      • Sending Successful Form Submission
      • FAQ & Troubleshooting
    • React Native
      • Installation
      • Sign In with Device
        • Add Email/Phone Verification
        • Authenticate from a Non-Trusted Device
        • Add a new Trusted Device
        • Remove Trusted Device
      • Sign In with Email/Phone Number
      • Getting Stored OAuth Tokens and User Information
      • FAQ
      • Older SDK Versions
        • Sign in with Email/Phone
        • Sending Code via WhatsApp
        • Sign In with Device
          • Authenticate from a Non-Trusted Device
          • Add a new Trusted Device
          • Customization
    • Flutter
      • Sign In with Device
        • Add Email/Phone Verification
        • Authenticate from a Non-Trusted Device
      • Sign in with Email/Phone Number
      • Getting the Logged-in User
      • Getting OAuth Tokens
      • Signing a User Out
    • iOS
      • Sign In with Email/Phone Number
      • Sign In with Device
        • Authenticate from a Non-Trusted Device
        • Push Notification
        • Check if Trusted Device is Enrolled
        • Add a New Trusted Device
        • Remove Trusted Device
      • Older Versions
        • Biometric/Pin
    • Android
      • Sign In with Device
        • Authenticate from a Non-Trusted Device
        • Check if Trusted Device is Enrolled
        • Add a new Trusted Device
        • Remove Trusted Device
        • Customization
      • Sign In with Email/Phone Number
      • Biometric/Pin
        • Advanced Methods
        • Customization
        • Setting Strings
        • Styling
      • Older SDK Version
        • Sign In with Device
          • Authenticate from a Non-Trusted Device
    • Python (for CLI)
    • API for Other Mobile Apps or CLI
      • Verify Email/Phone Number
        • Handling URL Scheme
    • Backend: Handling Response
  • 🛡️ Protecting Your Account
    • Only Allow Your Website/App to Use Your API Key
    • Rate Limit
    • Enable reCAPTCHA to Protect Against Automated Abuse
  • 🗝️ Getting Access Token
    • Cotter's OAuth 2.0 Tokens Specification
    • Getting the Tokens
      • Get Tokens during Authentication
      • Using the Refresh Token
    • Storing and Removing Tokens
    • Renewing Expired Tokens
    • Verifying JWT Tokens
    • Requesting Custom Fields on your JWT Token
    • Older API
      • Using HTTP Requests
      • Getting the Tokens
        • During Authentication
          • During Email/Phone Verification
        • During enrolling Trusted Devices
  • 🔌API Reference
    • User API
      • User Object
    • OAuth Tokens API
      • Verify JWT Token using API (serverless)
      • Requesting Custom Claims on your Access Token
      • Older API
    • OAuth Tokens from Social Login
    • Event Object
    • Reset PIN API
  • Older API
    • Validating Cotter's Identity Token
    • Validating Cotter's Event Response
Powered by GitBook
On this page
  • Overview
  • What you're building
  • Authorization Flow
  • Steps
  • Step 1: Create a Code Verifier
  • Step 1-b: Create a Code Challenge from Code Verifier
  • Step 2: Request Authorization from Cotter
  • Response
  • Step 3: Request Tokens and Identity
  • Request Tokens
  • Step 4: Include the Token to your Server
  • Validating Cotter's Access Token
  • 🎉 You're done!
  • Securing your Project
  • What are State and Code Challenge?
  1. SDK Reference
  2. API for Other Mobile Apps or CLI

Verify Email/Phone Number

Authentication API that can be called from your mobile apps. This API utilizes an in-app webview with cookies sharing to allow a single-sign-on for bypassing email and phone number verification.

PreviousAPI for Other Mobile Apps or CLINextHandling URL Scheme

Last updated 4 years ago

Concepts: Learn about how works.

Overview

Verifying email and phone number in your mobile app using our Authentication API consists of the following steps:

  1. Open a WebView within your app with shared cookies

  2. Direct users to Cotter's Auth page

  3. Redirect back to your app with an authorization code

  4. Call Cotter API with the authorization code

  5. Get back the user's email or phone number, and whether or not it's verified

Here's an example on opening the in-app Browser from iOS and Android

  • Android: Use the

  • iOS: Use the

What you're building

Authentication API for Android and iOS

Authorization Flow

  • Mobile apps can't securely store the Secret Key. This is because decompiling the App will reveal the Secret Key, and there's only one secret key so it'll be the same for all users.

  • Sending tokens to Custom URL schemes (ex. YourApp://) will potentially expose the tokens to malicious apps.

Steps

Step 1: Create a Code Verifier

function dec2hex(dec) {
  return ('0' + dec.toString(16)).substr(-2)
}

function generateRandomString() {
  var array = new Uint32Array(56/2);
  window.crypto.getRandomValues(array);
  return Array.from(array, dec2hex).join('');
}

var verifier = generateRandomString();
import os
import base64
verifier_bytes = os.urandom(32)
code_verifier = base64.urlsafe_b64encode(verifier_bytes).rstrip(b'=')
// import android.util.Base64;
SecureRandom sr = new SecureRandom();
byte[] code = new byte[32];
sr.nextBytes(code);
String verifier = Base64.encodeToString(code, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
var buffer = [UInt8](repeating: 0, count: 32)
_ = SecRandomCopyBytes(kSecRandomDefault, buffer.count, &buffer)
let verifier = Data(bytes: buffer).base64EncodedString()
    .replacingOccurrences(of: "+", with: "-")
    .replacingOccurrences(of: "/", with: "\_")
    .replacingOccurrences(of: "=", with: "")
    .trimmingCharacters(in: .whitespaces)
NSMutableData *data = [NSMutableData dataWithLength:32];
int result __attribute__((unused)) = SecRandomCopyBytes(kSecRandomDefault, 32, data.mutableBytes);
NSString *verifier = [[[[data base64EncodedStringWithOptions:0]
                        stringByReplacingOccurrencesOfString:@"+" withString:@"-"]
                        stringByReplacingOccurrencesOfString:@"/" withString:@"_"]
                        stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"="]];

Step 1-b: Create a Code Challenge from Code Verifier

A code_challenge is the hashed version of your code_verifier. We will send this hash on step 2 when you're requesting an authentication from Cotter.

function sha256(plain) { // returns promise ArrayBuffer
  const encoder = new TextEncoder();
  const data = encoder.encode(plain);
  return window.crypto.subtle.digest('SHA-256', data);
}

function base64urlencode(a) {
      var str = "";
      var bytes = new Uint8Array(a);
      var len = bytes.byteLength;
      for (var i = 0; i < len; i++) {
        str += String.fromCharCode(bytes[i]);
      }
      return btoa(str)
        .replace(/\+/g, "-")
        .replace(/\//g, "_")
        .replace(/=+$/, "");
    }

async function challenge_from_verifier(v) {
  hashed = await sha256(v);
  base64encoded = base64urlencode(hashed);
  return base64encoded;
}

var challenge = await challenge_from_verifier(verifier);
import hashlib
import base64
challenge_bytes = hashlib.sha256(code_verifier).digest()
code_challenge = base64.urlsafe_b64encode(challenge_bytes).rstrip(b'=')
// import android.util.Base64;
byte[] codeVerifierBytes = codeVerifier.getBytes("US-ASCII");
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(codeVerifierBytes);
byte[] codeChallengeBytes = md.digest();
String codeChallenge = Base64.encodeToString(codeChallengeBytes, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
// Dependency: Apple Common Crypto library
// http://opensource.apple.com//source/CommonCrypto
guard let data = verifier.data(using: .utf8) else { return nil }
var buffer = [UInt8](repeating: 0,  count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
    _ = CC_SHA256($0, CC_LONG(data.count), &buffer)
}
let hash = Data(bytes: buffer)
let challenge = hash.base64EncodedString()
    .replacingOccurrences(of: "+", with: "-")
    .replacingOccurrences(of: "/", with: "\_")
    .replacingOccurrences(of: "=", with: "")
    .trimmingCharacters(in: .whitespaces)
// Dependency: Apple Common Crypto library
// http://opensource.apple.com//source/CommonCrypto
u_int8_t buffer[CC_SHA256_DIGEST_LENGTH * sizeof(u_int8_t)];
memset(buffer, 0x0, CC_SHA256_DIGEST_LENGTH);
NSData *data = [verifier dataUsingEncoding:NSUTF8StringEncoding];
CC_SHA256([data bytes], (CC_LONG)[data length], buffer);
NSData *hash = [NSData dataWithBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
NSString *challenge = [[[[hash base64EncodedStringWithOptions:0]
                         stringByReplacingOccurrencesOfString:@"+" withString:@"-"]
                         stringByReplacingOccurrencesOfString:@"/" withString:@"_"]
                         stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"="]];

The code_challenge is sent first so that later in step 3, Cotter's server can verify that hash(code_verifier) is the same as code_challenge and that you are indeed made the original request.

Checking your code challenge and verifier

Step 2: Request Authorization from Cotter

Open Cotter's Auth URL from a WebView from your app.

https://js.cotter.app/app?
    api_key=<api_key_id>
    &redirect_url=yourapp://
    &type=PHONE
    &code_challenge=<code_challenge>
    &state=<state>

Query Parameter

Type

Description

api_key

string

Your API_KEY_ID

redirect_url

string

Your app's URL scheme where Cotter Auth will redirect back your users to your app

Example: com.example.app:redirect_uri_path or http://127.0.0.1:port

type

string

EMAIL or PHONE

code_challenge

string

state

string

Here's an example on opening the in-app Browser from iOS and Android

// Using ASWebAuthenticationSession
// https://developer.apple.com/documentation/authenticationservices/authenticating_a_user_through_a_web_service

guard let authURL = URL(string: "https://js.cotter.app/app?api_key=<api_key_id>&redirect_url=yourapp://&type=PHONE&code_challenge=<code_challenge>&state=<state>") else { return }
let scheme = "yourapp://"


self.authSession = ASWebAuthenticationSession(url: authURL, callbackURLScheme: scheme)
{ callbackURL, error in
    // Handle the callback.
}
if #available(iOS 13.0, *) {
    self.authSession?.presentationContextProvider = self
} else {
    // Fallback on earlier versions
}
self.authSession?.start()
// Dependencies: Trusted Web Activity
// https://developers.google.com/web/updates/2019/02/using-twa

//build.gradle (Module:app)
android {
        ...
    compileOptions {
       sourceCompatibility JavaVersion.VERSION_1_8
       targetCompatibility JavaVersion.VERSION_1_8
    }
}
dependencies {
    implementation 'com.google.androidbrowserhelper:androidbrowserhelper:1.0.0'
}

// MainActivity.java
import com.google.androidbrowserhelper.trusted.TwaLauncher;

public class MainActivity extends AppCompatActivity {
    ...
    static Uri LAUNCH_URI = Uri.parse("https://js.cotter.app/app?api_key=<api_key_id>&redirect_url=yourapp://&type=PHONE&code_challenge=<code_challenge>&state=<state>");
    
    public void login(View view) {
        new TwaLauncher(this).launch(LAUNCH_URI);
    }
}
  1. Open the user's browser with the URL above, also display it so the user can click it if opening the browser doesn't work.

  2. Listen to the localhost port that you specified in the redirect URL

  3. When the browser redirect back to you with the response below, handle the request and use the code, state, and challenge_id to continue to Step 3.

Response

After the user's email or phone is verified, Cotter will redirect back to your app using redirect_url that you specified in step 2.

yourapp://?
    code=<authorization_code>
    &state=<state>
    &challenge_id=<challenge_id>

Step 3: Request Tokens and Identity

In this step, you'll use your code_verifier , authorization_code and the challenge_id to request tokens and the user's email or phone number from Cotter's server.

Your authorization_token is valid for 5 minutes, and can only be used once.

curl -XPOST \
-H 'Content-type: application/json' \
-H 'API_KEY_ID: <api_key_id>' \
-d '{
  "code_verifier": "<code_verifier>",
  "authorization_code": "<authorization_code>",
  "challenge_id": <challenge_id>,
  "redirect_url": "<redirect_url>"
}' 'https://www.cotter.app/api/v0/verify/get_identity?oauth_token=true'

Request Tokens

POST https://www.cotter.app/api/v0/verify/get_identity

Request for tokens and user's email or phone number verification state (successfully verified or not).

Query Parameters

Name
Type
Description

oauth_token

boolean

If true, will return OAuth Tokens (read "Handling Authentication with Cotter")

Headers

Name
Type
Description

API_KEY_ID

string

Your API_KEY_ID

Content-Type

string

application/json

Request Body

Name
Type
Description

code_verifier

string

Your code_verifier created in Step 1

authorization_code

string

The authorization_code received in Step 2

challenge_id

integer

The challenge_id received in Step 2

redirect_url

string

This MUST match the redirect_url you specified in Step 2

{
  "identifier": {
    "ID": "f4286df9-a923-429c-bc33-5089ffed5f68",
    "created_at": "2020-07-21T22:53:21.211367Z",
    "updated_at": "2020-07-21T22:53:21.211367Z",
    "deleted_at": "0001-01-01T00:00:00Z",
    "identifier": "putri@cotter.app", // User's email
    "identifier_type": "EMAIL",
    "device_type": "BROWSER",
    "device_name": "Mozilla/5.0 (Linux; Android 9; Android SDK built for x86 Build/PSR1.180720.075) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
    "expiry": "2020-08-20T22:53:21.19705Z",
    "timestamp": "2020-07-21T22:53:21.19705Z"
  },
  "oauth_token": {
    "access_token": "eyJhbGciOiJFUz...", // Validate this access token
    "id_token": "eyJhbGciOiJFUzI1...",
    "refresh_token": "27944:lb31DY5pG229n...",
    "expires_in": 3600,
    "token_type": "Bearer",
    "auth_method": "OTP"
  },
  "token": {...},
  "user": {
    "ID": "643a42c7-316a-4abe-b27e-f4d0f903bfea", // Cotter uesr ID
    "identifier": "putri@cotter.app",
    ...
  }
}
{
  "msg": "Challenge Expired"
}

Step 4: Include the Token to your Server

Now that the email or phone number is verified, you can continue your Sign Up or Login process by submitting the email or phone number to your server, either now or after the user enters more information.

You should include this oauth_tokens into your call to your backend for Login or Registration. Your backend should then verify that the access token is valid.

Validating Cotter's Access Token

Check out how to verify the OAuth Tokens from Cotter here:

🎉 You're done!

Securing your Project

Since you'll be using your API Key from a front-end website or mobile app, your API_KEY_ID is exposed to anyone inspecting your code. Here are some ways to prevent abuse:

What are State and Code Challenge?

State:

code_challenge / verifier:

For mobile apps, we're going to use the . This flow is recommended for Mobile Apps because:

and a code_challenge

: Redirect user to Cotter to verify their email/phone and receive an authorization_code back to your app.

: Send your authorization_code and code_verifier to Cotter server and get back a token and the user's email or phone number.

: The token contains the user's verified email/phone number and a signature. Include this to your signup/login request to your backend

A code_verifier is a cryptographically-random key that will be sent to Cotter along with the authorization_code on Step 3. Read more about what are .

To check if your code_challenge and code_verifier are correctly generated and formatted, try comparing it with codes generated here

The code_challenge you created in

A random string that you generate from your application before opening to Cotter's Auth (ex. abcXYZ456). This is not the same as your code_verifier. You need to check if the state included by Cotter in the redirect_url is the same as the initial state that you set to make sure the request is for you. Learn more about .

Make sure the scheme of your redirect_url (the front part before ://) doesn't have an underscore or other special characters. To test it out, enter your redirect_url here:

Android: Use the

iOS: Use the

You should check that the state is the same as the initial state you passed in to the URL .

Check out how to .

Your app generates state=XYZ in the beginning of the auth flow. You should expect that Cotter's response on when Cotter redirect back to your redirect_url, the state is the same (state == XYZ). This makes sure that the redirect was in response to your initial authentication request.

This is needed for installed apps / SPA because they cannot store the Api Secret Key securely, so the code_challenge and code_verifier is for Cotter to make sure that the original App that requested authentication on is the same as the one that asked for access token on .

📘
OAuth 2.0 Authorization Code Flow with Proof Key for Code Exchange (PKCE)
https://example-app.com/pkce
https://jsfiddle.net/omd02jn5/
Trusted Web Activity
ASWebAuthenticationSession
Handle the URL Scheme
Verifying JWT Tokens
Only allow your website/app to use your API Key
Rate Limit the number of authentication requests
Enable reCAPTCHA to prevent automated abuse
Create a code_verifier
Request Authorization from Cotter
Request Tokens and Identity
Include Token to your server
code challenge and verifier
here
Step 2
Step 2
Step 3
Step 1
state
Sign in with Email/Phone Number
Trusted Web Activity
ASWebAuthenticationSession