Go


Table of Contents:

Prerequisites
Go SDK Installation
Run the Go Demo App
Creating your own app


Prerequisites


Creating an app involves two basic steps:

  1. Register/create an app in the portal and obtain credentials (this is detailed in the 'Register / Create a New App' menu section)
  2. Configure your app using an SDK, making use of these credentials

It is recommended that you first create a demo/test app with a redirect uri of http://127.0.0.1:8002/oidc in the portal, to enable you to run the SDK demo app and test the SDK functionality.

In the portal, when you create a new app, you will be issued with the client ID and client secret credentials that you need to specify when building your app with the SDK. These are found in the app settings screen:

app settings page

Note that you can control the Login Methods available (QR Code requires customer usage of the mobile app. While Browser Login enables logging in within the desktop browser, without the mobile app)

When creating your app in the portal, you also must specify a redirect_uri endpoint which comes from the url where you are hosting your app:

add app

The SDK will automatically create the redirect uri by appending '/oidc' to your url. You, of course, are in charge of what the base url of your app is. So it could be http://testapp.com/oidc, http://12.34.567.89:8080/oidc or http://127.0.0.1:8002/oidc as above. Note, however, that the authentication server will not accept 'localhost' as a base url.


Go SDK Installation


  1. The only dependency for this SDK is:

    If you are new to Go, it is advised that you follow the "Hello World" tutorial to make sure your 'go path' is set up correctly.

  2. To grab the SDK, first of all create a suitable subfolder within your Go projects folder, for example:

    cd goprojects
    mkdir -p src/github.com/miracl/

    Then:cd src/github.com/miracl/
    Then download or clone the SDK from: https://github.com/miracl/maas-sdk-go.git
    Or: go get github.com/miracl/maas-sdk-go


Run the Go demo app


Running the pre-configured demo app will help you make sure that you have all your settings correct and so will be able to proceed with confidence toward developing your own application:

  1. From the root directory, you now need to go to the directory containing the demo app:

    cd maas-sdk-go/example

    Here you will find two key files:

    1. example.go
    2. templates/index.tmpl
  2. Open the example.go file and configure with your client id, client secret and redirect_uri values, as set up in the Create a New App menu section:

    For security, the client ID and secret for a genuine app should not be stored in clear text in a script. This has only been done here for simple demo purposes. For a production scenario, the client ID and secret should be programmatically accessed via an encrypted API

    clientID     = flag.String("client-id", "4t****wcuf", "OIDC Client Id")
    clientSecret = flag.String("client-secret", "ergdre*************444dd1", "OIDC Client Secret")
    redirectURL  = flag.String("redirect", "http://127.0.0.1:8002/oidc", "Redirect URL")
    addr         = flag.String("addr", ":8002", "Listen address")
    
    mc maas.Client
    )

    Note that the port (8002) of the Listen Address means the script will work with an app that has been set up in the portal with a redirect uri of http://127.0.0.1:8002/oidc. You can of course change the listen address, but it must match the redirect uri as entered in the portal.

  3. Open the templates/index.tmpl file and, towards the end of the file, make sure the mpad script has the correct url in order to communicate with the authentication server (note that it begins with 'mcl.cdn.mpin.io'):

    <script src="https://mcl.cdn.mpin.io/mpad/mpad.js" data-authurl="{{ .AuthURL }}" data-element="btmpin"></script>
  4. Now run the demo app:

    go run example.go

    Your app should now be running on http://127.0.0.1:8002/

    image1

    From here you will be prompted to:

    1. Register
    2. Key in your identity
    3. Confirm your identity (by email activation)
    4. Create a 4-digit PIN
    5. Login

Creating your own app


What follows is a detailed breakdown of the structure and code used in creating the sample app, which will give you a broad understanding of how to begin creating your own app. Note that the snippets of code which follow can be put to use, and you can also find the full files (complete with commented code) in the example folder.

The key files are:

  • The templates/index.tmpl file, to create a main web page with the login button
  • The example.go script which contains the code for dealing with the Miracl Client object and communicating with the platform

The instructions to follow will guide you on how to create your own app which gives complete management of the authorization flow.

1.Create the main web page and the login button

Note that the use of col-md divs here assumes use of the bootstrap framework, as does the use of the {{ }} tags to receive flash messages in the standard bootstrap categories of 'success', 'info' and 'danger'.

In the templates/index.tmpl file, you will see that a javascript is used which can enable the prerollid functionality, which enables capturing the user's email address to bake it into the QR code. Ultimately, it will then be pre-populated in the user's phone app, saving them the trouble of entering their email address twice and enhancing the user experience:

<script type="application/javascript">
    $(document).ready(function(){
        $("input[type=email]").keyup(function(){
            var email = $("input[type=email]").val();
            $("#btmpin").attr("data-prerollid", email);
        });
    });
</script>

A login block is created for this which can then be used at any stage:

{{define "login_block"}}
    <div class="col-md-12">
        <div class="col-md-8" id="example-email">
            <div class="form-group">
                <label for="example-email-input" class="col-xs-2 col-form-label">Log In As:</label>
                <div class="col-xs-10">
                    <input class="form-control" type="email" id="example-email-input" placeholder="email address">
                </div>
            </div>
        </div>
        <div id="btmpin"></div>
    </div>
{{end}}

Then listen for messages coming from your main script:

<div class="row">
    <div class="col-md-12">
        {{ range $flash := .Messages }}
            <div class="alert alert-{{ $flash.Category }}">
              {{ $flash.Message }}
            </div>
        {{ end }}
    </div>
</div>

Then run a check for the retry message coming from your main script. The retry message means that an attempted login has failed (as will be seen later) and your script will have provided an authorization url. In this case a login block is created to contain the login button. The next if .Authorized check is to listen for the Authorized message, in which case display the user email and id, plus the refresh and logout buttons which initiate a redirect to the "/refresh" and "/logout" endpoints:

{{ if and (.Retry) (not (eq .AuthURL "")) }}
    {{template "login_block" .}}
{{ end }}
{{ if not .Retry }}
    <div class="row">
        {{ if .Authorized }}
            <div class="col-md-4">
                <b>E-mail:</b> {{ .Email }}<br/>
                <b>User ID:</b> {{ .UserID }}<br/>
            </div>
            <div class="col-md-4"></div>
            <div class="col-md-4">
                <a href="/refresh" class="btn btn-primary action">Refresh user data</a>
                <a href="/logout" class="btn btn-primary action">Log out</a>
            </div>
        {{ else }}
            {{ if not (eq .AuthURL "") }}
                {{template "login_block" .}}
            {{ end }}
        {{ end }}
    </div>
{{ end }}

The last else section of the above snippet then uses the login block to create the login button.

Towards the end of the file, the script for the login button is added, which makes use of the mpad.js library to send the authentication information to the server (note that it begins with 'mcl.cdn.mpin.io'):

<script src="https://mcl.cdn.mpin.io/mpad/mpad.js" data-authurl="{{ .AuthURL }}" data-element="btmpin"></script>

The parameters passed in this script are:

  • data-element: the login button ID (corresponds with <div id="btmpin">)
  • data-authurl: the authorization URL (this passes the client_id and redirect_uri to the authentication server). Each SDK has a 'Get Authorization Request URL' method for obtaining this.

3. Create a main server script

In the sample app this is the example.go file. The imports are:

import (
    "flag"
    "fmt"
    "html/template"
    "log"
    "net/http"
    "path/filepath"
    "time"

    "github.com/miracl/maas-sdk-go"
)

Then the appropriate variables are set for the app, including the correct credentials:

For security, the client ID and secret for a genuine app should not be stored in clear text in a script. This has only been done here for simple demo purposes. For a production scenario, the client ID and secret should be programmatically accessed via an encrypted API

var (
    clientID     = flag.String("client-id", "serher3asd4wef2", "OIDC Client Id")
    clientSecret = flag.String("client-secret", "_TDwesdgowmfmfYEU78fsdfovod9_7dfsdf", "OIDC Client Secret")
    redirectURL  = flag.String("redirect", "http://127.0.0.1:8002/oidc", "Redirect URL")
    addr         = flag.String("addr", ":8002", "Listen address")
    templatesDir = flag.String("templates-dir", "templates", "Template files location")
    debug        = flag.Bool("debug", false, "Debug mode")

  backend = flag.String("backend", maas.DiscoveryURI, "Backend url")

    mc maas.Client
)

Substitute the correct values for your app, as discussed in 'Prerequisites' above. Also make sure that your addr and redirectURL work together.

At this point, in the sample app, a very simple web session is setup with func checkSession(), func createSession and func deleteSession(). A real application would implement a session in a more advanced manner, or use whatever is provided by the framework in use.

Then it is necessary to set up flash messaging to be sent to templates/index.tmpl:

type flash struct {
    Category string
    Message  string
}

And a struct is set up to enable sending of the flash messages and other variables to control the authorization flow:

type context struct {
    Messages   []flash
    Retry      bool
    AuthURL    string
    Authorized bool
    Email      string
    UserID     string
}

The first task in the main() func is then to create a Miracl Client with your app credentials:

if *clientID == "" {
  log.Fatal("client-id required")
}
if *clientSecret == "" {
  log.Fatal("client-secret required")
}
if *redirectURL == "" {
  log.Fatal("Redirect URL required")
}

mc, err := maas.NewClient(maas.Config{
  ClientID:     *clientID,
  ClientSecret: *clientSecret,
  RedirectURI:  *redirectURL,
})
if err != nil {
  log.Fatal(err)
}

The session is then set up to accept user information as a String:

sessions := map[string]maas.UserInfo{}

A Callback route handler is then initialized at the "/oidc" endpoint. Here a user's login attempt is used to try and obtain an accessToken. If the authorization code is invalid a redirect to the "/" index page occurs. If successful, the user info can be retrieved from the server:

http.HandleFunc("/oidc", func(w http.ResponseWriter, r *http.Request) {
        ctx := context{}
        ctx.Messages = make([]flash, 10)

        code := r.URL.Query().Get("code")
        // CExchange authorization code for access token
        accessToken, jwt, err := mc.ValidateAuth(code)
        if err != nil {
            // if authorization code is invalid, redirect to index
            log.Printf("Invalid authentication code: %v\n", code)
            log.Println(err)
            http.Redirect(w, r, "/", 302)
            return
        }
        if *debug {
            claims, _ := jwt.Claims()
            log.Printf("Access token: %v", accessToken)
            log.Printf("JTW payload: %+v", claims)
        }

        // Retrieve use info from oidc server
        user, err := mc.GetUserInfo(accessToken)
        if err != nil {
            ctx.Messages = append(ctx.Messages, flash{Category: "error", Message: err.Error()})
            log.Println(err)
        } else {
            // If user info is successfully retrieved, create session and
            // redirect to `protected` page
            createSession(w, user, sessions)
            http.Redirect(w, r, "/", 302)
            return
        }

        // Else show the login page, along with any error messages
        if t, err := template.New("index.tmpl").ParseFiles(filepath.Join(*templatesDir, "index.tmpl")); err != nil {
            log.Fatalf("Failed to parse template: %+v", err)
        } else {
            t.Execute(w, ctx)
        }
    })

If debug mode has been set to True, if *debug enables retrieval of access token and claims details.

Then the accessToken can be used, with the .GetUserInfo method, to retrieve the user details, enable a logged in session and redirect to the main "/" protected page.

The logout handler functionality is then made available at the "/logout" endpoint:

http.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
  // On logout delete session and redirect to index
  deleteSession(r, w, sessions)
  http.Redirect(w, r, "/", 302)
})

Finally the login handler functionality is made available at the "/" endpoint:

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

  ctx := context{}
  ctx.Messages = make([]flash, 0)

  if user, err := checkSession(r, sessions); err != nil {
    // If user is not logged, populate authURL for mpad
    // so the user can authenticate
    authURL, e := mc.GetAuthRequestURL("test-state")
    if e != nil {
      ctx.Messages = append(ctx.Messages, flash{Category: "error", Message: e.Error()})
    }
    ctx.AuthURL = authURL
  } else {
    // Else display info for logged user (this is `protected` page)
    ctx.Authorized = true
    ctx.UserID = user.UserID
    ctx.Email = user.Email
  }

  if t, err := template.New("index.tmpl").ParseFiles(filepath.Join(*templatesDir, "index.tmpl")); err != nil {
    log.Fatalf("Failed to parse template: %+v", err)
  } else {
    if err = t.Execute(w, ctx); err != nil {
      log.Println(err)
    }
  }
})

log.Printf("Service %s started. Listening on %s", serviceName, *addr)
if err := http.ListenAndServe(*addr, nil); err != nil {
  log.Fatal(err)
}

Note that the service is set to run on the addr port as defined in the service variables at the beginning.

If the user is not logged in, GetAuthRequestURL is used to send the authURL so index.tmpl and mpad.js can create the login button.

If they are logged in, the Authorized message is sent to index.tmpl and ctx.UserID and ctx.Email are used to retrieve and display the user's ID and email.

Important Note on User Management

In terms of managing your users, it is important to note that, in the process of providing a secure login solution, the service has also registered your users with a confirmed user email and user ID. Once a user has been authenticated it is possible to make use of the above ctx.UserID and ctx.Email methods to return string values.

This removes a considerable amount of pain from the process of managing users and databases!

For example, if you have a SQL user database and you want to make a check to see if a user is present, or needs added as a new user, then it is possible to make use of the above methods.

Another example would be if you want to present a web form to capture more information that is needed to provide a user with access to your product features. Here you can use the above methods to prepopulate the ID and email address field, and you do not need to initiate a 'verify by email' process, as this has already been done by the service.

Top