Hyphen
  • Hyphen
  • Concepts
    • Auth Methods
    • Hyphen Multi-Sig Account
      • Device Key
      • Recovery Key
      • Server Key
    • Paymaster
    • Hybrid Custody
  • Mechanism Paper
  • iOS SDK
    • Quick Start
    • Authenticating Users
    • Using Hyphen UI Kit
      • Account Management Screen
    • Sample Project
  • Android SDK
    • Quick Start
    • Authenticating Users
    • Handling 2FA / Transaction Request
    • Using Hyphen UI Kit
    • Sending Transactions
  • Flutter SDK
    • Quick Start
    • Authenticating Users
    • Using Hyphen UI Kit
    • Sending Transactions
    • Example App
  • Hybrid Custody
    • Into the Hybrid Custody
  • Without Using SDK
    • Authenticating Users
    • Handling 2FA Push
  • REST API
    • API Reference
      • Account
      • Auth
      • Device
      • Key
      • Sign
    • Swagger
Powered by GitBook
On this page

Was this helpful?

  1. Without Using SDK

Authenticating Users

Using REST API directly to sign in or up with Hyphen account.

PreviousInto the Hybrid CustodyNextHandling 2FA Push

Last updated 1 year ago

Was this helpful?

Currently, Hyphen only supports for OAuth 2.0 Provider. To add your own provider, please get in touch with us!

Process

  1. Request Firebase Auth to the user. The client will get a Firebase ID token.

  2. In the client, check that the device key exists on the keystore.

    1. If It does, go to

    2. Else, go to

A. Signing In with Challenge

  1. Call Auth Request Challenge API. The client needs to send a request with the device's public key and the Firebase ID token.

    1. If HTTP 200 -> The server will respond with random challenge data. Go to A.2.

    2. If HTTP 400 with PleaseSignUp -> Go to

  1. Call Auth Finish SignIn Challenge API. Send the signature along with the original challenge data. The signature should be encoded in a hex string.

    1. If HTTP 400 with PleaseDeploy: The account contract needs to be set up on the chain you're using.

B. Signing In with 2FA

On the source device (i.e. who currently tries to sign in), It should request 2FA Sign-in.

  1. Generate a Device Key. The device key needs to be stored on the device's HSM keystore, such as Secure Enclave or Android Keystore. The keypair should be ECDSA with P-256 (secp256p1) curve.

  2. Call Auth Request 2FA API: Send a request with the device registration data, including the public key generated in the previous step and the push token of the device.

    • If HTTP 200 -> The server will respond with the 2FA request data. A push will be sent on the random device registered on the user's account.

On the other device which is selected for 2FA target, it should receive a Firebase push with the 2FA request data.

  1. Request user's approval: Make sure that the client is displaying enough information for the user to know about the request.

    1. If approves -> Go to step 2.

    2. If denies -> Call Deny 2FA request API to cancel the request.

  2. Send the transaction in the request using the device key.

  3. Sign the payload in the request using the device key. Note that the payload is hex-encoded, so you need to decode it before signing it.

  4. Call Approve 2FA request API: with the signature.

  5. Close the screen.

Now go back to the source device. It will receive a foreground data push as the status changes.

  1. Receive the silent push: 2fa-status-update.

    1. If approved -> a transaction ID, credentials, and account info will be given. Go to step 2.

    2. If denied -> Show the user an error message that the other device rejected the request.

  2. Wait for the transaction ID to confirm. The transaction registers your new key to the account.

C. Creating an Account

  1. Generate a Device Key. The device key needs to be stored on the device's HSM keystore, such as Secure Enclave or Android Keystore. The keypair should be ECDSA with P-256 (secp256p1) curve.

  2. Call Sign Up API. Send a request with the device registration data, including the public key generated in the previous step and the push token of the device.

    • If HTTP 200 -> Go to D. Finishing Authorization

    • If HTTP 400 with PleaseDeploy: The account contract needs to be set up on the chain you're using.

D. Finishing Authorization

  1. The server will respond with the credentials and account info.

  2. Store the credential data in the local storage. The credential object consists of a JWT access token and a refresh token.

All done! The client can redirect the user to the home screen of your application.

Sign the Challenge Data with the Device Key. The client should request the device keystore to sign the challenge data received from the server. The data is hex-encoded but don't decode the hex before signing it— just sign the UTF-8 binary data directly. The signature should be ECDSA P-256 signature with SHA2-256 digest, encoded in format (a.k.a. r||s concat format).

If HTTP 200 -> Go to

Go to

IEEE P.1363
D. Finishing Authorization
D. Finishing Authorization.
Firebase Auth
A. Signing In with Challenge
B. Signing In with 2FA.
C. Creating an Account.
  • Process
  • A. Signing In with Challenge
  • POSTStarts SignIn Challenge
  • POSTFinish SignIn Challenge
  • B. Signing In with 2FA
  • POSTSign In with 2FA
  • POSTFinish Sign In with 2FA
  • C. Creating an Account
  • POSTCreate Account
  • D. Finishing Authorization

Starts SignIn Challenge

post

Starts SignIn Challenge. This is required when the user is trying to sign in from the device already registered. The client should request with the public key of the device. If it's not registered, it will raise HTTP 400 with PleaseRegisterKey error.

Issues a random 32-byte challengeData to the client. The client should respond by signing the data with the device's key, within 5 minutes.

The challenge type should be specified with either passKey or deviceKey.

Body
challengeTypestring · enumRequiredPossible values:
publicKeystringRequired

The public key of the PassKey in client-side. Should be 64-byte hex string concatenated with [x: 32byte, y: 32byte].

Responses
200
Ok
application/json
post
POST //auth/v1/signin/challenge HTTP/1.1
Host: api.dev.hyphen.at
Content-Type: application/json
Accept: */*
Content-Length: 199

{
  "challengeType": "deviceKey",
  "request": {
    "method": "firebase",
    "token": "eyJhbGciOiJIUzI...Qedy-rosPJLzs3jArh6Vc",
    "chainName": "flow-mainnet"
  },
  "publicKey": "text",
  "passKey": {
    "username": "jun@meowauth.xyz"
  }
}
200

Ok

{
  "challengeData": "deadbeefdeadbeefdeadbeefdeadbeef",
  "expiresAt": "2023-08-01T00:00:00.000Z"
}

Finish SignIn Challenge

post

Finishes sign in challenge. The client should provide the signature of the challengeData issued by the server. If valid, it will return the API credentials and account information.

Body
challengeTypestring · enumRequiredPossible values:
challengeDatastringRequired
Responses
200
Ok
application/json
post
POST //auth/v1/signin/challenge/respond HTTP/1.1
Host: api.dev.hyphen.at
Content-Type: application/json
Accept: */*
Content-Length: 227

{
  "challengeType": "deviceKey",
  "challengeData": "text",
  "deviceKey": {
    "signature": "text"
  },
  "passKey": {
    "clientDataJSON": "text",
    "authenticatorData": "h4tyVoWj/Tc2Lzxtu79kl9kYtTtraUYwBbIV1ymJaugdAAAAAA==",
    "signature": "MEUCI...p33Sz9c="
  }
}
200

Ok

{
  "account": {
    "id": "text",
    "addresses": [
      {
        "address": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
        "profileImageUrl": "text",
        "domainName": "vitalik.eth",
        "chainName": "flow-mainnet",
        "chainId": 80001,
        "chainType": "evm"
      }
    ],
    "parent": [
      {
        "address": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
        "profileImageUrl": "text",
        "domainName": "vitalik.eth",
        "chainName": "flow-mainnet",
        "chainId": 80001,
        "chainType": "evm"
      }
    ],
    "createdAt": "2025-06-16T04:34:36.738Z",
    "updatedAt": "2025-06-16T04:34:36.738Z"
  },
  "transaction": {
    "id": "text",
    "chainName": "flow-mainnet",
    "refUrl": "text"
  },
  "credentials": {
    "accessToken": "eyJhbGciOiJIUzI...Qedy-rosPJLzs3jArh6Vc",
    "refreshToken": "eyJhbGciOiJIUzI...Qedy-rosPJLzs3jArh6Vc"
  }
}

Sign In with 2FA

post

Sign in with two-factor authentication from other device. This is required when the user is trying to sign on the new device.

Body
Responses
200
Ok
application/json
post
POST //auth/v1/signin/2fa HTTP/1.1
Host: api.dev.hyphen.at
Content-Type: application/json
Accept: */*
Content-Length: 441

{
  "request": {
    "method": "firebase",
    "token": "eyJhbGciOiJIUzI...Qedy-rosPJLzs3jArh6Vc",
    "chainName": "flow-mainnet"
  },
  "userKey": {
    "type": "device",
    "publicKey": "text",
    "device": {
      "publicKey": "faceb00ccafebabedeadbeefbadf00defaceb00ccafebabedeadbeefbadf00de",
      "pushToken": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
      "name": "iPhone 14",
      "osName": "iOS",
      "osVersion": "16.2",
      "deviceManufacturer": "Apple",
      "deviceModel": "SM-265N",
      "lang": "en",
      "type": "mobile"
    }
  }
}
200

Ok

{
  "twoFactorAuth": {
    "id": "text",
    "accountId": "text",
    "request": {
      "id": "faceb00c-cafe-babe-badd-deadbeef1234",
      "app": {
        "appId": "swirl-dev",
        "appName": "Swirl"
      },
      "userOpInfo": {
        "type": "sign-in",
        "signIn": {
          "email": "john@acme.com",
          "ip": "127.0.0.1",
          "location": "Seoul, KR"
        }
      },
      "srcDevice": {
        "id": "deadbeef-dead-beef-cafe-deadbeefcafe",
        "publicKey": "faceb00ccafebabedeadbeefbadf00defaceb00ccafebabedeadbeefbadf00de",
        "sdkVersion": "1.0.0",
        "pushToken": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
        "name": "iPhone 14",
        "osName": "iOS",
        "osVersion": "16.2",
        "deviceManufacturer": "Apple",
        "deviceModel": "SM-265N",
        "lang": "en",
        "type": "mobile"
      },
      "destDevice": {
        "id": "deadbeef-dead-beef-cafe-deadbeefcafe",
        "publicKey": "faceb00ccafebabedeadbeefbadf00defaceb00ccafebabedeadbeefbadf00de",
        "sdkVersion": "1.0.0",
        "pushToken": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
        "name": "iPhone 14",
        "osName": "iOS",
        "osVersion": "16.2",
        "deviceManufacturer": "Apple",
        "deviceModel": "SM-265N",
        "lang": "en",
        "type": "mobile"
      },
      "message": "deadbeefdeadbeefdeadbeef",
      "requestedAt": "2021-01-01T00:00:00Z"
    },
    "status": "pending",
    "extra": null,
    "result": {
      "txId": "text"
    },
    "expiresAt": "2021-01-01T00:00:00Z"
  },
  "ephemeralAccessToken": "eyJhbGciOiJIUzI...Qedy-rosPJLzs3jArh6Vc"
}

Finish Sign In with 2FA

post

Finish sign in after the user authorized the request on the other device. This API will return the API credentials and account information.

Authorizations
Body
twoFactorAuthRequestIdstringRequired

The latest 2FA request ID.

Example: deadbeef-dead-beef-dead-beefdeadbeef
Responses
200
Ok
application/json
post
POST //auth/v1/signin/2fa/finish HTTP/1.1
Host: api.dev.hyphen.at
Authorization: Bearer JWT
Content-Type: application/json
Accept: */*
Content-Length: 65

{
  "twoFactorAuthRequestId": "deadbeef-dead-beef-dead-beefdeadbeef"
}
200

Ok

{
  "account": {
    "id": "text",
    "addresses": [
      {
        "address": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
        "profileImageUrl": "text",
        "domainName": "vitalik.eth",
        "chainName": "flow-mainnet",
        "chainId": 80001,
        "chainType": "evm"
      }
    ],
    "parent": [
      {
        "address": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
        "profileImageUrl": "text",
        "domainName": "vitalik.eth",
        "chainName": "flow-mainnet",
        "chainId": 80001,
        "chainType": "evm"
      }
    ],
    "createdAt": "2025-06-16T04:34:36.738Z",
    "updatedAt": "2025-06-16T04:34:36.738Z"
  },
  "transaction": {
    "id": "text",
    "chainName": "flow-mainnet",
    "refUrl": "text"
  },
  "credentials": {
    "accessToken": "eyJhbGciOiJIUzI...Qedy-rosPJLzs3jArh6Vc",
    "refreshToken": "eyJhbGciOiJIUzI...Qedy-rosPJLzs3jArh6Vc"
  }
}

Create Account

post

Creates new account and register the current device's key as the initial user key. This involves deploying the account on the chain you've specified.

Body
methodstring · enumRequired

The login channel the user is currently signing in/up.

Example: firebasePossible values:
tokenstringRequired

The access token / ID token / redirect code retrieved as a result of OAuth 2.0 Sign In. Server uses it to call the OAuth provider to verify that the client correctly finished OAuth flow, and fetches users' basic profile information such as email.

Its value differs by channel:

  • For apple, it's the ID token returned after finishing the SIWA process from the client.
  • For firebase, it's the redirect code from OAuth2 Redirect URI.
Example: eyJhbGciOiJIUzI...Qedy-rosPJLzs3jArh6Vc
chainNamestring · enumRequiredPossible values:
userKeyany ofRequired

The user key (usually device key / PassKey) created from the client SDK. Used as one of initial multi-sig keys for creating an account.

or
or
Responses
201Success
application/json
post
POST //auth/v1/signup HTTP/1.1
Host: api.dev.hyphen.at
Content-Type: application/json
Accept: */*
Content-Length: 429

{
  "method": "firebase",
  "token": "eyJhbGciOiJIUzI...Qedy-rosPJLzs3jArh6Vc",
  "chainName": "flow-mainnet",
  "userKey": {
    "type": "device",
    "publicKey": "text",
    "device": {
      "publicKey": "faceb00ccafebabedeadbeefbadf00defaceb00ccafebabedeadbeefbadf00de",
      "pushToken": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
      "name": "iPhone 14",
      "osName": "iOS",
      "osVersion": "16.2",
      "deviceManufacturer": "Apple",
      "deviceModel": "SM-265N",
      "lang": "en",
      "type": "mobile"
    }
  }
}
201Success
{
  "account": {
    "id": "text",
    "addresses": [
      {
        "address": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
        "profileImageUrl": "text",
        "domainName": "vitalik.eth",
        "chainName": "flow-mainnet",
        "chainId": 80001,
        "chainType": "evm"
      }
    ],
    "parent": [
      {
        "address": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
        "profileImageUrl": "text",
        "domainName": "vitalik.eth",
        "chainName": "flow-mainnet",
        "chainId": 80001,
        "chainType": "evm"
      }
    ],
    "createdAt": "2025-06-16T04:34:36.738Z",
    "updatedAt": "2025-06-16T04:34:36.738Z"
  },
  "transaction": {
    "id": "text",
    "chainName": "flow-mainnet",
    "refUrl": "text"
  },
  "credentials": {
    "accessToken": "eyJhbGciOiJIUzI...Qedy-rosPJLzs3jArh6Vc",
    "refreshToken": "eyJhbGciOiJIUzI...Qedy-rosPJLzs3jArh6Vc"
  }
}