Using JWT to protect API access

Source: Internet
Author: User

A common use case for APIs is to provide an authorization middleware that allows clients to send authorization requests to APIs. Typically, the client will perform some authorization logic to generate a "session ID." The more popular JWT (JSON Web Tokens) recently provides a "session ID" with a timeout that does not require extra space to execute validation logic.

This article is written in the previous article. It is recommended to look at the previous article before reading the following. Handling HTTP requests with go-chi

Next we will add a authorization layer to APIs with go-chi/jwtauth. It is based on go-chi/chi. Authorization can be arbitrary (for logins and users who are not logged in) or targeted (only for users who have logged in). In this way, different authorization logics can be implemented for the two users, and additional authorization verification information is returned according to the legality of the JWT parameters. I used titpetric/factory/resputil to simplify error handling and formatting JSON data.

What is JWT?
JSON Web Token ( JWT ) is an open standard ( RFC 7513 ) that defines how to safely transfer JSON objects between parts. The standard is concise and self-contained, and it is digitally signed, so its legitimacy can be authenticating

"JWT" consists of three parts

Header: specifies the signature algorithm used
Declaration section: It can also contain timeouts
a signature generated based on a specified algorithm
Through these three pieces of information, the API server can regenerate the signature according to the information of the "JWT" header and the declaration part. The reason why this can be done is because the key required to generate the signature is stored on the server side.

jwtauth.New("HS256", []byte("K8UeMDPyb9AwFkzS"), nil)

If the signature key is relatively simple, it is recommended to change it to a more complicated one. After the change, all the generated "JWT" will be invalidated, forcing the client to re-authorize the server.

Declaration section
Through the "JWT" declaration, you can use a format like "user_id": "1337" to identify the client that uses the API service. Think of it as a Go data structure like map[string]interface{}, plus Some proper conversions. When the client initiates an authorization request to the API service, the client ID and some other data are sent to perform the login operation. After receiving the request, the server will generate a corresponding "JWT" and save it in the database, so that the client's subsequent request does not need to request the authorization until the "JWT" times out.

The application preferably generates a debug "JWT" and outputs it to the log file. The application can be debugged with this legal "JWT".

Type JWT struct {
    tokenClaim string
    tokenAuth *jwtauth.JWTAuth
}

Func (JWT) new() *JWT {
    Jwt := &JWT{
        tokenClaim: "user_id",
        tokenAuth: jwtauth.New("HS256", []byte("K8UeMDPyb9AwFkzS"), nil),
    }
    log.Println("DEBUG JWT:", jwt.Encode("1"))
    Return jwt
}

Func (jwt *JWT) Encode(id string) string {
    Claims := jwtauth.Claims{}.
        Set(jwt.tokenClaim, id).
        SetExpiryIn(30 * time.Second).
        SetIssuedNow()
    _, tokenString, _ := jwt.tokenAuth.Encode(claims)
    Return tokenString
}

Whenever a new "JWT" object is generated by JWT{}.new(), the debug "JWT" information is output in the log.

2018/04/19 11:35:18 DEBUG JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMSJ9.ZEBtFVPPLaT1YxsNpIzVGSnM4Vo7ZrEvp77jKgfN66s
You can pass this "JWT" as a URL query parameter to test a GET request, or test a more complex API request in the test code, which can be passed using an "authorization header" or a cookie.

Note: When generating "JWT", be sure to specify the expiration time, otherwise the generated "JWT" will remain in effect until the signature key is replaced. Another option is to invalidate individual "JWTs" on the server side, which requires some code to record and wake them up. For example, instead of using the user ID, the session ID is used to identify "JWT", so that additional verification can be performed on the expiration/logout. The above example has already set the necessary parameters so that we can have the expiration time. The "JWT" is verified. If a protected API is requested and "JWT" has timed out, the server will return an error message stating that you need to re-request authorization when calling these interfaces.

Use JWT to protect API access
Each request to the API can contain a "JWT Validator". It works like CORS - detecting the existence of "JWT" from "HTTP Request Parameters", cookies, or "Authorized HTTP Headers". The "verifier" returns a context variable about "JWT" and a possible parsing error. Even if "JWT" is not found, the "verifier" will not interrupt the normal request. It just provides some information to the "authorizer".

Go-chi/jwtauth provides a default "verifier" that we can use directly. Let's add a helper method to the previous "JWT" type and return to the default "verifier".
Func (jwt *JWT) Verifier() func(http.Handler) http.Handler {
    Return jwtauth.Verifier(jwt.tokenAuth)
}


We have added this "verifier" to every request, so that even if an API does not require authorization, we can receive these IDs. Extract and process the declarations, and still return a valid response when no "JWT" is found
Login := JWT{}.new()

Mux := chi.NewRouter()
mux.Use(cors.Handler)
mux.Use(middleware.Logger)
mux.Use(login.Verifier())


Previously we used mux.Route. In order to divide the request into two parts that require authorization and no authorization, we need to use mux.Group(). Use Group() to add a new handler to the global processor so that we can omit prefixes like "/api/private/*" when requesting

Group creates a new inline Mux with an empty middleware stack. Groups are especially useful if the prefix portions of some requests are the same and some of the same middleware needs to be executed.

// Protected API endpoints
mux.Group(func(mux chi.Router) {
    // Error out on invalid/empty JWT here
    mux.Use(login.Authenticator())
    {
        mux.Get("/time", requestTime)
        mux.Route("/say", func(mux chi.Router) {
            mux.Get("/{name}", requestSay)
            mux.Get("/", requestSay)
        })
    }
})

// Public API endpoints
mux.Group(func(mux chi.Router) {
    // Print info about claim
    mux.Get("/api/info", func(w http.ResponseWriter, r *http.Request) {
        Owner := login.Decode(r)
        resputil.JSON(w, owner, errors.New("Not logged in"))
    })
})

Now /time and /say must have a valid "JWT" to access, /time does not directly check "JWT", but the inspection is handed over to the "authorizer". For example, if we access /time with an expired "JWT", we will get the following information:
{
    "error": {
        "message": "Error validating JWT: jwtauth: token is expired"
    }
}


But if we request /info, we will receive the following information:

{
    "response": "1"
}

Accessing /info with an expired "JWT" will return:

{
    "error": {
        "message": "Not logged in"
    }
}

The two requests return different information because we implemented the full validation logic in the Decode function. If "JWT" is illegal or out of date, it will return a custom error message instead of the return value in the Authenticate method used to protect /time.

Func (jwt *JWT) Decode(r *http.Request) string {
    Val, _ := jwt.Authenticate(r)
    Return val
}

Func (jwt *JWT) Authenticate(r *http.Request) (string, error) {
    Token, claims, err := jwtauth.FromContext(r.Context())
    If err != nil || token == nil {
        Return "", errors.Wrap(err, "Empty or invalid JWT")
    }
    If !token.Valid {
        Return "", errors.New("Invalid JWT")
    }
    Return claims[jwt.tokenClaim].(string), nil
}

We use the same method to let the authorization middleware use "JWT" to protect access to the private API. Errors in the Decode() method are ignored because the called method returns an empty string by default. The authorization middleware returns the complete error message:

Func (jwt *JWT) Authenticator() func(http.Handler) http.Handler {
    Return func(next http.Handler) http.Handler {
        Return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            _, err := jwt.Authenticate(r)
            If err != nil {
                resputil.JSON(w, err)
                Return
            }
            next.ServeHTTP(w, r)
        })
    }
}


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.