Back to Documentation
🅰️

Angular Integration

Integration guide for Angular applications

Angular SDK Guide

Installation

bash
npm install @angular/common http

Service Implementation

typescript
// src/app/services/keyclaim.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class KeyClaimService {
  private apiBaseUrl = 'https://keyclaim.org/api';
  private apiKey = 'kc_your_api_key_here';

  constructor(private http: HttpClient) {}

  // Step 1: Create challenge
  createChallenge(ttl: number = 30): Observable<any> {
    return this.http.post(`${this.apiBaseUrl}/challenge/create`, {
      ttl: ttl
    }, {
      headers: {
        'Authorization': `Bearer ${this.apiKey}`
      }
    });
  }

  // Step 2: Generate HMAC response
  generateResponseHMAC(challenge: string): string {
    // Use Web Crypto API
    // Note: This is async, you'll need to handle it properly
    return this.generateHMAC(challenge, this.apiKey);
  }

  private async generateHMAC(message: string, secret: string): Promise<string> {
    const encoder = new TextEncoder();
    const keyData = encoder.encode(secret);
    const messageData = encoder.encode(message);
    
    const cryptoKey = await crypto.subtle.importKey(
      'raw',
      keyData,
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign']
    );
    
    const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData);
    const hashArray = Array.from(new Uint8Array(signature));
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
  }

  // Step 3: Validate
  validate(challenge: string, response: string, decryptedChallenge?: string): Observable<any> {
    const payload: any = {
      key: this.apiKey,
      challenge: challenge,
      response: response
    };

    if (decryptedChallenge) {
      payload.decryptedChallenge = decryptedChallenge;
    }

    return this.http.post(`${this.apiBaseUrl}/challenge/validate`, payload);
  }
}

Component Usage

typescript
// src/app/components/challenge-validation.component.ts
import { Component } from '@angular/core';
import { KeyClaimService } from '../services/keyclaim.service';

@Component({
  selector: 'app-challenge-validation',
  template: `
    <div>
      <button (click)="createChallenge()" [disabled]="loading">
        Create Challenge
      </button>
      
      <div *ngIf="challenge">
        <p>Challenge: {{ challenge }}</p>
        <button (click)="validateChallenge()" [disabled]="loading">
          Validate
        </button>
      </div>
      
      <div *ngIf="result">
        <p>{{ result.valid ? '✓ Valid' : '✗ Invalid' }}</p>
      </div>
    </div>
  `
})
export class ChallengeValidationComponent {
  challenge: string = '';
  result: any = null;
  loading: boolean = false;

  constructor(private keyClaimService: KeyClaimService) {}

  async createChallenge() {
    this.loading = true;
    try {
      this.keyClaimService.createChallenge().subscribe({
        next: (data: any) => {
          this.challenge = data.challenge;
        },
        error: (error) => {
          console.error('Failed to create challenge:', error);
        }
      });
    } finally {
      this.loading = false;
    }
  }

  async validateChallenge() {
    if (!this.challenge) return;
    
    this.loading = true;
    try {
      const response = await this.keyClaimService.generateResponseHMAC(this.challenge);
      
      this.keyClaimService.validate(this.challenge, response).subscribe({
        next: (data: any) => {
          this.result = data;
        },
        error: (error) => {
          console.error('Validation failed:', error);
        }
      });
    } finally {
      this.loading = false;
    }
  }
}

RSA Decryption

typescript
async decryptChallenge(encryptedChallenge: string, privateKeyPEM: string): Promise<string> {
  // Convert PEM to ArrayBuffer
  const pemContents = privateKeyPEM
    .replace('-----BEGIN PRIVATE KEY-----', '')
    .replace('-----END PRIVATE KEY-----', '')
    .replace(/\s/g, '');
  
  const binary = atob(pemContents);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  
  // Import and decrypt
  const key = await crypto.subtle.importKey(
    'pkcs8',
    bytes.buffer,
    { name: 'RSA-OAEP', hash: 'SHA-256' },
    false,
    ['decrypt']
  );
  
  const encryptedBytes = Uint8Array.from(atob(encryptedChallenge), c => c.charCodeAt(0));
  const decrypted = await crypto.subtle.decrypt(
    { name: 'RSA-OAEP' },
    key,
    encryptedBytes
  );
  
  return new TextDecoder().decode(decrypted);
}