Entwicklung & Code
Passkeys in der Praxis – Teil 2: Registrierung und Authentifizierung
Passkeys bestehen aus einem kryptografischen Schlüsselpaar, das bei der Registrierung erzeugt und bei jeder späteren Anmeldung wiederverwendet wird. Die genauen Abläufe – bei WebAuthn heißen sie Ceremonies – Registration und Authentication folgen dabei demselben Grundmuster: Der Server generiert eine Challenge, der Client ruft die WebAuthn API auf, der Authenticator signiert, und der Server validiert. Was sich unterscheidet, ist die Richtung: Registration (navigator.credentials.create()) erzeugt ein neues Credential und speichert den Public Key. Authentication (navigator.credentials.get()) beweist den Besitz des zugehörigen Private Key. Wer dieses Muster einmal wirklich versteht, kann alle Validierungsschritte direkt einordnen und erkennt sofort, wo eine Implementierung sicherheitskritische Abkürzungen nimmt.
Weiterlesen nach der Anzeige

Martina Kraus beschäftigt sich schon seit frühen Jahren mit der Webentwicklung. Das Umsetzen großer Softwarelösungen in Node.js und Angular hat sie schon immer begeistert. Als selbstständige Softwareentwicklerin arbeitet sie vornehmlich mit Angular mit Schwerpunkt auf Sicherheit in Webanwendungen.
Die Registration Ceremony (§7.1)
Die Registration Ceremony nach §7.1 der W3C-WebAuthn-Level-2-Spezifikation beginnt auf dem Server. Bevor navigator.credentials.create() im Browser aufgerufen werden kann, muss der Server ein PublicKeyCredentialCreationOptions-Objekt zusammenstellen und an den Client senden. Dieses Objekt ist mehr als eine Konfigurationsdatei, denn jedes seiner Felder trifft eine Entscheidung, die sich auf Sicherheit und Nutzererfahrung auswirkt.
PublicKeyCredentialCreationOptions – Feld für Feld
Bevor die einzelnen Felder im Detail besprochen werden, zeigt das folgende Listing, wie ein vollständiges PublicKeyCredentialCreationOptions-Objekt in der Praxis aussieht:
const creationOptions: PublicKeyCredentialCreationOptions = {
challenge: crypto.getRandomValues(new Uint8Array(32)), // serverseitig generiert
rp: {
id: "example.com",
name: "Example App",
},
user: {
id: crypto.getRandomValues(new Uint8Array(16)), // keine PII!
name: "martina@example.com", // nur für UI
displayName: "Martina",
},
pubKeyCredParams: [
{ type: "public-key", alg: -7 }, // ES256
{ type: "public-key", alg: -257 }, // RS256
],
excludeCredentials: existingCredentialIds.map(id => ({
type: "public-key",
id,
})),
authenticatorSelection: {
residentKey: "required",
userVerification: "required",
},
attestation: "none",
timeout: 60000,
};
Listing 1: PublicKeyCredentialCreationOptions
Weiterlesen nach der Anzeige
Das wichtigste Feld ist challenge. Diese ist ein Puffer aus kryptografisch zufälligen Bytes, den der Server generiert – mindestens 16 Bytes müssen es laut Spezifikation sein. In der Praxis haben sich allerdings 32 Bytes bewährt, weil sie einen deutlichen Sicherheitspuffer über dem Minimum bieten, ohne die Nutzlast spürbar zu vergrößern. Die Challenge wird serverseitig gespeichert, bis die Validierungsantwort eintrifft, und darf nur einmal verwendet werden. Ihr Zweck ist es, Replay-Angriffe zu verhindern: Ohne eine frische, serverseitig verifizierte Challenge könnte ein Angreifer eine abgefangene Authenticator-Antwort erneut einspielen.
Das rp-Objekt identifiziert die Relying Party (RP). Aus Teil 1 bekannt ist die Regel: Die id muss eine registrierbare Domain-Teilmenge der aktuellen Origin sein. In der Praxis bedeutet das, dass id entweder die exakte Domain oder eine übergeordnete Domain ist. shop.example.com kann demnach example.com als rpId nutzen, aber nicht umgekehrt. Der name ist ausschließlich für die Authenticator-UI relevant, nicht für die Sicherheit.
Das user-Objekt enthält Informationen über den registrierenden Nutzer. Hier lauert ein häufiger Fehler: Die user.id ist ein opaker – also für Client und Nutzer inhaltlich bedeutungsloser – Byte-Puffer, der keine personenbezogenen Daten enthalten soll, insbesondere keine E-Mail-Adresse und keinen Benutzernamen. Der Authenticator verknüpft das Credential intern mit dieser ID, und sie wird bei der Authentication im userHandle zurückgegeben. Eine zufällig generierte UUID ist die richtige Wahl. Die Spec schreibt außerdem vor, dass user.id zwischen 1 und 64 Bytes groß sein muss. Wer einen zu langen Puffer übergibt, bekommt einen TypeError. name und displayName werden nur in der Authenticator-UI angezeigt.
pubKeyCredParams ist ein Array, das angibt, welche Algorithmen der Server akzeptiert, in Prioritätsreihenfolge von oben nach unten. Die gängigen Einträge sind -7 für ES256 (ECDSA über P-256 mit SHA-256), -257 für RS256 und -8 für EdDSA. Ein moderner Server sollte mindestens ES256 und RS256 beherrschen, um eine breite Authenticator-Kompatibilität zu gewährleisten.
Mit excludeCredentials kann der Server verhindern, dass derselbe Authenticator mehrfach für dasselbe Konto registriert wird. Das Feld enthält eine Liste von credentialId-Werten, die dem Nutzer bereits gehören. Ist der Authenticator schon registriert, bricht der Browser die Anfrage ab. Das Feld ist optional, aber es ist Best Practice, es zu befüllen. Ohne diese Prüfung kann ein Nutzer denselben Passkey mehrfach anlegen, was die Credential-Verwaltung unnötig verkompliziert.
authenticatorSelection ist das Feld, mit dem die RP Einfluss auf den Authenticator-Typ nimmt. authenticatorAttachment mit dem Wert "platform" schränkt auf eingebettete Authenticators ein, "cross-platform" auf externe Geräte. Wichtiger ist residentKey: Mit dem Wert "required" wird ein Discoverable Credential erzeugt – eines, das der Authenticator selbst findet, ohne dass der Server eine credentialId mitschickt. Das ist die technische Voraussetzung für ein Log-in ohne Nutzernamen. "preferred" ist ein sinnvoller Kompromiss, wenn Kompatibilität wichtiger als garantierte Discoverable Credentials ist. Das dritte relevante Unterfeld ist userVerification: "required" bedeutet, dass der Authenticator zwingend Biometrie oder PIN verlangen muss. Diese Einstellung hat direkte Auswirkungen auf die spätere Assertion-Validierung – der Server muss bei jeder Authentication prüfen, ob das UV-Flag (User Verification) in der authData dem hier konfigurierten Wert entspricht.
Das attestation-Feld gibt an, welche Art von Attestationsdaten der Server erwartet. "none" ist für die meisten Consumer-Anwendungen die richtige Wahl: Der Server interessiert sich nicht dafür, welches Gerät das Credential erzeugt hat. "direct" liefert eine vollständige Zertifikatskette, mit der der Server das Authenticator-Modell verifizieren kann. Das ist relevant für Enterprise-Szenarien oder wenn nur zertifizierte Hardware akzeptiert werden soll. Wichtig dabei: Das attestation-Feld ist ein Hinweis, keine harte Vorgabe. Ein Authenticator kann trotz "none" eine vollständige Attestation zurückliefern – der Server entscheidet dann, ob er sie validiert oder ignoriert.
Das attestationObject – Aufbau und Inhalt
Das nächste Listing demonstriert eine rpIdHash-Prüfung in TypeScript:
// rpIdHash-Prüfung
const expectedRpIdHash = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(rpId)
);
const actualRpIdHash = authData.slice(0, 32);
if (!bufferEqual(expectedRpIdHash, actualRpIdHash)) {
throw new Error("rpIdHash mismatch – Credential für falsche RP");
}
Listing 2: rpIdHash-Prüfung in TypeScript
Nachdem navigator.credentials.create() aufgerufen wurde und die Nutzerin oder der Nutzer den Authenticator bedient hat, gibt der Browser ein PublicKeyCredential zurück, dessen response eine AuthenticatorAttestationResponse enthält. Das Herzstück davon ist das attestationObject, ein CBOR-kodiertes Objekt mit drei Feldern.
fmt gibt das Attestation-Format an: "packed", "tpm", "android-key", "fido-u2f", "apple" oder "none". Es bestimmt, wie das zweite Feld attStmt zu interpretieren ist. Bei "none" ist attStmt leer – der Authenticator macht keine Aussage über seine Herkunft. Bei "packed" enthält attStmt einen Algorithmus-Identifier (alg), eine Signatur (sig) und optional eine X.509-Zertifikatskette (x5c), mit der die Signatur verifiziert werden kann.
Das dritte Feld, authData, ist aus Teil 1 bekannt: der SHA-256-Hash der rpId, das flags-Byte, der signCount und – bei der Registration, wenn das AT-Flag (Attested Credential Data) gesetzt ist – die attestedCredentialData mit aaguid, credentialId und credentialPublicKey. Es sind genau diese letzten drei Werte, die der Server dauerhaft in der Datenbank speichern muss.
Server-Validierung nach §7.1 – die Pflichtschritte
Die Validierung beginnt mit clientDataJSON. Der Server dekodiert den ArrayBuffer als UTF-8 und prüft drei Dinge: type muss "webauthn.create" sein, challenge muss mit der serverseitig gespeicherten Challenge übereinstimmen (Base64url-dekodiert), und origin muss der erwarteten Origin der Anwendung entsprechen. Diese drei Prüfungen sind nicht optional: Die type-Prüfung verhindert, dass eine Authentication-Antwort fälschlich als Registration akzeptiert wird, die challenge-Prüfung schützt vor Replay-Angriffen mit abgefangenen Antworten, und die origin-Prüfung verhindert Phishing, indem sie sicherstellt, dass die Antwort tatsächlich für die eigene Domain erzeugt wurde.
Anschließend wird das attestationObject CBOR-dekodiert, und der Server extrahiert die authData. Der erste sicherheitskritische Check: Der rpIdHash in authData[0..31] muss mit SHA-256(rpId) übereinstimmen. Ist das nicht der Fall, wurde das Credential für eine andere Relying Party erzeugt – der Server lehnt es ab, ohne Fallback. Danach folgen die Flag-Prüfungen: Das UP-Flag (User Presence) muss gesetzt sein. Das UV-Flag muss gegen die konfigurierte userVerification-Policy geprüft werden – wenn der Server "required" gesetzt hat und das Flag fehlt, ist die Registration ungültig.
Bevor das Credential gespeichert wird, muss der Server prüfen, ob die credentialId bereits in der Datenbank existiert. Doppelte IDs dürfen nicht entstehen. Was am Ende in der Datenbank landet, sind mindestens fünf Felder: credentialId, credentialPublicKey (im COSE-Format, CBOR Object Signing and Encryption), signCount (initial 0), aaguid und die Verknüpfung zur userId. Für Produktionssysteme, die Passkeys und Hardware-Keys unterschiedlich behandeln wollen, sind zusätzlich das BE-Flag (backupEligible) und das BS-Flag (backupState) aus den authData-Flags essenziell. Sie signalisieren, ob das Credential synchronisiert werden kann und ob es aktuell synchronisiert ist. Optional, aber ebenfalls empfehlenswert ist die transports-Angabe aus der Response, um den Browser bei zukünftigen Authentifizierungen besser zu steuern.
Entwicklung & Code
Microsoft leitet letzte Phase für SQL Data Sync ein
Microsoft hat die nächste Stufe bei der Einstellung von SQL Data Sync gezündet: Seit dem 9. September lassen sich in Azure-Abonnements, die den Dienst bislang nicht genutzt haben, keine neuen SQL-Data-Sync-Bereitstellungen mehr anlegen. Bestehende Nutzer können Sync-Gruppen und Mitgliedsdatenbanken dagegen noch bis zum vollständigen Aus des Dienstes am 30. September 2027 erstellen, ändern und betreiben.
Weiterlesen nach der Anzeige
Die Einstellung hatte Microsoft bereits 2024 mit dreijähriger Übergangsfrist angekündigt. SQL Data Sync gleicht ausgewählte Daten zwischen mehreren Datenbanken ab, sowohl zwischen Azure SQL Database als auch mit SQL-Server-Instanzen im eigenen Rechenzentrum. Die Daten können dabei je nach Konfiguration in beide Richtungen fließen. Microsoft beschreibt die Einstellung und die vorgesehenen Migrationspfade in seiner Dokumentation.
Keine Einschränkung für bestehende Abonnements
Die neue Einschränkung trifft ausdrücklich ausschließlich Azure-Abonnements, die SQL Data Sync zuvor nie eingesetzt haben. Für Abonnements, die SQL Data Sync bereits nutzen, ändert sich zunächst nichts: Administratoren dürfen weiterhin Sync-Gruppen anlegen und verwalten sowie Datenbanken als Mitglieder hinzufügen. Microsoft will damit verhindern, dass kurz vor dem Endtermin noch neue Abhängigkeiten von dem auslaufenden Dienst entstehen. Die Details zur nun begonnenen letzten Ausmusterungsphase nennt Microsoft im Azure-SQL-Blog.
SQL Data Sync organisiert die Replikation in Sync-Gruppen nach einem Hub-and-Spoke-Prinzip. Als Hub dient stets eine Azure SQL Database; weitere Azure-SQL-Datenbanken oder SQL-Server-Datenbanken können als Mitglieder teilnehmen. Bei lokalen SQL-Server-Datenbanken ist zusätzlich ein lokaler Sync-Agent nötig. Der Dienst kann Daten in beide Richtungen oder nur vom Hub zu einem Mitglied beziehungsweise umgekehrt übertragen.
Inventur vor der Migration
Microsoft rät betroffenen Organisationen, zunächst alle vorhandenen Bereitstellungen zu erfassen – auch inaktive Sync-Gruppen. Dazu gehören Hub- und Mitgliedsdatenbanken, gegebenenfalls installierte Sync-Agenten sowie die Anwendungen, die auf replizierte Daten angewiesen sind. In kleineren Umgebungen lassen sich die Sync-Gruppen über den Bereich „Sync to other databases“ einer Azure SQL Database im Azure-Portal prüfen. Für größere Azure-Landschaften verweist Microsoft auf die PowerShell und die Azure-SQL-Management-REST-API.
Vor der Umstellung sollten Administratoren außerdem Datenfluss, Synchronisierungsrichtung, Intervall, Datenvolumen sowie Anforderungen an Latenz und Verfügbarkeit festhalten. Das ist wichtig, weil SQL Data Sync kein einheitliches Nachfolgeprodukt erhält. Je nach bisherigem Einsatz kann die Migration eine bloße Umkonfiguration sein oder einen Umbau der Replikationsarchitektur erfordern.
Weiterlesen nach der Anzeige
Ersatz hängt vom Einsatzfall ab
Für geplante oder inkrementelle Übertragungen nennt Microsoft Azure Data Factory als Alternative. Das eignet sich etwa, wenn Daten in festen Intervallen für ein Data Warehouse oder einen Reporting-Bestand kopiert werden sollen. Für die einseitige Verteilung von SQL Server zu Azure SQL Database verweist der Konzern auf die transaktionale Replikation.
Geht es vor allem um lesende Replikate oder regionale Verfügbarkeit, kommen etwa aktive Georeplikation und Read Replicas infrage. Für Reporting-Replikate vollständiger Datenbanken nennt Microsoft Always On Availability Groups bei SQL Server auf Azure-VMs sowie Managed Instance Link bei Azure SQL Managed Instance. Daneben führt die Migrationsdokumentation Azure Functions mit Azure-SQL-Triggern für anwendungsspezifische Synchronisation sowie Mirroring in Microsoft Fabric für Analyseumgebungen auf. Welche Variante passt, hängt unter anderem von Quell- und Zielplattform, Datenrichtung, Topologie, zulässiger Verzögerung und Durchsatz ab. Die Migrationsdokumentation enthält dazu eine Übersichtstabelle, die alle Pfade nach Quell- und Zielplattform aufschlüsselt.
Nach erfolgreicher Validierung der Ersatzlösung in einer Nicht-Produktivumgebung empfiehlt Microsoft, die automatische Synchronisierung der bisherigen Gruppe vorübergehend abzuschalten. Erst wenn Anwendungen nicht mehr auf SQL Data Sync angewiesen sind, sollten Unternehmen die alten Ressourcen entfernen. Spätestens zum 30. September 2027 muss der Umstieg abgeschlossen sein.
(fo)
Entwicklung & Code
Neu in .NET 10.0 [40]: JSON Patch zum reinen Testen von Werten
Um Werte zu testen, ohne etwas zu ändern, lässt sich in .NET 10.0 JSON Patch verwenden.
Weiterlesen nach der Anzeige

Dr. Holger Schwichtenberg ist technischer Leiter des Expertennetzwerks www.IT-Visions.de, das mit 53 renommierten Experten zahlreiche mittlere und große Unternehmen durch Beratungen und Schulungen sowie bei der Softwareentwicklung unterstützt. Durch seine Auftritte auf zahlreichen nationalen und internationalen Fachkonferenzen sowie mehr als 90 Fachbücher und mehr als 1500 Fachartikel gehört Holger Schwichtenberg zu den bekanntesten Experten für .NET und Webtechniken in Deutschland.
Das folgende Beispiel zeigt die Anwendung der neuen Klasse JsonPatchDocument aus System.Text.Json Version 10.0 auf ein JSON-Patch-Dokument, dass nur aus Test-Operationen besteht:
CUI.H2("Testen mit JSON Patch-Dokument");
string jsonPatchTest = """
[
{ "op": "test", "path": "/FirstName", "value": "Dr. Holger" },
{ "op": "test", "path": "/PrivateEmail", "value": null},
{ "op": "test", "path": "/PrivateWebsite", "value": null},
{ "op": "test", "path": "/Address/ZipCode", "value": "45257" }
]
""";
// JSON Patch-Dokument laden
JsonPatchDocument? patchTestDoc = JsonSerializer.Deserialize>(jsonPatchTest);
// JSON Patch-Dokument anwenden auf das Person-Objekt
patchTestDoc!.ApplyTo(person, patchError => CUI.PrintError(patchError.ErrorMessage));
In diesem Fall gibt es keine Fehlerausgabe. Wenn man das JSON-Patch-Dokument aber verändert
CUI.H2("Reines Testen eines Objekts mit JSON Patch-Dokument (absichtlich mit drei Fehlern!)");
string jsonPatchTest = """
[
{ "op": "test", "path": "/FirstName", "value": "Dr.Holger" },
{ "op": "test", "path": "/PrivateEmail", "value": null},
{ "op": "test", "path": "/PrivateWebsite", "value": ""},
{ "op": "test", "path": "/Address/ZipCode", "value": "45251" }
]
""";
bekommt man drei Fehlermeldungen:

Drei Fehler zeigt die Ausgabe (Abb. 1).
Dabei ist die Fehlermeldung bei 'PrivateWebsite' ungünstig, denn sie unterscheidet nicht zwischen Leerstring und Null. Im Objekt steht null, getestet wird auf Leerstring. In der Fehlermeldung steht jedoch in beiden Fällen ' '.
Weiterlesen nach der Anzeige
(rme)
Entwicklung & Code
Kommentar: Wenn Transparenz mit einem Rätsel beginnt
In weniger als vier Monaten sollen die Deutschen ihren Ausweis, Führerschein und vielleicht irgendwann auch ihr Schulzeugnis und den Kaninchenzuchtstammbaum auf dem Smartphone vorzeigen können. Eine Brieftasche als App halt. Ein echter, guter Fortschritt – den die EU ja auch erzwingt. Angesichts des offensichtlichen Security-Schabernacks ist das aber auch ein Projekt, das dringend Vertrauen bei den Bürgern aufbauen muss. Der neue Name, den Bundesdigitalminister Karsten Wildberger (CDU) dafür bei einer Veranstaltung in Berlin verkündet hat, lautet: d-you.
Weiterlesen nach der Anzeige

Moritz Förster schreibt seit 2012 für die iX und heise online. Er betreut neben dem iX-Channel den Bereich Arbeitsplatz.
Stop mal – d-you. Was ist denn das? Eine Dating-App? Ein Jugendportal? Ein Motivationspodcast? Wer nicht ohnehin schon von der EUDI-Wallet gehört hat, wird daraus niemals schlau und installiert die App schon einmal gar nicht. So wird ein dämlicher Name zu einem unnötigen Problem.
Ein Name ist keine Kosmetik
Natürlich entscheidet kein Name allein darüber, ob eine Technologie bei IT-affinen Nutzern angenommen wird oder nicht. Sicherheit, Datenschutz, einfache Einrichtung und echte Anwendungsfälle zählen da mehr. Aber der Name ist der erste Berührungspunkt für den Otto-Normal-Bürger. Der kommt dann noch nicht einmal zum Download, liest den verklausulierten Datenschutzhinweis oder erkundet die tollen Funktionen in der App. Ein guter Name beantwortet schon davor und in Millisekunden die Frage: Worum geht es hier, und ist das etwas für mich?
Die Technikakzeptanzforschung weiß das seit Jahrzehnten. Fred Davis zeigte bereits 1989 in seinem Technology Acceptance Model, dass Menschen neue Technologien vor allem dann nutzen, wenn sie deren Nutzen verstehen und sie als leicht handhabbar wahrnehmen. Spätere Forschung ergänzte soziale Einflüsse und Rahmenbedingungen. Was all diese Modelle gemeinsam haben: Der erste kognitive Eindruck zählt. Und im Gegensatz dazu erzeugt ein Name, der keine Orientierung gibt, Reibung – und zwar noch bevor jemand die Funktionen der App überhaupt wahrgenommen hat.
Leer, nicht nur englisch
Weiterlesen nach der Anzeige
Die berechtigte Kritik an d-you ist keine Kulturkritik an Anglizismen. Kunstnamen können funktionieren. Apple erklärt keine Computer, O2 erklärt keinen Mobilfunk. Aber private Marken kaufen sich über Jahre Bedeutung ein: durch Werbung, durch Nutzung, durch Wiederholung. Eine staatliche Identitätsinfrastruktur startet unter anderen Bedingungen. Sie und das Technikkonzept sind für die Bürger neu. Sie berührt sensible Daten. Und sie muss von möglichst vielen Menschen verstanden und genutzt werden, damit sie sich durchsetzt. Dafür genügen nicht nur wenige Early Adopter, die sich ohnehin für digitale Verwaltung interessieren.
d-you ist dabei nicht einmal ein besonders starker Kunstname. Das „d“ könnte für Deutschland stehen, muss es aber nicht. „you“ verspricht Individualität, sagt aber nichts über die Funktion. Der Bindestrich macht die Schreibweise erklärungsbedürftig. Und die Aussprache? „Di-ju“? „Dü“? In Behördenbriefen, Suchmaschinen und mündlicher Weitergabe werden schon bald Varianten kursieren. Vor allem aber: Kein Wort im Namen verrät, dass es hier um Ausweis, Nachweise oder digitale Identität geht. d-you ist also nicht zu Englisch, sondern zu leer.
Die Metapher, die man weggeworfen hat
Mein Gegenvorschlag: schlicht „Digitale Brieftasche“ und gut ist. Das ist technisch gesehen natürlich keine perfekte Bezeichnung. Eine Brieftasche liegt in der Tasche, ist offline, gehört einem allein. Und eine staatliche Wallet ist Software, Sicherheitsarchitektur und Schnittstelle zu Behörden und Unternehmen zugleich. Die EU-Kommission beschreibt die EUDI-Wallet als Ort für digitale Identitätsdaten und attestierte Nachweise. Die Metapher hängt sich also in vielen Details auf.
Aber sie stimmt im Wesentlichen: Eine Brieftasche verwahrt persönliche Dokumente, gibt sie auf Nachfrage vor und bleibt unter der Kontrolle ihres Besitzers. Das ist genau das, was die Wallet verspricht. Die Metapher ist eben ein brauchbarer Einstieg. Und genau deshalb hätte man sie nicht durch einen Fantasienamen ersetzen müssen.
Wildberger betonte bei der Vorstellung, wie wichtig ihm Kontrolle und Transparenz für die Bürgerinnen und Bürger seien. Das ist das offensichtlich richtige Versprechen. Aber wer Datensouveränität verspricht, sollte nicht mit sprachlicher Intransparenz beginnen.
(fo)
-
Entwicklung & Codevor 1 MonatKommentar: KI-Verbote in Open-Source-Projekten können KI nicht stoppen
-
UX/UI & Webdesignvor 2 MonatenRegional & mit Gefühl: Identity für Klimafonds Baden-Württemberg › PAGE online
-
UX/UI & Webdesignvor 2 MonatenVom Scribble zur Sammelkarte: Kreativer KI-Workflow mit Adobe Photoshop, Illustrator und Firefly Boards › PAGE online
-
Datenschutz & Sicherheitvor 2 MonatenKommentar: OpenAI – KI-Sicherheit nur ein Marketing-Gag?
-
Online Marketing & SEOvor 2 MonatenGoogle Unternehmensprofil optimieren ► Anleitung & Tipps
-
Künstliche Intelligenzvor 3 Monaten
Top 10: Der beste ergonomische Bürostuhl im Test – Herman Miller vor Flexispot
-
Künstliche Intelligenzvor 3 Monaten
Top 10: Der beste Saugroboter mit Wischfunktion im Test – Testsieger Roborock
-
UX/UI & Webdesignvor 3 MonatenSchriftfamilie Mainboard: Technisch und windschnittig › PAGE online
