README.md

# AppleAuth

Elixir library for Apple Sign in with Apple (SIWA) authentication. Validates Apple identity tokens against Apple's JWKS public keys and exchanges authorization codes for tokens.

## Installation

Add `apple_auth` to your dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:apple_auth, "~> 0.1.0"}          # or from Hex once published
  ]
end
```

## Configuration

```elixir
# config/runtime.exs or config/config.exs
config :apple_auth,
  bundle_id: System.get_env("APPLE_BUNDLE_ID") || "com.example.app",
  team_id: "YOUR_TEAM_ID",
  key_id: "YOUR_KEY_ID",
  client_secret_pem: System.get_env("APPLE_CLIENT_SECRET_PEM")  # ES256 private key
```

| Key                 | Env var                   | Required for     |
| ------------------- | ------------------------- | ---------------- |
| `bundle_id`         | `APPLE_BUNDLE_ID`         | Token validation |
| `team_id`           | `APPLE_TEAM_ID`           | Code exchange    |
| `key_id`            | `APPLE_KEY_ID`            | Code exchange    |
| `client_secret_pem` | `APPLE_CLIENT_SECRET_PEM` | Code exchange    |

Config resolution order: **opts keyword > Application env > System env var**.

## Usage

### Validate an identity token

```elixir
case AppleAuth.validate_id_token(token) do
  {:ok, %AppleAuth.Claims{sub: user_id, email: email}} ->
    # user_id is the stable Apple user identifier
    {:ok, user_id}

  {:error, reason} ->
    {:error, reason}
end
```

### Exchange an authorization code for tokens

```elixir
case AppleAuth.exchange_authorization_code(authorization_code) do
  {:ok, %{id_token: id_token, access_token: access_token, refresh_token: refresh_token}} ->
    # Validate the id_token next
    AppleAuth.validate_id_token(id_token)

  {:error, reason} ->
    {:error, reason}
end
```

### Phoenix Plug

Protect routes by adding the plug to your pipeline:

```elixir
# In your router
pipeline :apple_authenticated do
  plug AppleAuth.Plug, on_invalid_token: :unauthorized
end

scope "/api" do
  pipe_through [:api, :apple_authenticated]
  # ...
end
```

The plug extracts the Bearer token from the `Authorization` header, validates it, and assigns the result to `conn.assigns.apple_auth`.

Options for `on_invalid_token`:

- `:ignore` (default) — pass through, assigns remain `nil`
- `:unauthorized` — send 401 and halt
- `{:assign_error, key}` — assign the error to `conn.assigns[key]`

Access claims in your controller:

```elixir
def index(conn, _params) do
  %AppleAuth.Claims{sub: user_id} = conn.assigns.apple_auth
  # ...
end
```

## License

MIT