Skip to content

Instantly share code, notes, and snippets.

@knot126
Last active April 22, 2023 06:31
Show Gist options
  • Save knot126/ba506aa4db7efb9ad7286c01655d7723 to your computer and use it in GitHub Desktop.
Save knot126/ba506aa4db7efb9ad7286c01655d7723 to your computer and use it in GitHub Desktop.

Knot Auth Service and Protocol v0.0

The KnotAuth protocol is a basic authorisation protocol that uses basic HTTP POST + JSON requests authorise users.

It can be used similarly to OAuth 2 to allow apps to authorise and get basic information about users without gaining access to the user's password or any unneeded account details; however, it is not meant to fill the same role as OAuth 2.

One of the major differences to OAuth 2 is that it is NOT for granting access to other applications, and does not provide scoping beyond "do you really want to log in with this app?". It only allows the centralisation of and outsourcing of authorisation and password storage for the service altogther.

Overview

Register an app

When begining to create an app, you will need to register the app with the instance of KnotAuth that you want to use. How excatly this is done might vary between instances if you want to use thier respective GUI; however, it is standardised if you use the API. See the app API.

Registering your app will generate two things: an app ID and an app key.

The app ID is used to idenfity your app to the server and to users, especially when you want your app to retrive information.

The app key is used to verify that any requests your app makes to the central server are authorised.

Logging a user in: Global users

Global user mode involves sending the client to the auth server and using centralised credentials.

To begin the login flow, you send the user to the instance you want to use with a page prompting the user to join the app, along with the ID of your app. The user will then choose to accept or deny the app grant request.

For example: You might send the user to https://example.com/grant?app=wb4kq8jETeXOWrKY, which asks the user to log in to their current account and after that if they want to use this app.

If the users chooses to continue, the KnotAuth instance will create a grant for your app. A grant allows you to access some baisc info as well as feel confident that the user has logged in sucessfully.

After generating the grant successfully, it's ID and key will be passed on to your app. Currently, the only way to do this is via redirecting the user back to your app with the grant ID and key in the query string.

For example: The KnotAuth instance might redirect the user back to https://example.com/auth/granted?id=VEYUwa2feuuOpCDRrNneyw76gn2dRs9hffxAADo_PPY&key=VA7r9jgclLh-M9NjcLe9pRbXcuJee7PbVTAiYWGKFJE

The server should then verify that this grant ID is valid. If it is, it can then be used for the data API as well knowing that the user logged in sucessfully.

For example: Your app can make the following request to get valdiation about the grant and user info if the grant is valid:

POST /api/grant/identify HTTP/1.1
[ ... headers ... ]

{"app_id": "wb4kq8jETeXOWrKY", "app_key": "MySuperSecretAppKey", "grant_id": "VEYUwa2feuuOpCDRrNneyw76gn2dRs9hffxAADo_PPY", "grant_key": "VA7r9jgclLh-M9NjcLe9pRbXcuJee7PbVTAiYWGKFJE"}

This retrived information can then be used for basically whatever you like.

Logging a user in: App users

App users are like normal users, except that they are accounts exclusive to one app.

Generally, the user will create an account and log in using your user interface and API, which will then backend with the KnotAuth service for the heavy lifting of authentication.

It should be noted that this feature is optional for KnotAuth implementations.

Design

Generally, we want to make a moderately simple API for authentication that is still feature complete and secure.

We don't want to specify details that are not needed, but will make recommendations. For example, we don't require any specific representation of user tokens (lowercase hex? uppercase hex? email base64? base85?), nor to we impose any size limit or requirement (128-bits base 10? 32 urlsafe base64 chars? 128 base85 chars?); however, we do recommend the use of URL-safe base64 (or base32 on a case-insensitive platform), and we do recommend at least 256 bits of security on whatever representation you chose.

We will also sometimes do things that seem weird. For example, user tokens and grants require keys, which are basically passwords for tokens. Not all services do this and it may not be needed, but we do it becuase it has the potential to save your ass and minimise real world risk in some situations.

Key terms

  • App or Service: An app is anything that wants to request permission over the user's account.
  • Grant: Essentially, it's a session token for apps, created by the user when they authorise to the app.
  • Token: When we say "token", we usually mean the user's session token for the given KnotAuth instance.
  • User: An account on the KnotAuth instance that the service is using.
  • App User: A user account that is tied specifically to an app.
  • Data: Extra bits of information stored per-app.
  • Areas: The roles or permissions that a specific session token or user is allowed.

Request conventions

Requests are almost always POST requests with a JSON document as their body.

<token-and-key>

When you see <token-and-key>, this indicates that the request requires a valid user session token and its key. It is literally substituted as this:

  "token": <user-token>,
  "key": <user-key>,

For example:

POST /api/app/create

{
   "token": "YOUR TOKEN HERE",
   "key": "TOKEN KEY HERE",
   ... options go here ...
}

<grant-and-key>

When you see <grant-and-key>, this indicates that the request requires a valid user grant and a key. It is basically the same as <token-and-key>, except that the token feild is replaced with the grant_id feild and the key feild is replaced with the grant_key feild.

<app-id-and-key>

When you see <app-id-and-key>, this means the API requires a valid app ID and its private key. It is basically the same as <token-and-key>, but replaces the token feild with the app_id feild and the key felid with the app_key feild.

Endpoint responses

Endpoint responses, much like the requests, are always JSON documents.

Status part

While inputs to an endpoint can vary, there are two feilds that MUST exist in a response for it to be valid:

  • status: A short, alphanumeric ([A-Z|a-z|0-9]) with underscores (_) string representing the kind of error that occured. The strings to use are semi-standardised.
  • message: Human readable message that can be shown to the user about the error. These are not standardised and should only be used for displaying errors to humans.

The most important status string to remember is "done", which is used when an endpoint completes its task successfully and returns all data.

We will use <status-part> to represent the status part in endpoint descriptions.

App API

POST /api/app/info

{
  <token-and-key>,
  "app_id": <app-id>
}

Gets the public app info for users of the app. This will not show many of the app's settings; for that, see the next endpoint.

POST /api/app/config

{
  <token-and-key>,
  "app_id": <app-id>
}

Gets the configuration information about the app. This excludes the app key as it's expected to be hashed server side.

POST /api/app/create

{
  <token-and-key>,
  "title": <app-title>,
  "aeras": <areas>,
  "auth_mode": <auth-mode>,
  "auth_url": <auth-url>,
  "extra": {
    <extra-app-options>
  }
}

Create a new app with the following properties:

  • title: The name of the app that will be displayed.
  • areas: A list of user areas the app wants to access. This should at least include "userinfo" and may also include "data" and/or "areas".
  • auth_mode: The type of authentication this app will use. The <auth-mode> type is one of:
    • "get_with_qs": This app will use the standard GET request with a query string to log the user in.
    • "post_with_qs": The app does the same thing as "get_with_qs", but uses a POST request instead. This is more secure as the URL cannot be used to snipe the grant and key.
    • "post_and_await": Upon reciving the grant create request, the authentication server will send a POST request to your site's URL containing the grant, key, a one-time key for this grant and a magic password specified when configuring the app. The app is expected to store this grant by its one-time key, awaiting for the client to continue the process. After the auth service responds to the client, the client will then be redirected to a different URL with the one-time key in the query string. It is then expected that the app will assocaite this grant with the client by its one-time key and authorise the user.
    • "appuser": This app will use the App User API and won't use the centralised authentication system.
    • More may be available in the future, or extensions provided by KnotAuth server implementations.
  • auth_url: The URL to use for authentication, if relevant to the selected mode.
  • extra: Extra info about the app, usually implementation specific settings.

Note: As suggested, there is a weakness in "get_with_qs" that means if an attacker were to get the URL that the user was redirected to, they would have the grant and its key. This is ultimately a design weakness with grants and not adding another layer of indirection so that you could have a "pre-grant" key or a grant to make a grant that could then later be expired and therefore not serve as a threat; however, I don't really know if the extra design complexity is worth sacrificing for making GET requests more secure.

Successful response

{
  "status": "done",
  "message": "Your app was registered successfully.",
  "app_id": <app-id>,
  "app_key": <app-key>
}
  • app_id: The randomly generated app ID.
  • app_key: The randomly generated app key.

Important: This is the only time you will be able to get your app key from the server.

POST /api/app/update

{
  <token-and-key>,
  "title": <app-title>,
  "aeras": <areas>,
  "auth_mode": <auth-mode>,
  "auth_url": <auth-url>,
  "key_update": false | true,
  "extra": {
    <extra-app-options>
  }
}

Update information about the app. Every paramater is the same as /api/app/create except for key_update, which is set to true when you want to discard the current app key and use a new one.

POST /api/app/delete

{
  <token-and-key>,
  "app_id": <app-id>,
  "app_key": <app-key>
}

Delete the app from the authentication server. Becuase this action can be very dangerous if done accidentally, this requires the app key.

User API

The user API allows log ins and creations, as well as getting user info. The log in part of the API isn't meant to be used by apps, as this would require revealing the password to the application.

POST /api/user/create

{
  "handle": <handle>,
  "email": <email>,
  "password": <password> | null
}

This creates a new user account with the following settings:

  • handle: The handle to create the account by. The handle should follow any recommended naming requirements for the given server, though none are imposed by the Knot Auth protocol specifically.
  • email: The email of the user to register. Depending on the server, this may or may not be a required feild. If it is used, though, it should be a valid email address without "plus addresses" or tags.
  • password: Should be a strong password for the account. If null, the server should generate a random, cryptographically secure password for the user.

Note: What makes a strong password is up to the implementation to decide and provide details on, but a long password that is uncommon is recommended. We use the requirement of at least 16 characters, at least 12 of which must be unique.

Note: If you are an app developer and want to handle user registrations within the app, please consider using the App User API instead of the regular user API unless this instance of KnotAuth is only going to be used for one service. This will avoid your user's handles conflicting with the handles of users' on the auth server, and the other way around.

POST /api/user/login

{
  "handle": <handle>,
  "password": <password>,
  (opt) "areas": [],
  (opt) "expire": <expiry>
}
  • handle: The handle of the account to log in to.
  • password: The password of the account to log in to.
  • areas: What part of the account this token can affect. (optional)
  • expire: The number of seconds from right now that this token will expire. (optional)

Note: We recommend that the KnotAuth server implements limits on how long a token is allowed to last.

The areas feild is provided for two reasons:

  • If the user is logging in from an app, the user should not nessicarially be logging in to their main control panel for the KnotAuth instance as well.
  • We are preparing for possible future expasions to the protocol to enable it to be used more like OAuth.

POST /api/user/identify

{
  <token-and-key>
}

Verify that the user's token is valid and get their user ID and handle.

Response

{
  <status-part>,
  "valid": true | false,
  "user_id": <user-id> | null,
  "handle": <user-handle> | null,
  "expire": <token-expiry> | 0
}
  • valid: true if the token is valid, false otherwise.
  • user_id: The ID of the user, or null if the token isn't valid.
  • handle: The handle of the user, or null if the token isn't valid.
  • expire: The UNIX timestamp of when the token will expire, or 0 if the token is not valid.

POST /api/user/divorce

{
  <token-and-key>,
  "app_id": <app-id>
}

Remove a user's relationship with an app. All data and roles information will be deleted and any current grants are revoked.

Response

{
  "status": "done",
  "message": "The app's access to your account was revoked."
}

POST /api/user/logout

{
  <token-and-key>
}

Invalidates the given token and key.

Grant API

The grant API allows users to log in to a specific app, give it rights to access various account informations and APIs, and revoke the access later.

POST /api/grant/create

{
  <token-and-key>,
  "app_id": <app-id>,
  (opt) "expire": <expire-time>,
}
  • app_id: The app ID to grant access to the account.
  • expire: Number of seconds from right now the timestamp should expire. (optional)

Create a new grant for the app with the given ID. If the app and user don't already have a relationship, a new relationship is established with the app.

Note: We recommend that implementations limit the amount of time a grant can be created before it expires.

Response

{
  "grant_id": <grant-id>,
  "grant_key": <grant-key>
}

POST /api/grant/identify

{
  <app-id-and-key>,
  <grant-and-key>
}

Get information on a grant and valdiate that it is real.

Response

Same as /api/user/identify.

POST /api/grant/delete

{
  <grant-and-key>
}

Given either the grant and key, the grant is deleted.

Areas API

Optional

The areas API allows you to assign roles to users that can be used for resticting access to certian areas of an app if the user doesn't have the right permissions.

Data API

Optional

The Data API allows you to store a small amount of (probably authentication related) data with each user.

Your data is generally some kind of JSON data, and most implementations will limit it to around 20 KiB.

Other misc. functions

GET | POST /api/ping

Pings the server to check if it is online.

Response

{
  "status": "done",
  "message": "Pong!"
}

This endpoint cannot fail!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment