oauth2

package
v1.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 10, 2021 License: Apache-2.0 Imports: 21 Imported by: 5

README

OAuth2 Security

This package contains functions and types for enabling OAuth2 as a security mechanism in a microservice. It integrates with Goa generated OAuth2 security and also as part of the SecurityChain.

Setting up a OAuth2 security

There are a couple of things you need to do to enable the OAuth2 security middleware. For details on OAuth2 you can find many resources on the official site: https://oauth.net.

Setting up the secret keys

Create a directory in which you'll keep your key-pair:

mkdir rsa-keys
cd rsa-keys

The generate 2048-bit RSA key pair:

openssl genrsa -des3 -out private.pem 2048

The output the keys in PEM form:

openssl rsa -in private.pem -outform PEM -pubout -out public.pub

Remove passphrase

openssl rsa -in private.pem -out withoutPassphrase.pem

NOTE: Make sure that the public key ends in .pub. This is how the default key resolver of the library locates the public keys.

Set up OAuth2 with Goagen

Create a security file app/security.go with the following content:

package app

import (
	"github.com/keitaroinc/goa"
)

// NewOAuth2Security creates a OAuth2 security definition.
func NewOAuth2Security() *goa.OAuth2Security {
	def := goa.OAuth2Security{
		Flow:             "accessCode",
		TokenURL:         "http://localhost:8080/oauth2/token",
		AuthorizationURL: "http://localhost:8080/oauth2/authorize",
		Scopes: map[string]string{
			"api:read":  "no description",
			"api:write": "no description",
		}}
	return &def
}

More details on how to configure the OAuth2 security are available on the official documentation:

Setting up a SecurityChain

Once the security specs are generated by Goa, you need to set up a security chain for the microservice.

In the main.go file of your microservice, set up the JWT Security Chain middleware and add it to the security chain.


import (
	"github.com/Microkubes/microservice-security/oauth2"
	"github.com/Microkubes/microservice-security/chain"
)

func main() {
	// Create new OAuth2 security chain
  	// "rsa-keys" is the directory containing the RSA keys
  	// app.NewOAuth2Security() creates the OAuth2Security scheme structure
	OAUTH2Middleware := oauth2.NewOAuth2Security("rsa-keys", app.NewOAuth2Security())
	sc := chain.NewSecurityChain().AddMiddleware(OAUTH2Middleware)

    // other initializations here...

    service.Use(chain.AsGoaMiddleware(sc)) // attach the security chain as Goa middleware
}

Testing the setup

To test the setup, you'll need to generate and sign a JWT token, then use it in the request header.

To generate a JWT, you can use different tools. In this example we'll use jwtgen command line tool. To install it:

npm install -g jwtgen

Then, to generate the JWT token, type:

jwtgen -a RS256\
       -p rsa-keys/withoutPassphrase.pem\
       -c "userId=599316bbf456208abcbcc186" \
       -c "username=test-user"\
       -c "roles=user,admin"\
       -c "scopes=api:read api:write"\
       -c "organizations=Org1,Org2"

which will output the JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1MDM0NzQxOTcsInVzZXJJZCI6IjU5OTMxNmJiZjQ
1NjIwOGFiY2JjYzE4NiIsInVzZXJuYW1lIjoidGVzdC11c2VyIiwicm9sZXMiOiJ1c2VyLGFkbWluIiwic2NvcGV
zIjoiYXBpOnJlYWQgYXBpOndyaXRlIiwib3JnYW5pemF0aW9ucyI6Ik9yZzEsT3JnMiJ9.WUILgtGMgILtlUSrP9
MHbNfiD1-q4mIvw-UbOqjrg4HnMLvTyXMD7Td4H4yeSjThJZxEzglUf3JdMu8_8Xf4CRnAPLqeiDOSia8SWRQeRG
majagiOkRPWYp0Ns7zlsguSXuPJ64RN2eik7ROV43qtnwahHGTfyjducSNMyK9OqzacciZE2G1WDlMlbSb2p1fZZ
oXr7je6r64qfW2g1HWjG8ojXmmqbxZO5RURUgly9fUOkPBq-y5E2yq3mZ1FdPoqi7QQ9ZeUnRv4-m0q8kXVqY3P9
4uc3HYRXxd35jBQP0JfU_aVzr_eL7b16QbyT7O_dTsbUOcOvcm4Hh54Gu4Mg

(Note that the JWT is actually one line. For readability purposes it is displayed here in multiple lines.)

Then you'll need to add the token in the Authorization HTTP header of the request.

curl --header "Authorization: Bearer eyJ0eXAiOiJKV1Q...<full token here>...4pfw" "http://localhost:8080/profiles/me"

Documentation

Index

Constants

View Source
const OAuth2SecurityType = "OAuth2"

OAuth2SecurityType is the name of the security type (JWT, OAUTH2, SAML...)

Variables

View Source
var InternalServerError = goa.NewErrorClass("server_error", 500)

InternalServerError is a generic server error

View Source
var OAuth2AccessDenied = goa.NewErrorClass("access_denied", 403)

OAuth2AccessDenied is an access denied error for created auth

View Source
var OAuth2ErrorInvalidRedirectURI = goa.NewErrorClass("invalid_request", 400)

OAuth2ErrorInvalidRedirectURI is Bad Request error for invalid redirect URI

View Source
var OAuth2ErrorInvalidScope = goa.NewErrorClass("invalid_scope", 400)

OAuth2ErrorInvalidScope is Bad Request error for invalid scope requested

View Source
var OAuth2ErrorUnauthorizedClient = goa.NewErrorClass("unauthorized_client", 401)

OAuth2ErrorUnauthorizedClient is an error for bad client credentials

Functions

func CompareRedirectURI

func CompareRedirectURI(registered, provided string) error

CompareRedirectURI compares the registered redirect URI with a provided one.

func GenerateRandomCode

func GenerateRandomCode(n int) (string, error)

GenerateRandomCode generates a cryptographically strong random string with the specified length.

func NewOAuth2Security

func NewOAuth2Security(keysDir string, scheme *goa.OAuth2Security) chain.SecurityChainMiddleware

NewOAuth2Security creates a OAuth2 SecurityChainMiddleware using a simple key resolver that loads the public keys from the keysDir. The key files must end in *.pub. The scheme is obtained from app/security.go.

func NewOAuth2SecurityMiddleware

func NewOAuth2SecurityMiddleware(resolver goaJwt.KeyResolver, scheme *goa.OAuth2Security) goa.Middleware

NewOAuth2SecurityMiddleware creates a middleware that checks for the presence of an authorization header and validates its content. The steps taken by the middleware are: 1. Validate the "Bearer" token present in the "Authorization" header against the key(s) 2. If scopes are defined for the action validate them against the "scopes" JWT claim

Types

type AuthProvider

type AuthProvider struct {
	ClientService
	UserService
	TokenService
	tools.KeyStore
	SigningMethod             string
	AuthCodeLength            int
	RefreshTokenLength        int
	AccessTokenValidityPeriod int
	ProviderName              string
}

AuthProvider holds the data for implementing the oauth2.Provider interface.

func (*AuthProvider) Authenticate

func (provider *AuthProvider) Authenticate(clientID, clientSecret string) error

Authenticate checks the client credentials.

func (*AuthProvider) Authorize

func (provider *AuthProvider) Authorize(clientID, scope, redirectURI string) (code string, err error)

Authorize performs the authorization of a client and generates basic ClientAuth.

func (*AuthProvider) Exchange

func (provider *AuthProvider) Exchange(clientID, code, redirectURI string) (refreshToken, accessToken string, expiresIn int, err error)

Exchange exchanges the confimed ClientAuth for an access token and refresh token.

func (*AuthProvider) Refresh

func (provider *AuthProvider) Refresh(refreshToken, scope string) (newRefreshToken, accessToken string, expiresIn int, err error)

Refresh exchnages a refresh token for a new access token.

type AuthToken

type AuthToken struct {
	// AccessToken is the actual value of the access token.
	AccessToken string `json:"accessToken, omitempty" bson:"accessToken"`

	// RefreshToken  holds the refresh token value.
	RefreshToken string `json:"refreshToken, omitempty" bson:"refreshToken"`

	// Unix timestamp of the time when the access token was issued.
	IssuedAt int64 `json:"issuedAt, omitempty" bson:"issuedAt"`

	// ValidFor is the time duration for which this token is valid. Expressed in milliseconds.
	ValidFor int `json:"validFor, omitempty" bson:"validFor"`

	// Scope is the scope for which this access token is valid.
	Scope string `json:"scope, omitempty" bson:"scope"`

	// ClientID is the reference to the client for which this token has been issued.
	ClientID string `json:"clientId, omitempty" bson:"clientId"`

	// UserID is the reference to the user for which this token has been issued.
	UserID string `json:"userId, omitempty" bson:"userId"`
}

AuthToken holds the data for oauth2 token.

type Client

type Client struct {
	ClientID    string `json:"clientId, omitempty"`
	Name        string `json:"name, omitempty"`
	Description string `json:"description, omitempty"`
	Website     string `json:"domain, omitempty"`
	Secret      string `json:"secret, omitempty"`
}

Client holds the data for a specific client (app). A client must firt be registered for access on the platform.

type ClientAuth

type ClientAuth struct {
	ClientID    string `json:"clientId, omitempty" bson:"clientId"`
	UserID      string `json:"userId, omitempty" bson:"userId"`
	Scope       string `json:"scope, omitempty" bson:"scope"`
	Code        string `json:"code, omitempty" bson:"code"`
	GeneratedAt int64  `json:"generatedAt, omitempty" bson:"generatedAt"`
	UserData    string `json:"userData, omitempty" bson:"userData"`
	RedirectURI string `json:"redirectUri, omitempty" bson:"redirectUri"`
	Confirmed   bool   `json:"confirmed, omitempty" bson:"confirmed"`
}

ClientAuth is an authorization record for a specific client (app) and user. It holds the data for a specific client that is (or needs to be) authorized by a user to access some part of the platform.

type ClientService

type ClientService interface {

	// GetClient retrieves a Client by its ID.
	GetClient(clientID string) (*Client, error)

	// VerifyClientCredentials verfies that there is a registered Client with the specified client ID and client secret.
	// It returns the actual Client data if the credentials are valid, or nil if there is no such client.
	VerifyClientCredentials(clientID, clientSecret string) (*Client, error)

	// SaveClientAuth stores a ClientAuth.
	SaveClientAuth(clientAuth *ClientAuth) error

	// GetClientAuth retrieves a ClientAuth for the specified client ID and a generated random code for verification.
	GetClientAuth(clientID, code string) (*ClientAuth, error)

	// GetClientAuthForUser retrieves a ClientAuth for a Client and User.
	// Used when is situations where the access code is still not generated.
	GetClientAuthForUser(userID, clientID string) (*ClientAuth, error)

	// ConfirmClientAuth updates the Confirmed field (sets it to true).
	// Used to update the client auth once the user has accepted the client to access the data.
	ConfirmClientAuth(userID, clientID string) (*ClientAuth, error)

	// UpdateUserData updates the ClientAuth with the full user data.
	// This is techincally a workaround since the goa-oauth2 Provider does not take
	// into account the user in the access_grant flow.
	UpdateUserData(clientID, code, userID, userData string) error

	// DeleteClientAuth deletes the ClientAuth.
	// If you never call this, the ClientAuth should expire automatically after a certain period.
	DeleteClientAuth(clientID, code string) error
}

ClientService is an interface that defines the access to a Client and ClientAuth.

type TokenService

type TokenService interface {

	// SaveToken saves the token data to the backend.
	SaveToken(token AuthToken) error

	// GetToken retrieves the OAuth2Token for a refreshToken.
	GetToken(refreshToken string) (*AuthToken, error)

	// GetTokenForClient looks up an OAuth2Token for a specific client and user.
	// There should be only one such token.
	GetTokenForClient(userID, clientID string) (*AuthToken, error)
}

TokenService defines the interface for managing OAuth2 Tokens.

type User

type User struct {
	ID            string   `json:"id, omitempty"`
	Username      string   `json:"username, omitempty"`
	Email         string   `json:"email, omitempty"`
	Roles         []string `json:"roles, omitempty"`
	Organizations []string `json:"organizations, omitempty"`
	Namespaces    []string `json:"namespaces, omitempty"`
	ExternalID    string   `json:"externalId, omitempty"`
	Active        bool     `json:"active, omitempty"`
}

User holds the user data.

type UserService

type UserService interface {
	// VerifyUser verifies the credentials (username and password) and retrieves a
	// User if the credentials are valid.
	VerifyUser(username, password string) (*User, error)
}

UserService defines an interface for verification of the user credentials. This is used in the access_grant flow, to login the user and then prompt it for confirmation about authorizing the client to access the services on the platform.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL