Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions GolangBackendOAuth2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ion-api-credentials.json
src/infor.com/sample/sample
73 changes: 38 additions & 35 deletions GolangBackendOAuth2/README.md
Original file line number Diff line number Diff line change
@@ -1,57 +1,60 @@
Go applications
-------------------

Golang provides package golang.org/x/oauth2 to implement the OAuth2.0 protocol.

**Make OAuth 2.0 configuration**
First step is to define a configuration. Reference to downloaded credentials properties will be used in all code examples.

conf := &oauth2.Config{
ClientID: <Application ClientID>,
ClientSecret: <Application Client-Secret>,
Scopes: []string{
"openid profile",
},
Endpoint: oauth2.Endpoint{
AuthURL: <pu> + <oa>,
TokenURL: <pu> + <ot>,
},
}
This sample application demonstrates how to authenticate with the Infor ION API using OAuth2 in a Go backend application. It uses the `golang.org/x/oauth2` package to handle the OAuth2 flow.

**Obtain tokens**
Now it is ready to obtain tokens. Token struct in Go contains both access and refresh tokens.
**Configuration**

tok, err := conf.PasswordCredentialsToken(oauth2.NoContext, <Service Account AccessKey>, <Service Account SecretKey>)
if err != nil {
// handle error
}
All credentials and endpoint information are stored in the `ion-api-credentials.json` file. Before running the application, you must populate this file with the correct values for your environment.

**Create HTTP client and make a request**
OAuth 2.0 configuration struct has also a method to create HTTP client for you.
```json
{
"ci": "<Application ClientID>",
"cs": "<Application Client-Secret>",
"pu": "<API Gateway's base URI for OAuth2 endpoints>",
"oa": "<OAuth2 authorization endpoint>",
"ot": "<OAuth2 token endpoint>",
"or": "<OAuth2 token revocation endpoint>",
"saak": "<Service Account AccessKey>",
"sask": "<Service Account SecretKey>",
"iu": "<API Gateway's base URI for API endpoints>",
"ti": "<Tenant Id>"
}
```

client := conf.Client(oauth2.NoContext, tok)

resp, err := client.Get(<Request URL>)
if err != nil {
// handle error
}
The application loads these credentials at startup.

_Note:_ you do not need to refresh token manually, client cares about and will do it [automatically.](https://godoc.org/golang.org/x/oauth2#Config.Client)
**Obtain tokens**

**Revoke tokens**
The package does not provide methods to revoke any token. You can do it, calling revoke service directly.
The application uses the `PasswordCredentialsToken` function to exchange the service account credentials for an access token and a refresh token.

resp, err := http.Get(<pu> + <or> + "?token=" + tok.AccessToken)
tok, err := conf.PasswordCredentialsToken(oauth2.NoContext, creds.Saak, creds.Sask)
if err != nil {
// handle error
}

**Create HTTP client and make a request**

Once the tokens are obtained, an HTTP client is created to make authenticated API requests. The `oauth2` package automatically handles adding the `Authorization` header with the access token to each request.

client := conf.Client(oauth2.NoContext, tok)

resp, err := client.Get(<Request URL>)
if err != nil {
// handle error
}

_Note:_ The client will automatically use the refresh token to obtain a new access token if the original one has expired.

**Revoke tokens**

When the application no longer needs to make API calls, it's important to revoke the refresh token to invalidate the session. This is a security best practice.

if tok.RefreshToken != "" {
err = revokeToken(tok.RefreshToken, "refresh_token", revokeURL, creds.Ci, creds.Cs)
if err != nil {
log.Println("Error revoking refresh token:", err)
}
}


The `revokeToken` function sends a POST request to the token revocation endpoint to invalidate the refresh token.
12 changes: 12 additions & 0 deletions GolangBackendOAuth2/ion-api-credentials.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"ci": "<Application ClientID>",
"cs": "<Application Client-Secret>",
"pu": "<API Gateway's base URI for OAuth2 endpoints>",
"oa": "<OAuth2 authorization endpoint>",
"ot": "<OAuth2 token endpoint>",
"or": "<OAuth2 token revocation endpoint>",
"saak": "<Service Account AccessKey>",
"sask": "<Service Account SecretKey>",
"iu": "<API Gateway's base URI for API endpoints>",
"ti": "<Tenant Id>"
}
117 changes: 102 additions & 15 deletions GolangBackendOAuth2/src/infor.com/sample/BackendOAuth2.go
Original file line number Diff line number Diff line change
@@ -1,59 +1,146 @@
package main

import (
"encoding/json"
"golang.org/x/oauth2"
"io/ioutil"
"log"
"net/http"
"io/ioutil"
"os"
"path"
"strings"
"fmt"
"net/url"
"context"
"flag"
)

const mingleEndpoint string = "https://mingleinteg01-ionapi.mingledev.infor.com/AWSION_DEV/Mingle/"
const mingleSocial string = mingleEndpoint + "SocialService.SVC"
// IonApiCredentials matches the structure of the JSON credentials file
type IonApiCredentials struct {
Ci string `json:"ci"`
Cs string `json:"cs"`
Pu string `json:"pu"`
Oa string `json:"oa"`
Ot string `json:"ot"`
Or string `json:"or"`
Saak string `json:"saak"`
Sask string `json:"sask"`
Iu string `json:"iu"`
Ti string `json:"ti"`
}

// loadCredentials reads and parses the credentials file
func loadCredentials(file string) (IonApiCredentials, error) {
var creds IonApiCredentials
jsonFile, err := os.Open(file)
if err != nil {
return creds, err
}
defer jsonFile.Close()

byteValue, err := ioutil.ReadAll(jsonFile)
if err != nil {
return creds, err
}
if err := json.Unmarshal(byteValue, &creds); err != nil {
return creds, err
}
return creds, nil
}

func main() {
credentialsPath := flag.String("creds", "ion-api-credentials.json.example", "Path to the credentials file")
flag.Parse()

println("Loading credentials...")
creds, err := loadCredentials(*credentialsPath)
if err != nil {
log.Fatalf("Failed to load credentials from %s: %v", *credentialsPath, err)
}

println("Getting OAuth 2.0 token...")
conf := &oauth2.Config{
ClientID: "AWSION_DEV~k0UIGQzDF0g0OCK37MGd_Dt-3WKELV1d1rP-OKv1DWc",
ClientSecret: "A_9Sz4zwZzGkYCFkUKr9IjHT_87gi4fsmipW1-_77f5jLyaXT800GvKFFLVs40jvTm-rY95ZycahP0WeT_pD2Q",
ClientID: creds.Ci,
ClientSecret: creds.Cs,
Scopes: []string{""},
Endpoint: oauth2.Endpoint{
AuthURL: "https://mingleinteg01-sso.mingledev.infor.com/AWSION_DEV/as/authorization.oauth2",
TokenURL: "https://mingleinteg01-sso.mingledev.infor.com/AWSION_DEV/as/token.oauth2",
AuthURL: path.Join(creds.Pu, creds.Oa),
TokenURL: path.Join(creds.Pu, creds.Ot),
},
}

tok, err := conf.PasswordCredentialsToken(oauth2.NoContext,
"AWSION_DEV#Bhqfl0x3ec9AWOx5_JBY96KD9fcVbQKREMe83ozDAxc3qrgXLk49eeFSMUSOND6AN2D8b6MqmltN4VbAfW24Nw",
"f7pFGxW-pXRC9R1DpZM0MM33ZiNxDgqRUesCbemorUO0hKlatzJqkrZOrlQgLtjen-S1RYhfXOwfXvY9etfHyg")
tok, err := conf.PasswordCredentialsToken(context.Background(), creds.Saak, creds.Sask)
if err != nil {
log.Fatal(err)
}
println("Received token: ", tok.TokenType, tok.AccessToken)

println("Making Ming.le API test query...")
req, err := http.NewRequest("GET", mingleSocial+"/User/Detail/", nil)
println("Making API test query...")
apiUrl := path.Join(creds.Iu, creds.Ti, "/M3/m3api-rest/execute/MMS200MI/GetServerTime")
req, err := http.NewRequest("GET", apiUrl, nil)
if err != nil {
log.Fatal(err)
}
req.Header.Add("Accept", "application/json")

clientMingle := conf.Client(oauth2.NoContext, tok)
respMingle, err := clientMingle.Do(req)
client := conf.Client(context.Background(), tok)
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
} else {
data, err := readAllData(respMingle)
data, err := readAllData(resp)
if err != nil {
log.Fatal(err)
}
println("Test response: ", len(string(data)))
}

if tok.RefreshToken != "" {
println("Revoking refresh token...")
revokeURL := path.Join(creds.Pu, creds.Or)
err = revokeToken(tok.RefreshToken, "refresh_token", revokeURL, creds.Ci, creds.Cs)
if err != nil {
log.Println("Error revoking refresh token:", err)
}
}

println("Done.")
}

func readAllData(resp *http.Response) ([]byte, error) {
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}

func revokeToken(token string, tokenType string, revokeURL string, clientID string, clientSecret string) error {
client := &http.Client{}
data := url.Values{}
data.Set("token", token)
data.Set("token_type_hint", tokenType)

req, err := http.NewRequest("POST", revokeURL, strings.NewReader(data.Encode()))
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(clientID, clientSecret)

resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("Failed to read response body while revoking token. Status: %s", resp.Status)
return fmt.Errorf("failed to revoke token: %s", resp.Status)
}
log.Printf("Failed to revoke token. Status: %s, Body: %s", resp.Status, string(body))
return fmt.Errorf("failed to revoke token: %s", resp.Status)
}

println("Successfully revoked token:", tokenType)
return nil
}
5 changes: 5 additions & 0 deletions GolangBackendOAuth2/src/infor.com/sample/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module infor.com/sample

go 1.24.3

require golang.org/x/oauth2 v0.34.0
2 changes: 2 additions & 0 deletions GolangBackendOAuth2/src/infor.com/sample/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=