user2815: I am playing around with the nami cardano wallet. I am able to link the wallet in the frontend and now want to link it to the backend.
First action the frontend sends an auth request with the cardano address to the backend. The backend returns a nonce to the frontend. The front end then lets nami wallet sign this nonce and sends the signature back to the backend.
In this step, the backend should validate if the signature is valid.
Now, how can I let the backend validate this signature?
This is the information I have access to:

balance
change address
network id
reward address
unused addresses
used addresses

Can I use this info to validate the signature? Or do I need to access the blockchain from the backend code?
(I will most likely be adding blockfrost api later, so nami-wallet + blockfrost will be how I access the blockchain. Unless there are other libraries available for the backend (most likely node.js), nami+blockfrost will be how I access the blockchain.
I am also using @emurgo/cardano-serialization-lib-browser - maybe here something is possible?
Based on response below I made a quick node script:
const {COSESign1, Label} = require("./extra_modules/@emurgo/cardano-message-signing-nodejs");
const {Address} = require("@emurgo/cardano-serialization-lib-nodejs");
const {Buffer} = require('buffer');
let signedRaw = "845869a30127045820674d11e432450118d70ea78673d5e31d5cc1aec63de0ff6284784876544be3406761646472657373583901d2eb831c6cad4aba700eb35f86966fbeff19d077954430e32ce65e8da79a3abe84f4ce817fad066acc1435be2ffc6bd7dce2ec1cc6cca6cba166686173686564f44568656c6c6f5840a3b5acd99df5f3b5e4449c5a116078e9c0fcfc126a4d4e2f6a9565f40b0c77474cafd89845e768fae3f6eec0df4575fcfe7094672c8c02169d744b415c617609";
let message = COSESign1.from_bytes(Buffer.from(signedRaw, 'hex'));
let headers = message.headers().protected().deserialized_headers();
let address = Address.from_bytes(headers.header(Label.new_text('address')).as_bytes());
let publicKey = headers.key_id();
let signature = message.signature();

OK, finally got a working solution, sharing a node script that works for the next person with the same problem:
const {COSESign1, Label} = require("./extra_modules/@emurgo/cardano-message-signing-nodejs");
const {Address, Ed25519Signature, PublicKey, BaseAddress, StakeCredential, RewardAddress} = require('@emurgo/cardano-serialization-lib-nodejs');
const {Buffer} = require('buffer');

class SignedData
{
    constructor(signed)
    {
        let message = COSESign1.from_bytes(Buffer.from(Buffer.from(signed, 'hex'), 'hex'));
        let headermap = message.headers().protected().deserialized_headers();
        this.headers = {
            algorithmId: headermap.algorithm_id().as_int().as_i32(),
            address: Address.from_bytes(headermap.header(Label.new_text('address')).as_bytes()),
            publicKey: PublicKey.from_bytes(headermap.key_id())
        };
        this.payload = message.payload();
        this.signature = Ed25519Signature.from_bytes(message.signature());
        this.data = message.signed_data().to_bytes();
    }

    verify(address, payload)
    {
        if (!this.verifyPayload(payload)) {
            throw new Error('Payload does not match');
        }
        if (!this.verifyAddress(address)) {
            throw new Error('Could not verify because of address mismatch');
        }
        return this.headers.publicKey.verify(this.data, this.signature);
    };

    verifyPayload(payload)
    {
        return Buffer.from(this.payload, 'hex').compare(Buffer.from(payload, 'hex'));
    }

    verifyAddress(address)
    {
        const checkAddress = Address.from_bech32(address);
        if (this.headers.address.to_bech32() !== checkAddress.to_bech32()) {
            console.log('FASLE1');
            return false;
        }
        // check if BaseAddress
        try {
            const baseAddress = BaseAddress.from_address(this.headers.address);
            //reconstruct address
            const paymentKeyHash = this.headers.publicKey.hash();
            const stakeKeyHash = baseAddress.stake_cred().to_keyhash();
            const reconstructedAddress = BaseAddress.new(
                checkAddress.network_id(),
                StakeCredential.from_keyhash(paymentKeyHash),
                StakeCredential.from_keyhash(stakeKeyHash)
            );
            return checkAddress.to_bech32() === reconstructedAddress.to_address().to_bech32();
        } catch (e) {
        }

        try {
            const stakeKeyHash = this.headers.address.hash();
            const reconstructedAddress = RewardAddress.new(
                checkAddress.network_id(),
                StakeCredential.from_keyhash(stakeKeyHash)
            );
            return checkAddress.to_bech32() === reconstructedAddress.to_address().to_bech32();
        } catch (e) {
        }
        return false;
    };
}
let signed = "845869a30127045820674d11e432450118d70ea78673d5e31d5cc1aec63de0ff6284784876544be3406761646472657373583901d2eb831c6cad4aba700eb35f86966fbeff19d077954430e32ce65e8da79a3abe84f4ce817fad066acc1435be2ffc6bd7dce2ec1cc6cca6cba166686173686564f44568656c6c6f5840a3b5acd99df5f3b5e4449c5a116078e9c0fcfc126a4d4e2f6a9565f40b0c77474cafd89845e768fae3f6eec0df4575fcfe7094672c8c02169d744b415c617609";
let signedData = new SignedData(signed);
let address = 'addr1q8fwhqcudjk54wnsp6e4lp5kd7l07xwsw725gv8r9nn9ard8ngatap85e6qhltgxdtxpgdd79l7xh47uutkpe3kv5m9smpyc34';
let payload = 'hello';
console.log(signedData.verify(address, payload));


user1045: I've created javascript package that could help you with this. The examples in the README should show you how to sign data on frontend and validate the signature on the backend.

user6163: WARNING
The provided solution in the question is a great start, however
 verifyPayload(payload)
{
    return Buffer.from(this.payload, 'hex').compare(Buffer.from(payload, 'hex'));
}

Does not do a correct comparison since in the example the payload to check is not hexencoded but rather a string, so use this instead
    verifyPayload(payload)
{
    let hexMessage=Buffer.from(this.payload, 'hex');
    let stringMessage=hexMessage.toString();
    return stringMessage===payload;
}


user1097: You will need to send your verification key from Nami to the backend during the handshake in order to validate the signature.
cardano.signData(address: BaseAddress|RewardAddress, payload: string) : CoseSign1


The returned CoseSign1 object contains the payload, signature and the following protected headers:
key_id => PublicKey,
address => BaseAddress | RewardAddress
algorithm_id => EdDSA(0) (the algorithm used for Cardano addresses).

Source: Nami Readme
When you run the cardano.SignData method, you are returned an object that includes the public key for the private key that you used to sign the payload. Thus, when you send the signed object to the backend, the backend has everything it needs in the header to validate that transaction. To avoid hacking, you should also ensure that the public key is constant throughout communication with the same entity or in the same session.
The output is a hex-encoded byte string adhering to the CBOR format standard. Here is a link to a playground that will let you convert between the CBOR that is the output (e.g., "CoseSign1" from the readme) and something readable. Here is an example output (don't forget to check all of the boxes to get the full result).
[<< {1: -8, 4: h'ABF29B244C2E1C4DD3CA2F8DB27D87C6B73DA1A87D08389101D5D1ECEBDD0079', "address": h'E1DC0C1BD97FCD0ED6905EBA692C1DDEFF27DA43D59C5A1F2EA766442E'} >>, {"hashed": false}, 'this is a test\n', h'1F19958A76C7414F311F5176A52A544A4BE9D59FC5F3870F41B00D8B8C52C9B819953D10AE3F23C0D906A138D37D6C70DDADE77DB94D7054D730618106F18108']

It is a JSON array with two parts: the first is the header, and the second is the contents. According to CIP-0008, the block is expected to contain the following.
COSE_Sign1 = [
    Headers,
    payload : bstr / nil,
    signature : bstr
]

My guess is that the pub key is mapped to 4: ABF29B244C2E1C4DD3CA2F8DB27D87C6B73DA1A87D08389101D5D1ECEBDD0079
Hope this helps!

user2815: Script with the solution (node JS):
const {COSESign1, Label} = require("./extra_modules/@emurgo/cardano-message-signing-nodejs");
const {Address, Ed25519Signature, PublicKey, BaseAddress, StakeCredential, RewardAddress} = require('@emurgo/cardano-serialization-lib-nodejs');
const {Buffer} = require('buffer');

class SignedData
{
    constructor(signed)
    {
        let message = COSESign1.from_bytes(Buffer.from(Buffer.from(signed, 'hex'), 'hex'));
        let headermap = message.headers().protected().deserialized_headers();
        this.headers = {
            algorithmId: headermap.algorithm_id().as_int().as_i32(),
            address: Address.from_bytes(headermap.header(Label.new_text('address')).as_bytes()),
            publicKey: PublicKey.from_bytes(headermap.key_id())
        };
        this.payload = message.payload();
        this.signature = Ed25519Signature.from_bytes(message.signature());
        this.data = message.signed_data().to_bytes();
    }

    verify(address, payload)
    {
        if (!this.verifyPayload(payload)) {
            throw new Error('Payload does not match');
        }
        if (!this.verifyAddress(address)) {
            throw new Error('Could not verify because of address mismatch');
        }
        return this.headers.publicKey.verify(this.data, this.signature);
    };

    verifyPayload(payload)
    {
        return Buffer.from(this.payload, 'hex').compare(Buffer.from(payload, 'hex'));
    }

    verifyAddress(address)
    {
        const checkAddress = Address.from_bech32(address);
        if (this.headers.address.to_bech32() !== checkAddress.to_bech32()) {
            console.log('FASLE1');
            return false;
        }
        // check if BaseAddress
        try {
            const baseAddress = BaseAddress.from_address(this.headers.address);
            //reconstruct address
            const paymentKeyHash = this.headers.publicKey.hash();
            const stakeKeyHash = baseAddress.stake_cred().to_keyhash();
            const reconstructedAddress = BaseAddress.new(
                checkAddress.network_id(),
                StakeCredential.from_keyhash(paymentKeyHash),
                StakeCredential.from_keyhash(stakeKeyHash)
            );
            return checkAddress.to_bech32() === reconstructedAddress.to_address().to_bech32();
        } catch (e) {
        }

        try {
            const stakeKeyHash = this.headers.address.hash();
            const reconstructedAddress = RewardAddress.new(
                checkAddress.network_id(),
                StakeCredential.from_keyhash(stakeKeyHash)
            );
            return checkAddress.to_bech32() === reconstructedAddress.to_address().to_bech32();
        } catch (e) {
        }
        return false;
    };
}
let signed = "845869a30127045820674d11e432450118d70ea78673d5e31d5cc1aec63de0ff6284784876544be3406761646472657373583901d2eb831c6cad4aba700eb35f86966fbeff19d077954430e32ce65e8da79a3abe84f4ce817fad066acc1435be2ffc6bd7dce2ec1cc6cca6cba166686173686564f44568656c6c6f5840a3b5acd99df5f3b5e4449c5a116078e9c0fcfc126a4d4e2f6a9565f40b0c77474cafd89845e768fae3f6eec0df4575fcfe7094672c8c02169d744b415c617609";
let signedData = new SignedData(signed);
let address = 'addr1q8fwhqcudjk54wnsp6e4lp5kd7l07xwsw725gv8r9nn9ard8ngatap85e6qhltgxdtxpgdd79l7xh47uutkpe3kv5m9smpyc34';
let payload = 'hello';
console.log(signedData.verify(address, payload));


2815: <p>I am playing around with the nami cardano wallet. I am able to link the wallet in the frontend and now want to link it to the backend.</p>
<p>First action the frontend sends an auth request with the cardano address to the backend. The backend returns a nonce to the frontend. The front end then lets nami wallet sign this nonce and sends the signature back to the backend.</p>
<p>In this step, the backend should validate if the signature is valid.</p>
<p>Now, how can I let the backend validate this signature?</p>
<p>This is the information I have access to:</p>
<ul>
<li>balance</li>
<li>change address</li>
<li>network id</li>
<li>reward address</li>
<li>unused addresses</li>
<li>used addresses</li>
</ul>
<p>Can I use this info to validate the signature? Or do I need to access the blockchain from the backend code?</p>
<p>(I will most likely be adding blockfrost api later, so nami-wallet + blockfrost will be how I access the blockchain. Unless there are other libraries available for the backend (most likely node.js), nami+blockfrost will be how I access the blockchain.</p>
<p>I am also using @emurgo/cardano-serialization-lib-browser - maybe here something is possible?</p>
<p>Based on response below I made a quick node script:</p>
<pre><code>const {COSESign1, Label} = require(&quot;./extra_modules/@emurgo/cardano-message-signing-nodejs&quot;);
const {Address} = require(&quot;@emurgo/cardano-serialization-lib-nodejs&quot;);
const {Buffer} = require('buffer');
let signedRaw = &quot;845869a30127045820674d11e432450118d70ea78673d5e31d5cc1aec63de0ff6284784876544be3406761646472657373583901d2eb831c6cad4aba700eb35f86966fbeff19d077954430e32ce65e8da79a3abe84f4ce817fad066acc1435be2ffc6bd7dce2ec1cc6cca6cba166686173686564f44568656c6c6f5840a3b5acd99df5f3b5e4449c5a116078e9c0fcfc126a4d4e2f6a9565f40b0c77474cafd89845e768fae3f6eec0df4575fcfe7094672c8c02169d744b415c617609&quot;;
let message = COSESign1.from_bytes(Buffer.from(signedRaw, 'hex'));
let headers = message.headers().protected().deserialized_headers();
let address = Address.from_bytes(headers.header(Label.new_text('address')).as_bytes());
let publicKey = headers.key_id();
let signature = message.signature();
</code></pre>
<p>OK, finally got a working solution, sharing a node script that works for the next person with the same problem:</p>
<pre><code>const {COSESign1, Label} = require(&quot;./extra_modules/@emurgo/cardano-message-signing-nodejs&quot;);
const {Address, Ed25519Signature, PublicKey, BaseAddress, StakeCredential, RewardAddress} = require('@emurgo/cardano-serialization-lib-nodejs');
const {Buffer} = require('buffer');

class SignedData
{
    constructor(signed)
    {
        let message = COSESign1.from_bytes(Buffer.from(Buffer.from(signed, 'hex'), 'hex'));
        let headermap = message.headers().protected().deserialized_headers();
        this.headers = {
            algorithmId: headermap.algorithm_id().as_int().as_i32(),
            address: Address.from_bytes(headermap.header(Label.new_text('address')).as_bytes()),
            publicKey: PublicKey.from_bytes(headermap.key_id())
        };
        this.payload = message.payload();
        this.signature = Ed25519Signature.from_bytes(message.signature());
        this.data = message.signed_data().to_bytes();
    }

    verify(address, payload)
    {
        if (!this.verifyPayload(payload)) {
            throw new Error('Payload does not match');
        }
        if (!this.verifyAddress(address)) {
            throw new Error('Could not verify because of address mismatch');
        }
        return this.headers.publicKey.verify(this.data, this.signature);
    };

    verifyPayload(payload)
    {
        return Buffer.from(this.payload, 'hex').compare(Buffer.from(payload, 'hex'));
    }

    verifyAddress(address)
    {
        const checkAddress = Address.from_bech32(address);
        if (this.headers.address.to_bech32() !== checkAddress.to_bech32()) {
            console.log('FASLE1');
            return false;
        }
        // check if BaseAddress
        try {
            const baseAddress = BaseAddress.from_address(this.headers.address);
            //reconstruct address
            const paymentKeyHash = this.headers.publicKey.hash();
            const stakeKeyHash = baseAddress.stake_cred().to_keyhash();
            const reconstructedAddress = BaseAddress.new(
                checkAddress.network_id(),
                StakeCredential.from_keyhash(paymentKeyHash),
                StakeCredential.from_keyhash(stakeKeyHash)
            );
            return checkAddress.to_bech32() === reconstructedAddress.to_address().to_bech32();
        } catch (e) {
        }

        try {
            const stakeKeyHash = this.headers.address.hash();
            const reconstructedAddress = RewardAddress.new(
                checkAddress.network_id(),
                StakeCredential.from_keyhash(stakeKeyHash)
            );
            return checkAddress.to_bech32() === reconstructedAddress.to_address().to_bech32();
        } catch (e) {
        }
        return false;
    };
}
let signed = &quot;845869a30127045820674d11e432450118d70ea78673d5e31d5cc1aec63de0ff6284784876544be3406761646472657373583901d2eb831c6cad4aba700eb35f86966fbeff19d077954430e32ce65e8da79a3abe84f4ce817fad066acc1435be2ffc6bd7dce2ec1cc6cca6cba166686173686564f44568656c6c6f5840a3b5acd99df5f3b5e4449c5a116078e9c0fcfc126a4d4e2f6a9565f40b0c77474cafd89845e768fae3f6eec0df4575fcfe7094672c8c02169d744b415c617609&quot;;
let signedData = new SignedData(signed);
let address = 'addr1q8fwhqcudjk54wnsp6e4lp5kd7l07xwsw725gv8r9nn9ard8ngatap85e6qhltgxdtxpgdd79l7xh47uutkpe3kv5m9smpyc34';
let payload = 'hello';
console.log(signedData.verify(address, payload));
</code></pre>
 1045: <p>I've created <a href="https://github.com/pyropy/web3-cardano-token" rel="nofollow noreferrer">javascript package</a> that could help you with this. The examples in the README should show you how to sign data on frontend and validate the signature on the backend.</p>
 6163: <p>WARNING</p>
<p>The provided solution in the question is a great start, however</p>
<pre><code> verifyPayload(payload)
{
    return Buffer.from(this.payload, 'hex').compare(Buffer.from(payload, 'hex'));
}
</code></pre>
<p>Does not do a correct comparison since in the example the payload to check is not hexencoded but rather a string, so use this instead</p>
<pre><code>    verifyPayload(payload)
{
    let hexMessage=Buffer.from(this.payload, 'hex');
    let stringMessage=hexMessage.toString();
    return stringMessage===payload;
}
</code></pre>
 1097: <p>You will need to send your verification key from Nami to the backend during the handshake in order to validate the signature.</p>
<pre><code>cardano.signData(address: BaseAddress|RewardAddress, payload: string) : CoseSign1
</code></pre>
<blockquote>
<p>The returned CoseSign1 object contains the payload, signature and the following protected headers:</p>
<p>key_id =&gt; PublicKey,
address =&gt; BaseAddress | RewardAddress
algorithm_id =&gt; EdDSA(0) (the algorithm used for Cardano addresses).</p>
</blockquote>
<p>Source: <a href="https://github.com/Berry-Pool/nami-wallet#cardanosigndataaddress-payload" rel="nofollow noreferrer">Nami Readme</a></p>
<p>When you run the cardano.SignData method, you are returned an object that includes the public key for the private key that you used to sign the payload. Thus, when you send the signed object to the backend, the backend has everything it needs in the header to validate that transaction. To avoid hacking, you should also ensure that the public key is constant throughout communication with the same entity or in the same session.</p>
<p>The output is a hex-encoded byte string adhering to the CBOR format standard. <a href="http://cbor.me" rel="nofollow noreferrer">Here</a> is a link to a playground that will let you convert between the CBOR that is the output (e.g., &quot;CoseSign1&quot; from the readme) and something readable. Here is an example output (don't forget to check all of the boxes to get the full result).</p>
<pre><code>[&lt;&lt; {1: -8, 4: h'ABF29B244C2E1C4DD3CA2F8DB27D87C6B73DA1A87D08389101D5D1ECEBDD0079', &quot;address&quot;: h'E1DC0C1BD97FCD0ED6905EBA692C1DDEFF27DA43D59C5A1F2EA766442E'} &gt;&gt;, {&quot;hashed&quot;: false}, 'this is a test\n', h'1F19958A76C7414F311F5176A52A544A4BE9D59FC5F3870F41B00D8B8C52C9B819953D10AE3F23C0D906A138D37D6C70DDADE77DB94D7054D730618106F18108']
</code></pre>
<p>It is a JSON array with two parts: the first is the header, and the second is the contents. According to <a href="https://github.com/cardano-foundation/CIPs/blob/master/CIP-0008/CIP-0008.md" rel="nofollow noreferrer">CIP-0008</a>, the block is expected to contain the following.</p>
<pre><code>COSE_Sign1 = [
    Headers,
    payload : bstr / nil,
    signature : bstr
]
</code></pre>
<p>My guess is that the pub key is mapped to 4: <code>ABF29B244C2E1C4DD3CA2F8DB27D87C6B73DA1A87D08389101D5D1ECEBDD0079</code></p>
<p>Hope this helps!</p>
 2815: <p>Script with the solution (node JS):</p>
<pre><code>const {COSESign1, Label} = require(&quot;./extra_modules/@emurgo/cardano-message-signing-nodejs&quot;);
const {Address, Ed25519Signature, PublicKey, BaseAddress, StakeCredential, RewardAddress} = require('@emurgo/cardano-serialization-lib-nodejs');
const {Buffer} = require('buffer');

class SignedData
{
    constructor(signed)
    {
        let message = COSESign1.from_bytes(Buffer.from(Buffer.from(signed, 'hex'), 'hex'));
        let headermap = message.headers().protected().deserialized_headers();
        this.headers = {
            algorithmId: headermap.algorithm_id().as_int().as_i32(),
            address: Address.from_bytes(headermap.header(Label.new_text('address')).as_bytes()),
            publicKey: PublicKey.from_bytes(headermap.key_id())
        };
        this.payload = message.payload();
        this.signature = Ed25519Signature.from_bytes(message.signature());
        this.data = message.signed_data().to_bytes();
    }

    verify(address, payload)
    {
        if (!this.verifyPayload(payload)) {
            throw new Error('Payload does not match');
        }
        if (!this.verifyAddress(address)) {
            throw new Error('Could not verify because of address mismatch');
        }
        return this.headers.publicKey.verify(this.data, this.signature);
    };

    verifyPayload(payload)
    {
        return Buffer.from(this.payload, 'hex').compare(Buffer.from(payload, 'hex'));
    }

    verifyAddress(address)
    {
        const checkAddress = Address.from_bech32(address);
        if (this.headers.address.to_bech32() !== checkAddress.to_bech32()) {
            console.log('FASLE1');
            return false;
        }
        // check if BaseAddress
        try {
            const baseAddress = BaseAddress.from_address(this.headers.address);
            //reconstruct address
            const paymentKeyHash = this.headers.publicKey.hash();
            const stakeKeyHash = baseAddress.stake_cred().to_keyhash();
            const reconstructedAddress = BaseAddress.new(
                checkAddress.network_id(),
                StakeCredential.from_keyhash(paymentKeyHash),
                StakeCredential.from_keyhash(stakeKeyHash)
            );
            return checkAddress.to_bech32() === reconstructedAddress.to_address().to_bech32();
        } catch (e) {
        }

        try {
            const stakeKeyHash = this.headers.address.hash();
            const reconstructedAddress = RewardAddress.new(
                checkAddress.network_id(),
                StakeCredential.from_keyhash(stakeKeyHash)
            );
            return checkAddress.to_bech32() === reconstructedAddress.to_address().to_bech32();
        } catch (e) {
        }
        return false;
    };
}
let signed = &quot;845869a30127045820674d11e432450118d70ea78673d5e31d5cc1aec63de0ff6284784876544be3406761646472657373583901d2eb831c6cad4aba700eb35f86966fbeff19d077954430e32ce65e8da79a3abe84f4ce817fad066acc1435be2ffc6bd7dce2ec1cc6cca6cba166686173686564f44568656c6c6f5840a3b5acd99df5f3b5e4449c5a116078e9c0fcfc126a4d4e2f6a9565f40b0c77474cafd89845e768fae3f6eec0df4575fcfe7094672c8c02169d744b415c617609&quot;;
let signedData = new SignedData(signed);
let address = 'addr1q8fwhqcudjk54wnsp6e4lp5kd7l07xwsw725gv8r9nn9ard8ngatap85e6qhltgxdtxpgdd79l7xh47uutkpe3kv5m9smpyc34';
let payload = 'hello';
console.log(signedData.verify(address, payload));
</code></pre>
