# Google Sign-In

Exchange a Google ID token for Aurea tokens. The matching user is signed in, or a new user is created on first use.

## Overview

Your app obtains a Google ID token and sends it here. Aurea verifies it with Google and returns the same token set as [Login](https://docs.aureahub.com/docs/auth-login.md): `accessToken`, `refreshToken`, `expiresIn` and `user`, plus `isNewUser: true` when this call created the account.

- **Tenant.** The user belongs to the tenant identified by the `x-tenant-api-key` signing header. A `tenantApiKey` body field is accepted but does not change the tenant.
- **Tenant configuration.** The tenant's configuration must contain a Google *web client ID*. Without one the call returns `400` *Google Sign-In is not enabled for this tenant*.
- **Audience.** The ID token is verified with that web client ID as the expected audience, so it must be issued for it.
- **Email.** Google must report the account's email as verified, and the token must carry an email address.
- **No wallet** is created by this endpoint.

> ⚠️ This endpoint requires **tenant request signing**: send `x-tenant-api-key`, `x-timestamp` and `x-signature` as described in [Authentication](https://docs.aureahub.com/docs/authentication.md). It is rate-limited to **20 requests per minute per client IP**.

## Endpoint

### `POST /v1/auth/google`

Verifies a Google ID token, then signs in the matching user or creates a new one. Requires tenant HMAC signing headers.

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `idToken` | string | yes | Google ID token issued for the tenant's configured Google web client ID |
| `tenantApiKey` | string | no | Accepted (at least 32 characters) but not used: the tenant comes from the x-tenant-api-key header |

**Responses**

`200` OK

```json
{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIs...",
  "expiresIn": 900,
  "user": {
    "id": "<user-id>",
    "username": "alice",
    "email": "alice@gmail.com",
    "qrCode": "<qr-code>",
    "role": "user",
    "createdAt": "2026-09-01T10:00:00.000Z"
  },
  "isNewUser": true
}
```

`400` Not Enabled

```json
{ "error": "BadRequestError", "message": "Google Sign-In is not enabled for this tenant" }
```

`401` Invalid Token

```json
{ "error": "UnauthorizedError", "message": "Invalid Google ID token" }
```

`409` Conflict

```json
{ "error": "ConflictError", "message": "This email is already linked to a different Google account" }
```

`422` Email Not Verified

```json
{ "error": "ValidationError", "message": "Google account email is not verified" }
```

`429` Too Many Requests

```json
{ "statusCode": 429, "error": "Error", "message": "Rate limit exceeded, retry in 1 minute" }
```

**Example request**

```bash
curl -X POST https://api.aureahub.com/v1/auth/google \
  -H "Content-Type: application/json" \
  -H "x-tenant-api-key: <your-tenant-api-key>" \
  -H "x-timestamp: <unix-seconds>" \
  -H "x-signature: <hex-hmac-sha256>" \
  -d '{"idToken":"<google-id-token>"}'
```

## Account Matching

After the token is verified, the user is resolved inside the tenant in this order:

1. **Linked account.** If the Google account is already linked to an active user, that user is signed in.
2. **Same email.** Otherwise, if a user with the same email exists and has no Google account linked, the Google account is linked to that user and they are signed in. If that user already has a *different* Google account linked, the call returns `409`. If that user is not active, it returns `401` *Account is not active*.
3. **New account.** Otherwise a user is created with no password. The username is the part of the email before `@`, with any character other than letters, digits, `_` and `-` replaced by `_`; if that username is taken, `_1`, `_2`, … is appended. The response includes `isNewUser: true`.

`isNewUser` is omitted when an existing user was signed in. Users created here cannot use password [Login](https://docs.aureahub.com/docs/auth-login.md) (`401` *This account uses social sign-in*).

## Errors

On this endpoint, `400`, `401`, `409` and `422` bodies contain only `error` and `message`; `404` and `429` use the full envelope described in [Error Handling](https://docs.aureahub.com/docs/errors.md).

| Status | message | When |
| --- | --- | --- |
| 400 | `Request validation failed` | idToken is missing or empty. |
| 400 | `Google Sign-In is not enabled for this tenant` | The tenant has no Google web client ID configured. |
| 400 | `Google account has no email address` | The verified token carries no email. |
| 401 | `Invalid Google ID token` | Verification failed: for example a bad signature, an expired token, or a token issued for another client ID. |
| 401 | `Account is not active` | The existing user with the same email is not active. |
| 401 | `Missing required HMAC headers: x-tenant-api-key, x-timestamp, x-signature` | Signature problem. Other messages: Invalid x-timestamp header, Request expired, Invalid signature, Replay detected. See Authentication. |
| 404 | `Invalid API key` | The x-tenant-api-key header does not match an active tenant. |
| 409 | `This email is already linked to a different Google account` | A user with this email is linked to another Google account. |
| 422 | `Google account email is not verified` | The token reports the email as unverified. |
| 429 | `Rate limit exceeded, retry in 1 minute` | More than 20 requests in one minute from the same client IP. See the retry-after header. |

## Implementation

Forward the ID token with the tenant signature. Sign and send the same body string:

```javascript
import { createHash, createHmac } from 'node:crypto';

const API = 'https://api.aureahub.com';

// Tenant request signing: see Authentication → HMAC Request Signing
function signedHeaders(method, pathWithQuery, body = '') {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const bodyHash = createHash('sha256').update(body).digest('hex');
  const signature = createHmac('sha256', process.env.AUREA_TENANT_API_SECRET)
    .update(`${method}\n${pathWithQuery}\n${timestamp}\n${bodyHash}`)
    .digest('hex');
  return {
    'x-tenant-api-key': process.env.AUREA_TENANT_API_KEY,
    'x-timestamp': timestamp,
    'x-signature': signature,
  };
}

// idToken: the Google ID token your app obtained for the tenant's web client ID
export async function signInWithGoogle(idToken) {
  const path = '/v1/auth/google';
  const body = JSON.stringify({ idToken });

  const res = await fetch(API + path, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', ...signedHeaders('POST', path, body) },
    body,
  });
  const data = await res.json();

  if (!res.ok) {
    throw new Error(`${res.status}: ${data.message ?? data.error}`);
  }

  const { accessToken, refreshToken, expiresIn, user } = data;
  return { accessToken, refreshToken, expiresIn, user, isNewUser: data.isNewUser === true };
}
```

---

Web version: https://docs.aureahub.com/#auth-google
