Angular SDK Guide
Installation
bash
npm install @angular/common httpService 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);
}