README.md

# BankID

Pure Elixir client for Swedish BankID authentication and signing.

This library provides a complete, framework-agnostic implementation of the Swedish BankID API v6.0 with no external dependencies beyond standard Elixir libraries.

## Disclaimer

This is an early version of the library and you are advised to use it at your own risk at this stage.

## Features

- ✅ **Pure Elixir** - No Python or other external dependencies
- ✅ **mTLS Support** - Secure client certificate authentication
- ✅ **Authentication** - Full support for BankID authentication flow
- ✅ **QR Code Generation** - Native QR code support for mobile apps
- ✅ **Test & Production** - Bundled test certificates, easy production setup
- ✅ **Framework Agnostic** - Use with Phoenix, Plug, or any Elixir application

## Installation

Add `bankid` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:bankid, "~> 0.1.0"}
  ]
end
```

Or for local development:

```elixir
def deps do
  [
    {:bankid, path: "../bankid"}
  ]
end
```

## Quick Start

```elixir
# 1. Start authentication
{:ok, auth} = BankID.authenticate("192.168.1.1")

# 2. Generate QR code for mobile BankID app
qr_svg = BankID.QRCode.generate_svg(
  auth.qr_start_token,
  auth.start_t,
  auth.qr_start_secret
)

# 3. Poll for completion (repeat every 2 seconds)
{:ok, result} = BankID.collect(auth.order_ref)

case result.status do
  "pending" ->
    # Keep polling - show status based on result.hint_code

  "complete" ->
    # Success! Extract user information
    user_info = BankID.extract_user_info(result.completion_data)
    # %{
    #   "personal_number" => "199001011234",
    #   "given_name" => "Erik",
    #   "surname" => "Andersson"
    # }

  "failed" ->
    # Handle error based on result.hint_code
end

# 4. Cancel if needed
:ok = BankID.cancel(auth.order_ref)
```

## Configuration

### Testing (Default)

**No configuration needed!** The library includes bundled test certificates and uses BankID's test server by default:

```elixir
# Works out of the box with test server
{:ok, auth} = BankID.authenticate("192.168.1.1")
```

Test personal numbers (use with BankID test app):
- `198803290003`
- `199006292360`

### Production

You can configure certificates in two ways:

#### Option 1: Direct Certificate Content (Recommended for Serverless)

Ideal for serverless deployments (AWS Lambda, Google Cloud Functions, etc.) where file system access is limited.

Add to your `config/runtime.exs`:

```elixir
config :bankid,
  base_url: "https://appapi2.bankid.com/rp/v6.0",
  cert: System.get_env("BANKID_CERT"),
  key: System.get_env("BANKID_KEY"),
  cacert: System.get_env("BANKID_CACERT")
```

Then set environment variables with full PEM content:

```bash
export BANKID_CERT="-----BEGIN CERTIFICATE-----
MIIEyjCCArKgAwIBAgIIG8/maByOzV4w...
-----END CERTIFICATE-----"

export BANKID_KEY="-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAAS...
-----END PRIVATE KEY-----"

# Optional: CA certificate (defaults to BankID's CA cert)
export BANKID_CACERT="-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----"
```

#### Option 2: File Paths (Traditional)

Use when certificates are stored as files on the file system:

Add to your `config/runtime.exs`:

```elixir
config :bankid,
  base_url: "https://appapi2.bankid.com/rp/v6.0",
  cert_path: System.get_env("BANKID_CERT_PATH"),
  key_path: System.get_env("BANKID_KEY_PATH")
```

Then set environment variables before starting your application:

```bash
export BANKID_CERT_PATH="/etc/bankid/production-cert.pem"
export BANKID_KEY_PATH="/etc/bankid/production-key.pem"
```

**Important**: Production certificates must be obtained from your bank after signing a BankID agreement.

**Priority**: If both direct content (`:cert`) and file path (`:cert_path`) are provided, direct content takes priority.

## Usage

### Authentication Flow

```elixir
# Initiate authentication
{:ok, auth} = BankID.authenticate("192.168.1.1")

# auth contains:
# %{
#   order_ref: "...",          # For polling
#   qr_start_token: "...",     # For QR generation
#   qr_start_secret: "...",    # For QR generation (keep server-side!)
#   auto_start_token: "...",   # For same-device flow
#   start_t: 1234567890        # Timestamp for QR generation
# }
```

### Require Specific User

```elixir
{:ok, auth} = BankID.authenticate("192.168.1.1",
  personal_number: "199001011234"
)
```

### Display Custom Message

```elixir
{:ok, auth} = BankID.authenticate("192.168.1.1",
  user_visible_data: "Login to MyApp",
  user_visible_data_format: "simpleMarkdownV1"
)
```

### QR Code Generation

Generate an animated QR code that updates every second:

```elixir
# In a LiveView or controller that runs every second
qr_svg = BankID.QRCode.generate_svg(
  auth.qr_start_token,
  auth.start_t,
  auth.qr_start_secret,
  width: 300,
  color: "#0066CC"
)
```

**Security Note**: Never send `qr_start_secret` to the client. QR codes must be generated server-side.

### Polling for Status

Poll the BankID API every 2 seconds:

```elixir
{:ok, result} = BankID.collect(auth.order_ref)

case result.status do
  "pending" ->
    # Check hint_code for user feedback:
    case result.hint_code do
      "outstandingTransaction" -> "Open BankID app"
      "noClient" -> "Starting BankID app..."
      "started" -> "Enter your security code"
      "userSign" -> "Confirming..."
    end

  "complete" ->
    user_info = BankID.extract_user_info(result.completion_data)
    # User authenticated successfully

  "failed" ->
    # Check hint_code for error reason:
    case result.hint_code do
      "userCancel" -> "Cancelled by user"
      "expiredTransaction" -> "Session expired"
      "certificateErr" -> "Certificate error"
      "startFailed" -> "Failed to start BankID"
    end
end
```

### Cancel Authentication

```elixir
:ok = BankID.cancel(auth.order_ref)
```

## API Reference

### Core Functions

- `BankID.authenticate/2` - Start authentication
- `BankID.collect/2` - Poll for status
- `BankID.cancel/2` - Cancel authentication
- `BankID.extract_user_info/1` - Extract user data from completion

### QR Code Generation

- `BankID.QRCode.generate_svg/4` - Generate QR code SVG
- `BankID.QRCode.generate_content/3` - Generate raw QR content
- `BankID.QRCode.elapsed_seconds/1` - Calculate elapsed time

### Low-Level Client

- `BankID.Client` - Direct access to client functions
- `BankID.HTTPClient` - HTTP client with mTLS

## Framework Integration

This is a **low-level client library** designed to be framework-agnostic. For framework-specific integrations:

### Ash Framework

Use the `ash_authentication_bankid` package for declarative authentication:

```elixir
{:ash_authentication_bankid, "~> 0.1.0"}
```

### Phoenix/Plug

Build custom controllers using this library:

```elixir
def create(conn, _params) do
  client_ip = get_client_ip(conn)
  {:ok, auth} = BankID.authenticate(client_ip)

  conn
  |> put_session(:order_ref, auth.order_ref)
  |> put_session(:start_time, auth.start_t)
  |> json(%{order_ref: auth.order_ref})
end

def poll(conn, %{"order_ref" => order_ref}) do
  {:ok, result} = BankID.collect(order_ref)
  json(conn, result)
end
```

### Custom Integration

Use in any Elixir application (GenServers, background jobs, etc.):

```elixir
defmodule MyApp.BankIDAuth do
  def authenticate_user(ip_address, personal_number) do
    with {:ok, auth} <- BankID.authenticate(ip_address, personal_number: personal_number),
         {:ok, result} <- wait_for_completion(auth.order_ref),
         user_info <- BankID.extract_user_info(result.completion_data) do
      {:ok, user_info}
    end
  end

  defp wait_for_completion(order_ref) do
    # Poll every 2 seconds for up to 3 minutes
    # Implementation left as exercise
  end
end
```

## Security Considerations

1. **Certificate Security**
   - Never commit certificates to Git
   - Store production certificates securely (e.g., `/etc/bankid/`)
   - Use environment variables in production
   - Set proper file permissions: `chmod 600 /path/to/cert.pem`

2. **QR Code Secret**
   - `qr_start_secret` must NEVER be sent to the client
   - Always generate QR codes server-side
   - Use server-side polling to check authentication status

3. **Session Security**
   - Bind order references to user sessions to prevent hijacking
   - Implement proper timeout mechanisms
   - Clean up expired orders

4. **IP Address**
   - Always use the actual client IP address
   - Be careful with proxy configurations

## Architecture

```
┌─────────────────────────────────────┐
│           BankID Module             │
│  (Public API / Convenience Layer)   │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│        BankID.Client                │
│  (Core authentication operations)   │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│      BankID.HTTPClient              │
│     (mTLS HTTP communication)       │
└──────────────┬──────────────────────┘
               │
               ▼
        BankID API v6.0
```

**Separate modules:**
- `BankID.QRCode` - QR code generation (independent)

## Testing

The library includes test certificates and works out-of-the-box with BankID's test environment.

```elixir
# In your tests
test "authenticate with BankID" do
  {:ok, auth} = BankID.authenticate("192.168.1.1")
  assert auth.order_ref
  assert auth.qr_start_token
end
```

**Note**: Actual authentication requires interaction with the BankID test app, so automated tests are limited to API communication testing.

## Troubleshooting

### Certificate Errors

If you see SSL/TLS errors:
- Verify certificate paths are correct
- Check file permissions
- Ensure certificates are valid and not expired

### Connection Errors

If you can't connect to BankID:
- Check network connectivity
- Verify you're using the correct base URL (test vs production)
- Check firewall rules

### Test Mode Not Working

If test mode fails:
- Bundled certificates should work out-of-the-box
- Verify the library is properly installed
- Check logs for detailed error messages

## Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

## License

MIT

## Related Projects

- [ash_authentication_bankid](https://github.com/yourusername/ash_authentication_bankid) - Ash Framework integration
- [pybankid](https://github.com/hbldh/pybankid) - Python implementation (inspiration)

## Resources

- [BankID API Documentation](https://www.bankid.com/en/utvecklare/guider/teknisk-integrationsguide)
- [BankID Integration Guide](https://www.bankid.com/en/utvecklare)
- [BankID Test Environment](https://www.bankid.com/en/utvecklare/test)