Tokens
A token represents a valid session for an authenticated user associated with your application. Each token is designed with the following characteristics:
- Passwordless: Users only need an active email account to receive a token. There is no requirement for passwords or additional credentials, simplifying the authentication process.
- Self-contained: Each token embeds everything needed to verify it — the expiration time, a signature and the user’s email — so it can be validated with nothing more than the app configuration and secret.
- Signed: The token is signed with Ed25519 using a key derived from your App ID and secret, so any modification invalidates the signature.
- Temporal: Tokens are created with a defined lifespan for security purposes. The expiration time is specified in the application’s configuration during its creation process.
About the Token Structure
Tokens are generated by the service and delivered to users via email. They are included as the token query parameter (alongside the user email) in a magic link, which uses the application’s redirect_url. This process ensures that the token reaches the intended recipient and that it is only valid for a limited period.
A token consists of three parts, separated by a dot:
<expiration>.<signature>.<email>
For example:
MjAyNS0wMy0zMVQwMjoyNToyNC4xMjc3NDkrMDI6MDA.6VjTMr+lyUXtxNT7MZ17E/Mvdhtx87bJM8cTQ9El8ERokB+K9ew0AWRkI5vUjmKv1Slq5nvlfdqypege0oXgCg.dXNlckBleGFtcGxlLmNvbQ
Each part is a base64url-encoded value:
- Expiration: the moment the token expires, encoded from its RFC 3339 timestamp.
- Signature: the Ed25519 signature over
sha256(AppID || email || expiration), computed with a private key deterministically derived from the full secret and the App ID. The signature makes the token tamper-evident: verifying it proves the token was issued by your application and has not been altered. - Email: the base64url encoding of the authenticated user’s email address.
Token secret
The full secret used to sign tokens is calculated by hashing two parts with sha256: one remains private in the service (to ensure that the token comes from the right instance) and another defined by the app creator (to ensure that it comes from your app).
Request a Token via the API
Request a token for a user by sending their email to the POST /tokens endpoint, authenticating with your App ID and secret headers:
POST /tokens
X-SIMPLEAUTHLINK-APPID: <your-app-id>
X-SIMPLEAUTHLINK-SECRET: <your-app-secret>
Content-Type: application/json
{
"email": "user@example.com"
}
A magic link is sent to that email address. The link is the app’s redirect_url with the token and user query parameters appended.
Verify a Token via the API
Verify a token (for example, when the user returns through the magic link) by sending it to the PUT /tokens endpoint:
PUT /tokens
X-SIMPLEAUTHLINK-APPID: <your-app-id>
X-SIMPLEAUTHLINK-SECRET: <your-app-secret>
Content-Type: application/json
{
"token": "<the-token-from-the-magic-link>"
}
Response:
{
"valid": true,
"expiration": "2025-03-31T02:25:24.127749+02:00"
}
Go client examples
Request and verify a token
import (
"fmt"
"log"
"github.com/simpleauthlink/authapi/api/client"
"github.com/simpleauthlink/authapi/token"
)
func main() {
cli, err := client.Default("<your-app-id>", "<your-app-secret>", false)
if err != nil {
log.Fatal(err)
}
// send a login email to the user
err = cli.RequestToken(new(token.Email).SetString("user@example.com"))
if err != nil {
log.Fatal(err)
}
// the user receives the token by email (the magic link)
rawToken := "<the-token-from-the-magic-link>"
userToken := new(token.Token).SetString(rawToken)
// verify it
valid, expiresAt, err := cli.VerifyToken(userToken)
if err != nil {
log.Fatal(err)
}
if valid {
fmt.Printf("session valid until %s\n", expiresAt)
}
}
Verify a token on an incoming request
Once the user returns through the magic link, the token arrives as a token query parameter on your redirect_url (or as an X-SIMPLEAUTHLINK-TOKEN header). You can validate it inside your HTTP handler:
// token delivered as a query parameter
email, valid, err := cli.AuthorizedRequestURLParams(r)
// token delivered as a request header
email, valid, err := cli.AuthorizedRequestHeaders(r)
Both helpers return the authenticated user’s email and whether the token is currently valid.