Authentication¶
General¶
There are two ways of logging into the Go application:
- Via Adgangsplatformen
- Via Unilogin
Go session¶
The Go session is maintained by using the iron-session tool. The session data is stored in a cookie encrypted and is only readable server side. The session architecture was decided upon as a part of developing the Unilogin login flow which is documented in an ADR in the docs/architecture section. The go session cookie is used for patrons logging in with either Unilogin or Adgangsplatformen and has two major attributes:
isLoggedIn - can be either true
or false
and
type - can be either:
anonymous
unilogin
adgangsplatformen
The overall authorization behavior of the Go application is controlled by these parameters.
Go session type cookie¶
Because we need a different behavior of the application depending of the session type ("unilogin" or "adgangsplatformen") and we don't want to call our session endpoint every time we decided to create a cookie called "go-session:type". Since it is not sensitive data we can make it accessible both client and server side. The cookie is for instance used to decide whether we need to contacting our own Pubhub API or the Publizon adapter when requesting Publizon data.
Login¶
Login via Unilogin¶
The login flow is mainly controlled via the openid-client package. It is a tool to ease the setup of the Oauth 2 flows. The decision behind the choice of tools for the login handling is described in an ADR in the docs/architecture section.
sequenceDiagram
actor Patron
participant Go
participant Unilogin Login
participant Unilogin WS
participant Adgangsplatformen
Patron->>Go: Patron opens the Login sheet
Patron->>Go: Clicks Unilogin login button
Go->>Go: Go redirects to login auth route (/auth/login/unilogin)
Note over Go: See chapter: "Building the Unilogin authorization url"
Go->>Go: Go builds an authorization url
Note over Go: code_verifier is used for validating authenticity of the redirect back from the Unilogin login
Go->>Go: Stores code_verifier value in Go session cookie
Go->>Unilogin Login: Go is redirecting to the external Unilogin form by using the authorization url
Unilogin Login->>Go: After successful login the patron is redirected to the unilogin callback route (/auth/callback/unilogin)
Unilogin WS-->>Go: Go requests access token, refresh token and expire timestamps and validates the expected response
Note over Unilogin WS: Introspection data contains uniid and institution_ids of the user
Unilogin WS-->>Go: Go requests and validates introspection data from the access token
Note over Unilogin WS: Userinfo data contains the sub that in this case is a GUID
Unilogin WS-->>Go: Go requests and validates user info
Note over Go: See chapter: "Unilogin login authorization check"
Go-->>Go: Checks if user is authorized to log in
Go-->>Go: Saves the go session with tokens and user info
Go-->>Go: Saves the go session type cookie
Go-->>Go: Redirects the Patron to the user profile page
Building the Unilogin authorization url¶
In order to follow the Oauth2 standard and the Unilogin STIL specification an authorization url is constructed with the help of the openid-client tool.
A PKCE code verifier is generated (the GO_SESSION_SECRET
is used as salt).
The code verifier is stored in the session for future validation of the authenticity of the request from Unilogin coming back from the external login form.
And the code verifier is also used in order to create the code challenge needed as an url parameter for the authorization url.
Unilogin login authorization check¶
As a part of the Unilogin flow when coming back form a successful login we check
if the municipality id (kommunenr
) of the first institution in the userinfo
matches the one that is configured to the site (UNILOGIN_MUNICIPALITY_ID
).
If the id's are identical the user is allowed to login in otherwise a logout is
forced both in the SSO and locally.
Login via Adgangsplatformen¶
sequenceDiagram
actor Patron
participant Go
participant CMS
participant CMS Graphql API
participant Adgangsplatformen
Note over CMS: The login url contains the route to the login route in the CMS<br/>and a url parameter (current_path which is an internal CMS url) is attached.<br /> current_path instructs the CMS where to go after the external SSO login
CMS-->>Go: Go fetches the login url from the CMS
Patron->>Go: Patron opens the Login sheet
Patron->>Go: Clicks Adgangsplatformen login button
Go->>CMS: Go redirects patron to /login at the CMS
Note over Adgangsplatformen: NB: The Adgangsplatformen Oauth flow<br />is described in the dpl-cms documentation
CMS->>Adgangsplatformen: Patron is sent to login form at Adgangsplatformen
Adgangsplatformen->>CMS: After successful login the patron is redirected to the CMS
Note over CMS,CMS: The Go specific route<br />(dpl_go.post_adgangsplatformen_login) in the CMS<br />is specified via the current_path url parameter
CMS->>CMS: The CMS redirects to the Go specific route
Note over Go: The callback endpoint in Go is at /auth/callback/adgangsplatformen
CMS->>Go: The CMS redirects to a callback endpoint in Go
Note over CMS,Go: By passing the Drupal SESS* cookie in the header<br/>Go is authorized and identified as the Drupal Patron user
CMS Graphql API-->>Go: Go fetches the user token from CMS API
Go->>Go: Go instantiates a go session with the user token attached
Go->>Go: Patron is redirected to the user profile page
Logout¶
When a user click logout we need to handle that the current session either can be:
- Adgangsplatformen
- Unilogin
- Anonymous
- In a, for some reason, broken state
This chart shows how we handle the various types:
flowchart TD
UserClicksLogout[User clicks logout] -->
RedirectToLogoutEndpoint[User gets redirected to logout endpoint] -->
SessionExist{Is there an active go-session?}
SessionExist --> |Yes| CheckType{Check type}
CheckType --> IsUnknown[Unknown]
CheckType --> IsUnilogin[Unilogin]
CheckType --> IsAdgangsplatformen[Adgangsplatformen]
IsUnknown --> DestroySession[Destroy Go session - and id token]
IsAdgangsplatformen --> DestroySessionBeforeRedirect[
Destroy Go session - and id token, if it exist
]
DestroySessionBeforeRedirect ---> RedirectToAdgangsplatformenLogout[
Redirect to CMS Adgangsplatformen logout - with current_path url arg
]
RedirectToAdgangsplatformenLogout --> LogoutRemoteAdgangsplatformen[
Logging out of Adgangsplatformen remotely
]
LogoutRemoteAdgangsplatformen ---> RedirectBackToGo[
CMS redirects back to Go frontpage
]
IsUnilogin --> CallUniloginLogout[Call Unilogin Logout service]
CallUniloginLogout --> DestroySession[Destroy Go session - and id token]
DestroySession --> RedirectToFrontpage[Redirect to frontpage]
SessionExist -->|No| RedirectToFrontpage
Token handling¶
Token types¶
We have four different token types:
- Access token
- Refresh token
- Id token
- Library token
Access token¶
Access tokens exist in both Unilogin and Adgangsplatformen session. The Unilogin access token is only used to get user information as a part of the login process but apart from that is is not used in the rest of the application.
The Adgangsplatformen access token is a part of the go-session
iron-session cookie.
Whenever a fetch is fired and service requested needs an Adgangsplatformen access
token as bearer token, the access token is fetched from the internal
/auth/session
route.
Refresh token¶
Is used as a part of the Unilogin session. When access token is expired the refresh token is used to issue a new access token.
Id token¶
Is used when a user logs out of an active Unilogin session to terminate the
remote SSO session.
See the handleUniloginLogout()
function.
Library token¶
The documentation of the library token does not really belong here since it is not a part of the session or authentication process. But since we document all the token here it is worth mentioning.
The library token is fetched regularly in the middleware and set as a cookie. Whenever it expired a new library token is fetched and the cookie is updated. As mentioned before it is a separate system and not coupled to the session handling.