var express =require("express"); var session =require("express-session"); var grant =require("grant").express(); express() .use(session({ secret:"grant", saveUninitialized:true, resave:false})) .use(grant(require("./config.json"))) .get("/hello",(req, res)=>{ res.end(JSON.stringify(req.session.grant.response,null,2)); }) .listen(3005);
With DigitalOcean app platform, it is possible to deploy multiple components (such as frontend and backend) from a single code repository.
A monorepo makes it easier to maintain sets of related components that work together. It typically has a single build pipeline, code standardization can be enforced on all components together and it greatly improves collaboration by code sharing as team scales.
This blog explains how to deploy a monorepo with identity-awareness on DigitalOcean app platform.
Freeing you from implementing authentication yourself, so you can focus on things that make you unique.
api-go - Protected micro service HTTP endpoint, written in Golang.
frontend-react - SPA that performs performs authentication and consumes the /api-go endpoint, written in React.js.
components diagram
flowchart LR
subgraph App-Platform
direction LR
subgraph frontend-react
direction LR
end
subgraph api-go
direction LR
token-middleware -- valid token --> endpoint
end
end
subgraph Crossid
direction LR
end
frontend-react --redirect browser to login--> Crossid
Browser --GET /react--> frontend-react
Browser --GET /api-go--> api-go
The component /api-go demonstrates how to expose a protected HTTP endpoint, in Golang.
It response some data only if the request was sent with a valid access token.
note
Micro services should be kept simple and not handle any user authentication. it's up to a frontend (see below) to authenticate visitors and pass a valid access-token to the service.
Client sends a request, providing a valid access token.
The token middleware verifies token validity or denies the request.
If token is valid, the endpoint is invoked, returning some data.
The component /frontend-react demonstrates how to build a static SPA app, written in React.js.
The frontend-react component let user:
Let visitors see the home page, anonymously.
A protected route (/posts) that can only be seen by authenticated users. this route also consumes our api-go service.
Sign users in via Crossid.
Sign users out via Crossid.
interaction diagram
sequenceDiagram; autonumber
Browser->>+frontend-react: GET: /react/posts
Browser->>+Crossid: User not authenticated
Crossid->>Crossid: User Signin
Crossid->>Browser: Access Token
Browser->>api-go: GET /api-go
An anonymous visitor tries to access /react/posts, a protected react route.
Browser is redirected to Crossid for login.
User logs in using its credentials (OTP, TOTP, Fingerprint, etc...)
Browser is redirected back to /react/posts route with a valid access-token.
The react app consumes the /api-go services by providing a valid access-token.
DigitalOcean app platform is a PAAS that let you focus on code rather infra, Crossid is a SAAS IAM that frees you from handling identity, when combining those together, you focus on things that make you unique.
If you want to free yourself from coding authentication for your apps, or just want to servce protected files only for your users, a reverse proxy with identity awareness can be a good fit.
Such reverse proxy, like oauth2-proxy is able to authenicate users before forwarding the requests to your app.
The proxy enhances each request with headers that identifies the the authenticated user, so your app can simply reply on those headers to establish some identity context.
This post explains how to configure and run oauth2-proxy in a docker container and authenticate users by Crossid.
sequenceDiagram; autonumber
Browser->>+Oauth2-Proxy: GET: /myapp
Browser->>+Crossid: User not authenticated
Crossid->>Crossid: User Signin
Crossid->>Browser: User Session Created
Browser->>App: GET /myapp
Note right of App: forwarded-user: foo@bar.com
An anonymous visitor tries to access the app.
oauth2-proxy has no session for the visitor, so it redirects the user to Crossid for login.
Crossid asks the user to login.
oauth2-proxy creates a session for the authenticated user.
oauth2-proxy proxies the request to the app with some identity headers.
Replace <tenant> with your tenant (e.g., acme.crossid.io/....).
Replace <client_id> and <client_secret> from previous step.
With this configuration, every request to http://127.0.0.1:4180/anything will be proxied to the upstream (our app).
We simply use httpbin.org that simply echos the request info.
Tip: for a random cookie secret run python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(16)).decode())'
Navigate to http://127.0.0.1:4180/anything should redirect user to crossid for login.
Upon successful login, the request should be proxied to our app (httpbin.org).
Echo has its own JWT middleware to protect routes with JWTs.
Lets create a JWT Middleware instance:
funcauthmw() echo.MiddlewareFunc { // jwksURL is the URL of the public JSON Web Tokens that sign our tokens. jwksURL :="https://<tenant>.crossid.io/oauth2/.well-known/jwks.json" jwks, err := keyfunc.Get(jwksURL, keyfunc.Options{ RefreshErrorHandler:func(err error){ panic(fmt.Sprintf("There was an error with the jwt.KeyFunc\nError:%s\n", err.Error())) }, }) if err !=nil{ panic(fmt.Sprintf("Failed to create JWKs from resource at the given URL.\nError:%s\n", err.Error())) } // initialize JWT middleware instance return middleware.JWTWithConfig(middleware.JWTConfig{ // skip public endpoints Skipper:func(context echo.Context)bool{ return context.Path()=="/" }, KeyFunc:func(token *jwt.Token)(interface{},error){ // a hack to convert jwt -> v4 tokens t,_, err :=new(jwtv4.Parser).ParseUnverified(token.Raw, jwtv4.MapClaims{}) if err !=nil{ returnnil, err } return jwks.KeyFunc(t) }, }) }
line 3 - We must provide a valid well known JWKs endpoint where all public keys used to sign tokens are exposed.
line 26 - is the actual function that performs the access token verification against the authorization server keys.
If a self-hosted OIDC / OAuth2 provider is your thing, then most chances Hydra would be your best choice.
This post explains how to write a single page app (SPA) in js, with no backend that performs authentication via Hydra.
It uses crossid-spa-js, a javascript OAuth2 client (with PKCE) that suites for SPA apps that is able to sign users in and out with advanced features such caching and refresh token.
git clone https://github.com/crossid/crossid-spa-js-example myspa cd myspa npm install
This example is composed only from two files: src/main.js which contains the logic to initialize a client and signin and index.html which is the html of our app.
Lets edit src/main.js to init a Client that matches the params of the Hydra client we created above.
// ... import{ newCrossidClientByDiscovery,Client}from"@crossid/crossid-spa-js"; // inits a client that handles signing users in and out. exportconstinitClient=async()=>{ const client =awaitnewCrossidClientByDiscovery({ // configure a client by the oidc configuration well known endpoint wellknown_endpoint: "http://localhost:4444/.well-known/openid-configuration", client_id:"myspa", audience:["myspa.com"], scope:"openid offline", redirect_uri:"http://localhost:3009", // default cache is 'memory', enable to preserve user in cache // cache_type: "session_storage", }); return client; }; //...
Take a look at index.html that uses our login method.
But extra bits are required to perform the actual translation.
While it's possible to use dependencies such as react-intl, this post shows a much simpler and cleaner approach
that scales right, no dependencies required!
Over the last decade, the world has beeing moving to the cloud, with legacy and on-premise applications side by side.
Such hybrid architecture arises many technological complexities. One of the challenges is about how to securely exchange and provision identities between the different parties.
SCIM standardizes how identity resources such Users and Groups look like and how to exchange them between the different applications.