README
¶
aaa

AAA - Authentication, Authorization & Accounting
Motivation
The package provides AAA features for http router github.com/axkit/vatel using JWT. AAA plays a proxy between users/roles/permission storage and vatel.
Concepts
- JTW is used.
- AAA is independent of users, roles and permissions storage structure.
- AAA does not know password encryption approach.
- Developer can extend token payload.
Endpoints
Sign in
POST /auth/sign-in Input
{
"login" : "user1"
"password" : "plain-or-encrypted-password",
}
Output Successfull: HTTP 200
{
"access_token" : "abc.."
"refresh_token": "zyx..."
"permissions" : ["PermissionCode1","PermissionCode2", "PermissionCode3"...]
}
Access token holds following user specified payload inside:
{
"user": 42,
"login": "user1",
"role": 1,
"perms": "ZTY1ZmZmN2YyZmVlYzNlZmJjN2RmZmJmZGNmM2Y3ZmYzZjlmZmRmZmZmN2Y3NWJkMDE="
}
Refresh token holds user specified payload inside:
{
"user": 42
}
Failed: HTTP 401
{
"message" : "invalid cridentials"
}
Access token validation
POST /auth/is-token-valid Output Successfull: HTTP 200
{
"result" : "ok"
}
Failed: HTTP 401
{
"message" : "invalid token"
}
Refresh token
POST /auth/refresh-token Input
{
"refresh_token" : "xyz.."
}
Output Successfull: HTTP 200
{
"access_token" : "abc.."
"refresh_token": "zyx..."
"permissions" : ["PermissionCode1","PermissionCode2", "PermissionCode3"...]
}
Failed: HTTP 401
{
"message" : "invalid token"
}
- Application functionality can be limited by using permissions.
- Permission (access right) represented by unique string code.
- Application can have many permissions.
- A user has a role.
- A role is set of allowed permission, it's subset of all permissions supported by application.
- As a result of succesfull sign in backend provides access and resresh tokens.
- Payload of access token shall have list of allowed permissions.
- A single permission code looks like "Customers.Create", "Customer.AttachDocuments", "Customer.Edit", etc.
- Store allowed permission codes could increase token size.
- Bitset comes here.
- Every permission shall be accociated with a single bit in the set.
- Bitset adds to the token as hexadecimal string.
Usage Examples
Sign In
var perms bitset.Bitset
perms.Set(1) // 0000_0010
perms.Set(2) // 0000_0110
perms.Set(8, 10) // 0000_0110 0000_0101
tokenPerms := perms.String() // returns "0605"
Check allowed permission in auth middleware
...
tokenPerms := accessToken.Payload.Perms // "0605
bs := bitset.Parse(tokenPerms) // returns 0000_0110 0000_0101
if bs.AreSet(2,8) {
// the permission allowed
}
Further Improvements
- Finalize integration BitSet with database/sql
- Add benchmarks
- Reduce memory allocations
Prague 2020
curl examples
curl 127.0.0.1:8083/api/auth/sign-in -d '{"login" : "testadmin", "password":"test"}'
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJlbGVwaGFudHNvZnQiLCJzdWIiOiJ0YXVkaXQiLCJhdWQiOiJodHRwczovL2VsZXBoYW50c29mdC5ydSIsImV4cCI6MTU4NjI0ODQ3MywiaWF0IjoxNTg2MjQ2NjczLCJqdGkiOiJ0ZXN0IiwidXNlcl9pZCI6MTEsImxvZ2luIjoidGVzdGFkbWluIiwicm9sZV9pZCI6MSwicGVybV9iaXRzZXQiOjcsImV4dHJhIjpudWxsfQ.1u_UbBAPHIg819JqJjzDHKsaW2wZBMVcEYjt92FRRWw","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJlbGVwaGFudHNvZnQiLCJzdWIiOiJ0YXVkaXQiLCJhdWQiOiJodHRwczovL2VsZXBoYW50c29mdC5ydSIsImV4cCI6MTU4ODgzODY3MywiaWF0IjoxNTg2MjQ2NjczLCJqdGkiOiJ0ZXN0IiwidXNlcl9pZCI6MTF9.x7383jbhlhk2VhABF8YfgjUY3SNp5_GFqA3lcctupjs","allowed_permissions":{"TestCreateEntity","TestDeleteEntity","TestUpdateEntity"}}
curl -X POST 127.0.0.1:8083/api/auth/is-token-valid -H "Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJlbGVwaGFudHNvZnQiLCJzdWIiOiJ0YXVkaXQiLCJhdWQiOiJodHRwczovL2VsZXBoYW50c29mdC5ydSIsImV4cCI6MTU4NjI0ODQ3MywiaWF0IjoxNTg2MjQ2NjczLCJqdGkiOiJ0ZXN0IiwidXNlcl9pZCI6MTEsImxvZ2luIjoidGVzdGFkbWluIiwicm9sZV9pZCI6MSwicGVybV9iaXRzZXQiOjcsImV4dHJhIjpudWxsfQ.1u_UbBAPHIg819JqJjzDHKsaW2wZBMVcEYjt92FRRWw"
{"result" : "ok"}
curl 127.0.0.1:8083/api/auth/refresh-token -d '{"refresh_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJlbGVwaGFudHNvZnQiLCJzdWIiOiJ0YXVkaXQiLCJhdWQiOiJodHRwczovL2VsZXBoYW50c29mdC5ydSIsImV4cCI6MTU4ODgzODY3MywiaWF0IjoxNTg2MjQ2NjczLCJqdGkiOiJ0ZXN0IiwidXNlcl9pZCI6MTF9.x7383jbhlhk2VhABF8YfgjUY3SNp5_GFqA3lcctupjs"}'
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJlbGVwaGFudHNvZnQiLCJzdWIiOiJ0YXVkaXQiLCJhdWQiOiJodHRwczovL2VsZXBoYW50c29mdC5ydSIsImV4cCI6MTU4NjI0ODY1NiwiaWF0IjoxNTg2MjQ2ODU2LCJqdGkiOiJ0ZXN0IiwidXNlcl9pZCI6MTEsImxvZ2luIjoidGVzdGFkbWluIiwicm9sZV9pZCI6MSwicGVybV9iaXRzZXQiOjcsImV4dHJhIjpudWxsfQ.3wD4cfhOFFu_ZTV1jgPz_PcMPvt4MVoHLacUW2QCxG4","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJlbGVwaGFudHNvZnQiLCJzdWIiOiJ0YXVkaXQiLCJhdWQiOiJodHRwczovL2VsZXBoYW50c29mdC5ydSIsImV4cCI6MTU4ODgzODg1NiwiaWF0IjoxNTg2MjQ2ODU2LCJqdGkiOiJ0ZXN0IiwidXNlcl9pZCI6MTF9.wIh1VpkmDKkGFAY5c0IMO0SC3TVwXNsl1NufNzdITUI","allowed_permissions":["TestCreateEntity","TestDeleteEntity", "TestUpdateEntity"]}
Documentation
¶
Index ¶
- Variables
- type AAA
- type ApplicationPayload
- type BasicAAA
- func (s *BasicAAA) Decode(encodedToken []byte) (vatel.Tokener, error)
- func (a *BasicAAA) Endpoints() []vatel.Endpoint
- func (a *BasicAAA) GenerateToken(u Userer) (*TokenSet, error)
- func (a *BasicAAA) Init(ctx context.Context) error
- func (a *BasicAAA) IsAllowed(requestPerms []byte, bitpos ...uint) (bool, error)
- func (a *BasicAAA) Refresh(encodedToken []byte) (*TokenSet, error)
- func (a *BasicAAA) SetExtraAssigner(f func(userID int) (map[string]interface{}, error))
- func (a *BasicAAA) SignIn(login, password string) (*TokenSet, error)
- func (a *BasicAAA) Start(ctx context.Context) error
- type Config
- type IsTokenValidController
- type RefreshController
- type RefreshToken
- type RoleStorer
- type SignInController
- type Token
- type TokenSet
- type UserStorer
- type Userer
Constants ¶
This section is empty.
Variables ¶
var ( // EPSignIn holds endpoint path to sign in. EPSignIn = "/auth/sign-in" // EPIsTokenValid holds endpoint path to is token valid. EPIsTokenValid = "/auth/is-token-valid" // EPRefreshToken holds endpoint path to refresh token. EPRefreshToken = "/auth/refresh-token" )
var DefaultConfig = Config{ AccessTokenDuration: time.Minute * 30, RefreshTokenDuration: time.Hour * 24 * 30, IsRefreshNotBeforeEnabled: false, Issuer: "", Subject: "", Audience: []string{""}, EncryptionKey: "default", }
DefaultConfig holds default JWT configuration.
Functions ¶
This section is empty.
Types ¶
type AAA ¶
type AAA interface { // SignIn предоставляет метод для аутентификации пользователя. SignIn(login, password string) (*TokenSet, error) // ForceSignIn генерирует JWT токены для пользователя. // может использоваться для принудительной аутентификации пользователя, при // переходе по ссылки из письма активации адреса email. ForceSignIn(Userer) (*TokenSet, error) // RefreshToken принимает токен в виде base64 строки, проверяет на валидность, // обновляет и возвращает новый токен. RefreshToken(encodedToken []byte) (*TokenSet, error) SetExtraAssigner(func(userID int) map[string]interface{}) }
type ApplicationPayload ¶
type ApplicationPayload struct { UserID int `json:"user"` UserLogin string `json:"login"` RoleID int `json:"role"` PermissionBitSet json.RawMessage `json:"perms,omitempty"` IsDebug bool `json:"debug,omitempty"` ExtraPayload map[string]interface{} `json:"extra,omitempty"` }
ApplicationPayload defines attributes what will be injected into JWT access token.
func (*ApplicationPayload) Extra ¶
func (t *ApplicationPayload) Extra() interface{}
func (*ApplicationPayload) Login ¶
func (t *ApplicationPayload) Login() string
func (*ApplicationPayload) Perms ¶
func (t *ApplicationPayload) Perms() []byte
func (*ApplicationPayload) Role ¶
func (t *ApplicationPayload) Role() int
func (*ApplicationPayload) User ¶
func (t *ApplicationPayload) User() int
type BasicAAA ¶
type BasicAAA struct {
// contains filtered or unexported fields
}
BasicAAA holds data required for implementation AAA interface and axkit/vatel interfaces Authorizer, TokenDecoder.
func New ¶
func New(cfg Config, u UserStorer, r RoleStorer) *BasicAAA
New returns default implementation of AAA based on JWT.
func (*BasicAAA) GenerateToken ¶
GenerateToken generates JWT token without cridentials.
func (*BasicAAA) IsAllowed ¶
IsAllowed implements interface axkit/vatel Autorizer. Method receives perms from JTW token and endpointPemrs. Return true if all endpointPerms are inside requestPerms.
func (*BasicAAA) SetExtraAssigner ¶
SetExtraAssigner receives a funcion what will be called in /sign-in and /refresh-token endpoints. Data returned by the function will be assigned to JWT payload attribute "app->extra".
type Config ¶
type Config struct { AccessTokenDuration time.Duration RefreshTokenDuration time.Duration IsRefreshNotBeforeEnabled bool Issuer string Subject string Audience []string EncryptionKey string }
Config describes JWT configuration.
type IsTokenValidController ¶
type IsTokenValidController struct {
// contains filtered or unexported fields
}
IsTokenValidController implements /is-token-valid HTTP endpoint.
func (*IsTokenValidController) Handle ¶
func (c *IsTokenValidController) Handle(ctx vatel.Context) error
Handle implements github.com/axkit/vatel Handler interface.
func (*IsTokenValidController) Result ¶
func (c *IsTokenValidController) Result() interface{}
Result implements github.com/axkit/vatel Resulter interface.
type RefreshController ¶
type RefreshController struct {
// contains filtered or unexported fields
}
RefreshController implements /refresh-token HTTP endpoint.
func (*RefreshController) Handle ¶
func (a *RefreshController) Handle(ctx vatel.Context) error
Handle implements github.com/axkit/vatel Handler interface.
func (*RefreshController) Input ¶
func (a *RefreshController) Input() interface{}
Input implements github.com/axkit/vatel Inputer interface.
func (*RefreshController) Result ¶
func (a *RefreshController) Result() interface{}
Result implements github.com/axkit/vatel Resulter interface.
type RefreshToken ¶
type RefreshToken struct { jwt.Payload UserID int `json:"user"` }
type RoleStorer ¶
type RoleStorer interface { IsRoleExist(roleID int) bool RolePermissions(roleID int) ([]string, bitset.BitSet) }
RoleStorer is an interface what wraps methods IsRoleExist and RolePermissions.
IsRoleExist returns true if role is roleID is exists.
RolePermissions returns array of permissions and BitSet permission representation.
type SignInController ¶
type SignInController struct {
// contains filtered or unexported fields
}
SignInController implements sign in HTTP endpoint.
func (*SignInController) Handle ¶
func (c *SignInController) Handle(ctx vatel.Context) error
Handle implements github.com/axkit/vatel Handler interface.
func (*SignInController) Input ¶
func (c *SignInController) Input() interface{}
Input returns reference to incoming struct.
func (*SignInController) Result ¶
func (c *SignInController) Result() interface{}
Result returns reference to sucessfull output.
type Token ¶
type Token struct { jwt.Payload App ApplicationPayload `json:"app"` }
Token implements interface axkit/vatel Tokener.
func (*Token) ApplicationPayload ¶
func (t *Token) ApplicationPayload() vatel.TokenPayloader
func (*Token) SystemPayload ¶
SystemPayload returns JWT system attributes related to standard.
type TokenSet ¶
type TokenSet struct { Access string `json:"access_token"` Refresh string `json:"refresh_token"` AllowedPermissions []string `json:"allowed_permissions"` }
TokenSet describes response on successfull sign in and refresh token requests.
type UserStorer ¶
type UserStorer interface { UserByCredentials(login, password string) (Userer, error) UserByID(userID int) (Userer, error) }
UserStorer is an interface what wraps metods UserByCridentials and UserByID.
UserByCredentials returns a user (object implementing interface Userer) if user with login and password is found.
UserByID returns a user (object implementing interface Userer) identified by userID.