Skip to content

devclub-iitd/SingleSignOn

Repository files navigation

DevClub Single Sign On

This is an implementation of an SSO for DevClub sites. A web server hosted at auth.devclub.in handles the authentication part for each service on DevClub domains and allows a user to stay signed in across multiple services.

Our model uses a single SSO JWT token generated by the auth server which is stored in a cookie and will be sent to all subdomains of devclub.in

JWT Structure

Payload

    "user":{
    	"id": <uid>,
    	"firstname": <First Name>
    	"lastname": <Last Name>
    	"email": <Registered Email>
    	"roles": [<list of role strings> ]
		"is_verified" : A boolean specifying whether the user is verified
    }

Signature We utilize a RSA private key at the auth server to sign the payload and the public key is distributed to the SSO client at the time of registration.

Cookie Properties

  1. HttpOnly - This ensures that the cookie is inaccessible frontend javascript and hence immunises us against XSS attacks
  2. Secure - Enables transmission over SSL only.
  3. SameSite - Lax, Enables transmission of cookies from a.devclub.in to b.devclub.in
  4. max-age - 900s

These features hence rely on the browser to do all the cookies handling part and hence nothing has to be changed in the front-end code of any client.

Registration with SSO

A client will have to register with the SSO before using the service. The registration can be done at auth.devclub.in/client/register.

Note: This endpoint will be accessible only to devclub members.

The client will give its basic details like the subdomain name, and they can also specify what additional roles are required by them, to implement specific access control for their subdomain and what filter should be applied to check if a particular user is entitled for this role.

For example, yearbook when registering with the SSO gives the following information to the SSO server.

  • SUBDOMAIN = yearbook
  • ROLE_NAME = yearbook_role
  • ROLE_FILTER = {fname:<regexp>,lname:<regexp>,entry_num:<regexp>...}

The ROLE_FILTER will be an object with regexp to match for each field in the user model. Only when all regexp are matched, the user will be given this role.

Note:

  • The Client can specify multiple roles as long as the role_names do not conflict with existing role names in the db.
  • Alternatively, we can keep ROLE_FILTER to be an arrow function that takes the user model as input and returns True/False after verifying if the user is entitled to the role or not. However to implement this method we will have to take care of input sanitisation since it can lead to privilege escalation, once a single devclub member account is compromised.

As soon as the client is finished giving the information, the auth server will run the ROLE_FILTER regexps on each user and if the regex match, the ROLE_NAME will be appended to the roles field of the user model.

The SSO server returns some variables for the client to store in its .env file.

  • Universal Role Strings (like regular_user, iitd_user, admin, devclub_member, etc.)
  • Service specific custom Role Strings - The Role strings that the client had requested at the time of registration
  • The Server's public key - to decrypt the SSO token and hence verify it.

Login and accessing

  1. The user visits yearbook.devclub.in.
  2. Middleware -
    1. The middleware at yearbook.devclub.in checks for the presence of a token in the cookie.
    2. If this is found the middleware verifies the token using the public key provided by SSO.
    3. After verification it checks if the Access Control Role specified by yearbook at the time of its registration with SSO server is present in the payload data or not. Note: The role strings that need to be checked for in the payload will have to be provided to the middleware function as parameters. Hence this needs to be specified by the client that which roles does it need to check for authenticating the user. The role strings can be present in the .env file at the client. They are provided by the auth server during the registration of the client with SSO.
    4. Refresh - Next it checks if ttl for cookie is less than 1 min, if so then it sends a request to auth.devclub.in/refresh-token with the present access token and the auth server sends a response with Set-Cookie header to a new access-token.
    5. If all the above checks pass the user is authenticated, and if not then the user is redirected to auth.devclub.in/login/?serviceURL=yearbook.devclub.in
  3. SSO Server login - At the login page of auth.devclub.in/login/?serviceURL=<service_url> the user enters the credentials for logging in, and after the credentials are verified in the database, the SSO service sends a response to set a Cookie with the SSO token that is signed using the private key that only the SSO server holds. It then redirects the user back to the serviceURL in the query parameter.

Accessing another site

  1. The user accesses say study.devclub.in
  2. The browser sends the token cookie to study.devclub.in (since SameSite=Lax) and the middleware library performs the same functions as outlined in the section Login and accessing

Refresh token

Handled by middleware by sending request to auth.devclub.in/refresh-token when the ttl is less than 1 min. This is outlined in Refresh by middleware

Logging Out

The middleware deletes the cookie from the browser by sending a Set-Cookie header with a cookie that has expired in the past. This effectively logs the user out of all the services on DevClub.

Password Change

When doing a password change, the old password and new password will be asked, when the request succeeds the user will be logged out and redirected to auth.devclub.in/user/login.

Password Reset

Since the user is not logged in, hence the basic procedure of first checking the email in the database and then sending a password reset verification link at the specified link is to be followed. Once the user resets the password, it will be redirected to auth.devclub.in/user/login

Database

We have utilized MongoDb for our database.

The model for the db is as follows: Table 1 - User Model

user = {
  unique_id
  name, email and other metadata
  password_hash,
  roles: ManytoMany relationship to "Role" model,
  is_verified: <boolean>
}

If someone registers with an email, then we have a verification system, where a verification email is sent at the email-address. Until the email-address is verified is_verified is set to False, and only upon verification is_verified is set to True.

All log-in requests for is_verified=False users are not authenticated, unless the account is verified.

Table 2: Role

role:{
	role_name: <string>,
	regex_dict: {fname:<regexp>,lname:<regexp>,entry_num:<regexp>,...}
}

Table 3: SSO Client

services = {
  subdomain: <string>
  custom_roles: Many to Many relationship to model Role
}

Table 4: Social Accounts

social = {
	provider: <string>,
	email: <string>,
	primary_account: Foreign Key to User model
}
  • Whenever Social Auth is used to login, - first the tuple(provider,email) is checked to see if it already exists in the social model objects. - If an entry is found the user is logged in with the matching object's primary_account field - Else a new db entry for a primary account (User model) will also be created with the default role of regular_user and this user object will be set in the primary_account field of the social accounts object.

  • However when the user is already logged in and asks for linking their account with a social account, a new db entry for only the social account model will be created and the primary_account will be set to the user object that requested linkage of accounts.

Some points worth noting

  • Only a single token is used and hence this reduces the hassle of managing different tokens for each service, hence creating problems in logout. In this model once logged out the user gets logged out of all the services since there is a global dependency on the sso_token
  • The roles field in the payload can take care of both the public access control and the private access control rules. A client can choose to use the universal roles (like regular_user, iitd_user etc.) while invoking the middleware (which will be the case most of the times), however some websites likeyearbook may require additional access control methods (like allowing only final year students). These can be specified during the registration as they are some additional roles relevant only for yearbook. Hence if in future any new service of devclub comes up and it requires some access control rules, they can follow this doc to get themselves registered to the SSO and have their private access control managed by the SSO server.

Releases

No releases published

Packages

No packages published