NameDrop is developed by TakingNames.io for delegating control over DNS domains and subdomains. It is an open protocol, and implementation by others is encouraged.
NameDrop is based on OAuth2, with a few additions to facilitate domain name delegation.
One key difference from how OAuth2 is generally implemented, is that client
registration with the authorization server is not required before initiating
grant flows. To maintain a level of security, it is required that the
client_id
be a prefix string of the redirect_uri
, ie the redirect_uri
(which is where the token ends up) must be on the same domain as the
client_id
. This method is essentially what is described here. NameDrop
authorization servers should display the client_id
to users and inform them
that is who is requesting access.
All API endpoints described in this article are assumed to be appended to a base URL. For example, TakingNames.io uses
https://takingnames.io/namedrop
It is not necessary for the API name to start with /namedrop
, but it can
be useful for namespacing if the server has other non-NameDrop endpoints.
NameDrop scopes are prefixed with namedrop-
, in order to facilitate
composition with other OAuth2 protocols on the same authorization server.
The following scopes are currently specified:
namedrop-hosts
- grants control over A, AAAA, CNAME, and ALIAS (aka ANAME) recordsnamedrop-mail
- grants control over MX, DKIM TXT, and SPF TXT recordsnamedrop-acme
- grants control over ACME TXT recordsnamedrop-atproto-handle
- grants control over atprotodid=
TXT records (see here)
Permissions are granted to a FQDN (domain or subdomain). Currently this works
in a hierarchical fashion, ie if you have a token with permissions for
example.com
, you can create records for any subdomain of example.com
(*.example.com
). Likewise, if you have permissions for sub.example.com
, you
can create records for *.sub.example.com
, but not example.com
.
The basic OAuth2 endpoints are defined as follows:
GET /authorize
Authorization endpoint (user consent to get code). Can be a web browser redirect, or a direct link, such as one printed from a CLI application.
POST /token
Token endpoint (swap code for token). Always server-to-server.
Access tokens are returned as JSON in the following format:
{
"access_token": String(),
"refresh_token": String(),
"token_type": "bearer",
"expires_in": Number(),
"permissions": [
{
"scope": String(),
"domain": String(),
"host": String(),
}
]
}
Example:
{
"access_token": "lkjaslkajsoidfnaiosnf",
"refresh_token": "iousdoinfoiseofinsef",
"token_type": "bearer",
"expires_in": 3600,
"permissions": [
{
"scope": "namedrop-hosts",
"domain": "example.com",
"host": ""
},
{
"scope": "namedrop-mail",
"domain": "example.com",
"host": "mail"
},
{
"scope": "namedrop-acme",
"domain": "example.com",
"host": ""
}
]
}
Records are managed via a simple RPC API. All requests use the POST method
with a JSON body. The Content-Type
can be anything. This allows browser
clients to send
"simple"
requests that don't trigger CORS preflights, which are
an abomination. This is safe because all requests are authorized via the
included token property.
For create-records
, set-records
, and delete-records
, the top-level
domain
and host
properties are used as defaults for any records where they
are missing. This information can also be inferred from the token, assuming it
doesn't have permissions for multiple domains/hosts. This can make client code
less verbose.
type
is the record type such as A
, CNAME
, MX
, etc. ttl
and
priority
are both integers.
When setting value
for a record, the template variable {{host}}
can be
used. It will be replaced with the actual host value when the server evaluates
the record. This is particularly useful for things like DKIM and ACME challenge
records.
Retrieves current records. This could be done via DNS, but it's convenient to provide it as part of the NameDrop API to make things easier for clients.
The request is JSON in the following format:
{
"domain": String(),
"host": String(),
"token": String(),
}
Example:
{
"domain": "example.com",
"host": "sub",
"token": "lkjaslkajsoidfnaiosnf"
}
Create new records, returning an error if any duplicate records exist.
{
"domain": String(),
"host": String(),
"records": [
{
"domain": String(),
"host": String(),
"type": String(),
"value": String(),
"ttl": Number(),
"priority": Number(),
},
// more records
}
Example:
{
"domain": "example.com",
"host": "sub",
"token": "lkjaslkajsoidfnaiosnf",
"records": [
{
"type": "A",
"value": "192.168.0.1"
},
{
"host": "sub1._domainkey.{{host}}",
"type": "CNAME",
"value": "f1.example.com.dkim-server.com"
}
]
}
Set records, overriding any existing duplicate records.
{
"domain": String(),
"host": String(),
"records": [
{
"domain": String(),
"host": String(),
"type": String(),
"value": String(),
"ttl": Number(),
"priority": Number(),
},
// more records
}
Example:
{
"domain": "example.com",
"host": "sub",
"token": "lkjaslkajsoidfnaiosnf",
"records": [
{
"type": "A",
"value": "192.168.0.1"
},
{
"host": "sub1._domainkey.{{host}}",
"type": "CNAME",
"value": "f1.example.com.dkim-server.com"
}
]
}
Delete records, silently ignoring any records that don't exist.
{
"domain": String(),
"host": String(),
"records": [
{
"domain": String(),
"host": String(),
"type": String(),
"value": String(),
"ttl": Number(),
"priority": Number(),
},
// more records
}
Example:
{
"domain": "example.com",
"host": "sub",
"token": "lkjaslkajsoidfnaiosnf",
"records": [
{
"type": "A",
"value": "192.168.0.1"
},
{
"host": "sub1._domainkey.{{host}}",
"type": "CNAME",
"value": "f1.example.com.dkim-server.com"
}
]
}
Returns the public IP of the client, as observed from the server. This is useful for helping self-hosted clients test whether they can be reached by the outside world.
The IP is returned as a simple string.
This causes the server to create a special A
and/or AAAA
record pointing at
the client's IP address, as observed by the server. The domain must start with
the IP address, but with '.' or ':' characters replaced with '-'. The rest of
the domain can be anything. The created domain is returned as a simple string.
So, for example, TakingNames.io creates the record and returns something like this:
157-245-231-242.bootstrap.takingnames.live
The purpose of these domains is to allow the client to retrieve a TLS certificate from a service like LetsEncrypt, which makes the OAuth2 flows more secure. This is particularly useful for self-hosters who are trying to bootstrap a service that doesn't yet have a domain or certificate.
The server should ensure the domain remains valid for at least 5 minutes after a successful request, but no guarantees are required beyond that.