Skip to content

Latest commit

 

History

85 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🩺 SkinScan — AI-Powered Skin Disease Detection Platform

An ASP.NET Core backend for SkinScan, a mobile platform that provides AI-powered preliminary assessment of skin conditions from user-uploaded skin images. The backend handles authentication, prediction management, scan history, medical recommendations, and dermatologist discovery across all 27 Egyptian governorates.

Graduation Project — Faculty of Computers & Informatics, Zagazig University (2025–2026)

Live Link
API / Swagger https://skinscan.runasp.net/swagger
Web Demo https://skinscan-eg.vercel.app

Architecture Overview

SkinScan System Architecture

The following diagram provides a high-level overview of the SkinScan system architecture and illustrates how the Flutter mobile application, ASP.NET Core backend, external services, AI inference service, and SQL Server database interact during a prediction request.


Features

Feature Description
JWT Authentication Short-lived access tokens (30 min) with rotating refresh tokens (14 days)
Account Lockout 5 consecutive failed login attempts locks the account for 5 minutes
Email Confirmation Required before login, with resend support
OTP Password Reset 6-digit CSPRNG code, salted hash, 3-minute expiry, 5-attempt limit, 2-minute resend cooldown
AI Skin Prediction Uploaded images are sent to an external AI inference service, returning the top 5 predictions ranked by confidence
Parallel Processing Image upload (Cloudinary) and AI prediction run concurrently via Task.WhenAll
Orphaned Resource Cleanup Cloudinary image is automatically deleted if the AI call fails after a successful upload
GradCAM & TTA Optional heatmap overlay and test-time augmentation via query parameters
Curated Disease Recommendations 35 seeded entries with description, severity, symptoms, do's/don'ts, and doctor-consultation guidance
Scan History Full CRUD — list, detail, delete (cascades to Cloudinary asset removal)
Find a Doctor Dermatologist directory across all 27 Egyptian governorates, filterable by location
Result Pattern Typed errors (Result<T> / Error) mapped to RFC 7807 ProblemDetails
Validation FluentValidation with automatic validation middleware on every request DTO
Structured Logging Serilog with request logging middleware
Swagger / OpenAPI Enabled in both Development and Production environments

🏗️ Project Structure

SkinScan.Api/
├── Controllers/
│   ├── AuthController.cs          # Auth flows (login, register, refresh, confirm email, OTP reset)
│   ├── AccountController.cs       # Profile management (get, update, change password)
│   ├── PredictionController.cs    # AI skin prediction endpoint (image upload, top-5 results)
│   ├── ScansController.cs         # Scan history CRUD (list, detail, delete)
│   ├── DoctorsController.cs       # Dermatologist directory, filterable by governorate
│   └── GovernoratesController.cs  # Governorate reference data
│
├── Services/
│   ├── AuthService.cs             # JWT, refresh token rotation, OTP-based password reset
│   ├── UserService.cs             # Profile retrieval, update, password change
│   ├── PredictionService.cs       # AI model orchestration, image + prediction pipeline
│   ├── ScanService.cs             # Scan CRUD, cascade delete of Cloudinary assets
│   ├── DoctorService.cs           # Doctor directory retrieval & governorate filtering
│   ├── ImageService.cs            # File upload/delete via Cloudinary
│   └── EmailService.cs            # SMTP email sending via MailKit
│
├── Authentication/
│   └── JwtProvider.cs             # JWT generation & token validation
│
├── Clients/
│   └── IScanDiseaseClient.cs      # Refit client for the AI model (Hugging Face)
│
├── Persistance/
│   ├── ApplicationDbContext.cs
│   ├── Migrations/
│   └── Seeders/                   # DiseaseRecommendationSeeder, DoctorSeeder, GovernorateSeeder
│
├── Contracts/                     # Request & Response DTOs + FluentValidation validators
├── Entities/                      # Domain models (User, Scan, Doctor, Governorate, ...)
├── Errors/                        # Typed error classes + GlobalExceptionHandler
├── Helpers/                       # Email templates, HTML response builders
├── Extensions/                    # Claims extraction, other helpers
├── Mapping/                       # Mapster configuration
├── Settings/                      # Strongly-typed configuration (Options Pattern)
├── DependencyInjection.cs
├── GlobalUsings.cs
└── Program.cs

Database Schema

Entity Description
ApplicationUser Extends ASP.NET Identity — stores first/last name, gender, phone
RefreshToken Owned entity — rotating refresh tokens with expiry & revocation tracking
PasswordResetOtp Hashed OTP record with expiry and failed-attempt counter
Scan A single skin scan — image URL, top prediction, confidence, timestamp
ScanPrediction One of the top-5 predictions belonging to a Scan
DiseaseRecommendation Seeded medical reference data for all 35 detectable disease classes
Governorate Reference data — all 27 Egyptian governorates
Doctor Dermatologist profile — name, address, phone, WhatsApp, linked governorate

Getting Started

Prerequisites

  • .NET 10 SDK
  • SQL Server (LocalDB or full instance)
  • A Cloudinary account
  • A Gmail account (or other SMTP provider) for transactional email
  • Access to a compatible external AI prediction service (the production system uses one hosted on Hugging Face Spaces)

1. Clone the repository

git clone https://github.com/a7medhazem/SkinScan.git
cd SkinScan

2. Configure appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=SkinScan;Trusted_Connection=True;Encrypt=False"
  },
  "Jwt": {
    "key": "YOUR_SUPER_SECRET_KEY_MIN_32_CHARS",
    "Issuer": "SkinScan",
    "Audience": "SkinScan users",
    "ExpiryMinutes": 30
  },
  "EmailSettings": {
    "Mail": "your@gmail.com",
    "DisplayName": "Skin Scan",
    "Password": "your-app-password",
    "Host": "smtp.gmail.com",
    "Port": 587
  },
  "AppSettings": {
    "BaseUrl": "https://your-domain.com"
  },
  "SkinDiseaseApi": {
    "BaseUrl": "https://your-model-endpoint",
    "PredictEndpoint": "/predict",
    "TimeoutSeconds": 180,
    "ApiKey": "your-model-api-key"
  },
  "Cloudinary": {
    "CloudName": "your_cloud_name",
    "ApiKey": "your_api_key",
    "ApiSecret": "your_api_secret"
  }
}

3. Apply migrations & seed data

dotnet ef database update --project SkinScan.Api

On first run, UseSeedDataAsync() automatically seeds the disease recommendation database, all 27 governorates, and the dermatologist directory.

4. Run the API

dotnet run --project SkinScan.Api

Swagger UI: https://localhost:{port}/swagger

API Reference

Auth — /Auth

Method Endpoint Auth Description
POST /Auth — Login with email & password
POST /Auth/register — Register a new account
GET /Auth/confirm-email — Confirm email via link token
POST /Auth/resend-confirmation-email — Resend confirmation email
POST /Auth/check-email-confirmation — Check whether an email is confirmed
POST /Auth/refresh — Rotate access & refresh tokens
POST /Auth/revoke-refresh-token — Logout / revoke refresh token
POST /Auth/forget-password — Send OTP reset code to email
POST /Auth/verify-otp — Verify OTP and receive a reset token
POST /Auth/reset-password — Reset password using the reset token
POST /Auth/resend-otp — Resend OTP (2-minute cooldown)

Account — /Account (Bearer required)

Method Endpoint Description
GET /Account/profile Get current user profile
PUT /Account/update-profile Update first name, last name, phone
PUT /Account/change-password Change account password

Prediction — /api/Prediction (Bearer required)

Method Endpoint Description
POST /api/Prediction/predict?gradcam={bool}&tta={bool} Upload a skin image and receive top-5 AI predictions + recommendation

Scans — /api/Scans (Bearer required)

Method Endpoint Description
GET /api/Scans List the current user's scan history
GET /api/Scans/{scanId} Get full details of a specific scan
DELETE /api/Scans/{scanId} Delete a scan and its Cloudinary image

Find a Doctor — /api/Governorates & /api/Doctors (Bearer required)

Method Endpoint Description
GET /api/Governorates List all 27 Egyptian governorates
GET /api/Doctors List all dermatologists
GET /api/Doctors?governorateId={id} List dermatologists filtered by governorate
GET /api/Doctors/{id} Get details of a specific doctor

Security

Token Strategy

  • Access Token — JWT, HMAC-SHA256, 30-minute expiry, ClockSkew = TimeSpan.Zero (no tolerance window)
  • Refresh Token — 14-day expiry, rotated on every use, revoked on logout
  • ValidateLifetime = false is set intentionally on the refresh endpoint, so an already-expired access token can still be presented to trigger rotation JWT Claims
sub         → User ID
email       → User email
given_name  → First name
family_name → Last name
jti         → Unique token ID

Authentication Flow

Register → Email Confirmation (24h token) → Login → JWT (30 min) + Refresh Token (14 days)
                                                         ↓
                                              Refresh → Old token revoked → New JWT + New Refresh Token

Login Protection

5 consecutive failed attempts → account locked for 5 minutes

Password Reset Flow

User requests reset
        ↓
6-digit OTP generated (CSPRNG)
        ↓
OTP hashed via ASP.NET Core Identity PasswordHasher (salted)
        ↓
Raw OTP sent via email (Gmail SMTP over STARTTLS)
        ↓
User submits OTP → verified against hash (3-minute expiry, max 5 attempts)
        ↓
Reset token issued (valid only after successful OTP verification)
        ↓
Password updated via ASP.NET Core Identity

Image Upload Security

File validation runs in three layers before a single byte is stored:

  1. FileNameValidator — filename must match safe character regex
  2. FileSizeValidator — max 10 MB enforced
  3. ImageSignatureValidator — reads magic bytes (file header) to verify actual format — a renamed .exe returns 400 Bad Request regardless of extension

Other Controls

  • All authenticated endpoints extract the user ID exclusively from JWT claims — never from client-supplied identifiers (prevents IDOR)
  • Users can only access their own scans (UserId filter on every query)
  • Email existence is never revealed on forgot-password or resend-OTP endpoints (always returns 200 OK)
  • Passwords hashed with PBKDF2 via ASP.NET Core Identity
  • All external communication (SMTP, AI model, Cloudinary) over TLS/HTTPS
  • API keys and secrets stored in configuration, never in source control
  • Global exception handler returns a generic 500 response — no internal details ever leak to the client

AI Integration

SkinScan integrates with an external AI inference service for skin disease classification. The backend does not host or train the model — it calls the service through a typed client and returns the results to the user.

Property Detail
Integration External AI inference service, hosted on Hugging Face Spaces
Client Refit-typed IScanDiseaseClient, 30-second timeout, Bearer-authenticated
Request Skin image (multipart/form-data)
Response Top 5 disease predictions ranked by confidence, matched against a curated recommendation
Optional Parameters tta (Test-Time Augmentation), gradcam (heatmap overlay)

Built With

Layer Library
Framework ASP.NET Core 10, C# 13
Data Access Entity Framework Core 10 (SQL Server)
Authentication ASP.NET Core Identity, System.IdentityModel.Tokens.Jwt
Validation FluentValidation
Mapping Mapster
External Services Refit (AI model client), CloudinaryDotNet (image CDN), MailKit (SMTP email)
Logging Serilog
Documentation Swashbuckle.AspNetCore (Swagger / OpenAPI)

Demo

Resource Link Notes
Swagger UI skinscan.runasp.net/swagger Live API reference
Web Client skinscan-eg.vercel.app Experimental React/Vite frontend — primary client is the Flutter mobile app
Demo Video Watch on YouTube Full walkthrough

Contributing

Pull requests are welcome. For major changes, please open an issue first.

  1. Fork the repository
  2. Create your branch: git checkout -b feature/your-feature
  3. Commit your changes: git commit -m 'feat: add some feature'
  4. Push to the branch: git push origin feature/your-feature
  5. Open a Pull Request

License

This project is licensed under the MIT License.


Developed as a Graduation Project at the Faculty of Computers and Informatics, Zagazig University (2025–2026).

About

AI-powered skin disease detection platform built with ASP.NET Core, Flutter, SQL Server, and Swin Transformer.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages