Authentication Methods
In web applications, including SaaS, there are two main authentication methods:
・Session-based authentication
・Token-based authentication
SaaSus Platform adopts token-based authentication.
The following sections provide an overview of each authentication method.
Types of Authentication
Session-Based Authentication
Session-based authentication is a method in which the client authenticates using a session ID generated by the server.
This method allows for stateful communication; however, it places a higher load on the server compared to token-based authentication.
Token-Based Authentication
Token-based authentication is a method in which the client authenticates using token information.
This method does not require the server to store authentication information, making it lower in server load compared to session-based authentication and enabling stateless communication.
Differences Between Session-Based and Token-Based Authentication
Session-based authentication stores authentication information on the server, whereas token-based authentication does not store authentication information on the server and relies solely on token validation for authentication.
Authentication in SaaSus Platform
SaaSus Platform adopts token-based authentication.
Token generation and validation are performed using the SaaSus Platform SDK, which calls the SaaSus Platform API.
Authentication Timing
When executing processes that require login, authentication must be performed at the time the server receives the request.
In SaaSus Platform, authentication is carried out for each request using the SaaSus Platform SDK.
By executing the user information retrieval process in the SDK, the following information can be obtained:
・Token validation
・Retrieval of logged-in user information (user data, tenant data, permissions)
If there are multiple processes that require login, it is necessary to check the login status by retrieving user information using the SaaSus Platform SDK at the start of each process.
As the number of processes increases, implementing authentication individually becomes challenging.
Therefore, it is recommended to utilize the middleware functionality of the framework to introduce a unified authentication check mechanism.
Types of Tokens
SaaSus Platform generates the following three types of tokens:
・ID Token
・Access Token
・Refresh Token
ID Token
The ID token is used for authentication and has a validity period of one hour.
Once the token expires, it becomes invalid, and the SaaS user will be logged out.
- PHP
- Node.js
- Go
- Python
- Java
- C#(.Net8)
- C#(.Netfw4.8)
// In the case of Laravel, use the standard Auth Middleware of the SaaSus SDK in the routing file.
// Define routes for features that require authentication on the SaaSus Platform.
Route::middleware(\AntiPatternInc\Saasus\Laravel\Middleware\Auth::class)->group(function () {
Route::get('/userinfo', [IndexController::class, 'userinfo']);
Route::get('/users', [IndexController::class, 'users']);
Route::get('/tenant_attributes', [IndexController::class, 'tenantAttributes']);
Route::get('/user_attributes', [IndexController::class, 'userAttributes']);
Route::post('/user_register', [IndexController::class, 'userRegister']);
Route::delete('/user_delete', [IndexController::class, 'userDelete']);
Route::get('/delete_user_log', [IndexController::class, 'deleteUserLog']);
Route::get('/pricing_plan', [IndexController::class, 'pricingPlan']);
Route::get('/tenant_attributes_list', [IndexController::class, 'tenantAttributesList']);
Route::post('/self_sign_up', [IndexController::class, 'selfSignUp']);
Route::post('/logout', [IndexController::class, 'logout']);
});
// In the case of Express, use the standard AuthMiddleware of the SaaSus SDK.
// Define routes for features that require authentication on the SaaSus Platform.
import { AuthMiddleware } from 'saasus-sdk'
app.use([
'/userinfo',
'/users',
'/tenant_attributes',
'/user_register',
'/user_delete',
'/delete_user_log',
'/pricing_plan',
'/tenant_attributes_list',
'/self_sign_up'
], AuthMiddleware)
// By using the created authMiddlewareEcho, user authentication is performed, and UserInfo is set in the context.
authMiddleware := authMiddlewareEcho(idTokenGetter)
e.GET("/userinfo", getMe, authMiddleware)
e.GET("/users", getUsers, authMiddleware)
e.GET("/tenant_attributes", getTenantAttributes, authMiddleware)
e.GET("/user_attributes", getUserAttributes, authMiddleware)
e.GET("/pricing_plan", getPricingPlan, authMiddleware)
e.POST("/user_register", userRegister, authMiddleware)
e.DELETE("/user_delete", userDelete, authMiddleware)
e.GET("/delete_user_log", getDeleteUserLogs, authMiddleware)
e.POST("/self_sign_up", selfSignup, authMiddleware)
e.POST("/logout", logout, authMiddleware)
func authMiddlewareEcho(getter middleware.IDTokenGetter) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
userInfo, err := middleware.Authenticate(c.Request().Context(), getter.GetIDToken(c.Request()))
if err != nil {
http.Error(c.Response().Writer, "Unauthorized "+err.Error(), http.StatusUnauthorized)
return nil
}
c.Set(string(ctxlib.UserInfoKey), userInfo)
return next(c)
}
}
}
# In the case of FastAPI, create an authentication method.
def fastapi_auth(request: Request) -> Union[dict, HTTPException]:
auth_header = request.headers.get("Authorization", "")
token = auth_header.replace("Bearer ", "") if "Bearer " in auth_header else ""
referer = request.headers.get("Referer", "")
user_info, error = auth.authenticate(id_token=token, referer=referer)
if error:
raise HTTPException(status_code=401, detail=str(error))
return user_info
# Call the created authentication method in routes that require authentication.
@app.get("/userinfo")
def get_user_info(user_info: dict = Depends(fastapi_auth)):
return user_info
// Create a method to obtain the ID token
public static String getIDToken(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null) {
StringTokenizer st = new StringTokenizer(authHeader);
if (st.countTokens() == 2 && st.nextToken().equalsIgnoreCase("Bearer")) {
return st.nextToken();
}
}
return "";
}
@GetMapping(value = "/userinfo", produces = "application/json")
public ResponseEntity<?> getMe(HttpSession session, HttpServletRequest request) throws Exception {
AuthApiClient apiClient = new Configuration().getAuthApiClient();
apiClient.setReferer(request.getHeader("Referer"));
UserInfoApi userInfoApi = new UserInfoApi(apiClient);
UserInfo userInfo = null;
try {
// Call the created method to obtain the ID token when executing authentication-required processes.
userInfo = userInfoApi.getUserInfo(getIDToken(request));
System.out.println(userInfo);
} catch (ApiException e) {
System.err.println("Exception when calling UserInfoApi#getUserInfo");
System.err.println("Status code: " + e.getCode());
System.err.println("Reason: " + e.getResponseBody());
System.err.println("Response headers: " + e.getResponseHeaders());
e.printStackTrace();
throw e;
}
System.out.println(userInfo.toJson());
return ResponseEntity.ok(userInfo.toJson());
}
// Create a method to obtain the ID token
string? GetBearerToken(HttpContext context)
{
if (context.Request.Headers.TryGetValue("Authorization", out var authHeader) &&
authHeader.ToString().StartsWith("Bearer "))
{
return authHeader.ToString().Substring("Bearer ".Length).Trim();
}
return null;
}
app.MapGet("/userinfo", async (HttpContext context) =>
{
// Call the created method to obtain the ID token when executing authentication-required processes.
var token = GetBearerToken(context);
if (string.IsNullOrEmpty(token))
{
return Results.Unauthorized();
}
try
{
var authApiClientConfig = CreateClientConfiguration(c => c.GetAuthApiClientConfig(), context);
var userInfoApi = new UserInfoApi(authApiClientConfig);
var userInfo = await userInfoApi.GetUserInfoAsync(token);
var jsonResponse = userInfo.ToJson();
return Results.Text(jsonResponse, "application/json");
}
catch (Exception ex)
{
return HandleApiException(ex);
}
});
// Create a method to obtain the ID token
private string GetBearerToken(HttpRequestMessage request)
{
var authHeader = request.Headers.Authorization;
if (authHeader != null && authHeader.Scheme.Equals("Bearer", StringComparison.OrdinalIgnoreCase))
{
return authHeader.Parameter?.Trim() ?? throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
[HttpGet]
[Route("userinfo")]
public async Task<IHttpActionResult> GetUserInfo()
{
try
{
// Call the created method to obtain the ID token when executing authentication-required processes.
var token = GetBearerToken(Request);
var authApiClientConfig = CreateClientConfiguration(c => c.GetAuthApiClientConfig());
var userInfoApi = new UserInfoApi(authApiClientConfig);
var userInfo = await userInfoApi.GetUserInfoAsync(token);
return Ok(userInfo);
}
catch (Exception ex)
{
return HandleApiException(ex);
}
}
Refresh Token
If a longer login session is required, the refresh token can be used to regenerate the ID token, enabling continuous login.
The refresh token has a validity period of one month.
- PHP
- Node.js
- Go
- Python
- Java
- C#(.Net8)
- C#(.Netfw4.8)
public function refresh(Request $request)
{
// Obtain the refresh token
$refreshToken = $request->cookie('SaaSusRefreshToken');
if (!is_string($refreshToken)) {
return response('Refresh token not found', Response::HTTP_BAD_REQUEST);
}
try {
$authClient = $this->client->getAuthClient();
// The first argument is for setting the temporary code, so specify Blank.
// Set the authentication flow to refreshTokenAuth in the second argument, and set the refresh token retrieved from the Cookie in the third argument.
$response = $authClient->getAuthCredentials([
'',
'refreshTokenAuth',
$refreshToken
]);
return response()->json($response->getBody());
} catch (\Exception $e) {
return response('Error occurred', Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
app.get('/refresh', async (request: Request, response: Response) => {
// Obtain the refresh token
const refreshToken = request.cookies.SaaSusRefreshToken
if (typeof refreshToken !== 'string') {
response.status(400).send('Refresh token not found')
return
}
const client = new AuthClient()
// The first argument is for setting the temporary code, so specify Blank.
// Set the authentication flow to refreshTokenAuth in the second argument, and set the refresh token retrieved from the Cookie in the third argument.
const credentials = (
await client.credentialApi.getAuthCredentials(
'',
'refreshTokenAuth',
refreshToken
)
).data
response.send(credentials)
})
func refresh(c echo.Context) error {
// Obtain the refresh token
token, err := c.Cookie("SaaSusRefreshToken")
if err != nil {
return c.String(http.StatusInternalServerError, "internal server error")
}
c.Logger().Error("SaaSusRefreshToken: %v", token.Value)
// Set the refresh token retrieved from the Cookie in the second argument.
credentials, err := credential.GetAuthCredentialsWithRefreshTokenAuth(context.Background(), token.Value)
if err != nil {
return c.String(http.StatusInternalServerError, "internal server error")
}
return c.JSON(http.StatusOK, credentials)
}
@app.get("/refresh")
def refresh(request: Request):
# Obtain the refresh token
saasus_refresh_token = request.cookies.get("SaaSusRefreshToken")
if not saasus_refresh_token:
raise HTTPException(status_code=400, detail="SaaSusRefreshToken is missing")
try:
# Obtain new authentication credentials using the refresh token.
credentials = callback.get_refresh_token_auth_credentials(saasus_refresh_token)
return credentials
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
// Obtain the refresh token from the cookie.
@GetMapping(value = "/refresh", produces = "application/json")
public ResponseEntity<?> refresh(HttpSession session, HttpServletRequest request,
@CookieValue(name = "SaaSusRefreshToken", defaultValue = "") String cookieValue) throws Exception {
if (cookieValue == "") {
return ResponseEntity.badRequest().body("No refresh token found");
}
AuthApiClient apiClient = new Configuration().getAuthApiClient();
apiClient.setReferer(request.getHeader("Referer"));
CredentialApi apiInstance = new CredentialApi(apiClient);
Credentials result = null;
try {
// Set the first argument to null to specify a temporary code.
// Set the second argument to refreshTokenAuth for the authentication flow, and set the third argument to the refresh token obtained from the cookie.
result = apiInstance.getAuthCredentials(null, "refreshTokenAuth", cookieValue);
} catch (ApiException e) {
System.err.println("Exception when calling CredentialApi#getAuthCredentials");
System.err.println("Status code: " + e.getCode());
System.err.println("Reason: " + e.getResponseBody());
System.err.println("Response headers: " + e.getResponseHeaders());
e.printStackTrace();
throw e;
}
return ResponseEntity.ok(result.toJson());
}
app.MapGet("/refresh", async (HttpContext context) =>
{
// Retrieve the refresh token from the cookie.
var refreshToken = context.Request.Cookies["SaaSusRefreshToken"];
if (string.IsNullOrEmpty(refreshToken))
{
return Results.BadRequest("No refresh token found.");
}
try
{
var authApiClientConfig = CreateClientConfiguration(c => c.GetAuthApiClientConfig(), context);
var credentialApi = new CredentialApi(authApiClientConfig);
// Set the first argument to null to specify a temporary code.
// Set the second argument to refreshTokenAuth for the authentication flow, and set the third argument to the refresh token obtained from the cookie.
var credentials = await credentialApi.GetAuthCredentialsAsync(null, "refreshTokenAuth", refreshToken);
var jsonResponse = credentials.ToJson();
return Results.Text(jsonResponse, "application/json");
}
catch (Exception ex)
{
return HandleApiException(ex);
}
});
[HttpGet]
[Route("refresh")]
public async Task<IHttpActionResult> Refresh()
{
try
{
// Retrieve the refresh token from the cookie.
var refreshTokenCookie = Request.Headers.GetCookies("SaaSusRefreshToken").FirstOrDefault();
if (refreshTokenCookie == null)
{
return BadRequest("No refresh token found.");
}
var authApiClientConfig = CreateClientConfiguration(c => c.GetAuthApiClientConfig());
var credentialApi = new CredentialApi(authApiClientConfig);
// Set the first argument to null to specify a temporary code.
// Set the second argument to refreshTokenAuth for the authentication flow, and set the third argument to the refresh token obtained from the cookie.
var credentials = await credentialApi.GetAuthCredentialsAsync(null, "refreshTokenAuth", refreshTokenCookie["SaaSusRefreshToken"].Value);
return Ok(credentials);
}
catch (Exception ex)
{
return HandleApiException(ex);
}
}
Access Token
The access token is required when using features that require authorization.
For example, it is used for functions that require specific permissions, such as inviting users to a tenant.
- PHP
- Node.js
- Go
- Python
- Java
- C#(.Net8)
- C#(.Netfw4.8)
// Retrieve information from the request.
$email = $request->input('email');
$tenantId = $request->input('tenantId');
if (!$email || !$tenantId) {
return response()->json(['message' => 'Missing required fields'], Response::HTTP_BAD_REQUEST);
}
// Retrieve UserInfo.
$userInfo = $request->userinfo;
if (!$userInfo) {
return response()->json(['detail' => 'No user'], Response::HTTP_BAD_REQUEST);
}
try {
// Retrieve the access token of the user creating the invitation.
$accessToken = $request->header('X-Access-Token');
// If the access token is not included in the request header, return an error.
if (empty($accessToken)) {
return response()->json(['error' => 'Access token is missing'], 401);
}
// Create the parameters for the tenant invitation.
$createTenantInvitationParamEnvsItem = new CreateTenantInvitationParamEnvsItem();
$createTenantInvitationParamEnvsItem
->setId(3) // Specify the ID of the production environment: 3.
->setRoleNames(['admin']);
$createTenantInvitationParam = new CreateTenantInvitationParam();
$createTenantInvitationParam
->setEmail($email)
->setAccessToken($accessToken)
->setEnvs([$createTenantInvitationParamEnvsItem]);
// Call the tenant invitation API.
$authClient = $this->client->getAuthClient();
$authClient->createTenantInvitation(
$tenantId,
$createTenantInvitationParam
);
return response()->json(['message' => 'Create tenant user invitation successfully']);
} catch (\Exception $e) {
Log::error($e->getMessage());
return response()->json(['detail' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
const { email, password, tenantId, userAttributeValues }: UserRegisterRequest = request.body
if (!email || !password || !tenantId) {
return response.status(400).send({ message: 'Missing required fields' });
}
const userInfo = request.userInfo
if (userInfo === undefined) {
return response.status(400).json({ detail: 'No user' })
}
try {
// Retrieve the access token of the user creating the invitation.
const accessToken = request.header('X-Access-Token')
// If the access token is not included in the request header, return an error.
if (!accessToken) {
return response.status(401).json({ detail: 'Access token is missing' })
}
// Create the parameters for the tenant invitation.
const invitedUserEnvironmentInformationInner: InvitedUserEnvironmentInformationInner = {
id: 3, // Specify the ID of the production environment: 3.
role_names: ['admin']
}
const createTenantInvitationParam: CreateTenantInvitationParam = {
email: email,
access_token: accessToken,
envs: [invitedUserEnvironmentInformationInner]
}
// Call the tenant invitation API.
const client = new AuthClient()
await client.invitationApi.createTenantInvitation(tenantId, createTenantInvitationParam)
response.json({ message: 'Create tenant user invitation successfully' })
} catch (error) {
console.error(error);
response.status(500).json({ detail: error });
}
var request UserRegisterRequest
if err := c.Bind(&request); err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{"error": "Invalid request"})
}
email := request.Email
tenantID := request.TenantID
userInfo, ok := c.Get(string(ctxlib.UserInfoKey)).(*authapi.UserInfo)
if !ok {
c.Logger().Error("failed to get user info")
return c.String(http.StatusInternalServerError, "internal server error")
}
// Retrieve the access token of the user creating the invitation.
accessToken := c.Request().Header.Get("X-Access-Token")
// If the access token is not included in the request header, return an error.
if accessToken == "" {
return c.String(http.StatusBadRequest, "Access token is missing")
}
// Create the parameters for the tenant invitation.
createTenantInvitationJSONRequestBody := authapi.CreateTenantInvitationJSONRequestBody{
AccessToken: accessToken,
Email: email,
Envs: []struct {
Id uint64 `json:"id"`
RoleNames []string `json:"role_names"`
}{
{
Id: 3, // Specify the ID of the production environment: 3.
RoleNames: []string{"admin"},
},
},
}
// Call the tenant invitation API.
authClient.CreateTenantInvitation(context.Background(), tenantID, createTenantInvitationJSONRequestBody)
return c.JSON(http.StatusOK, echo.Map{"message": "Create tenant user invitation successfully"})
# Retrieve information from the request.
email = request.email
tenant_id = request.tenantId
try:
# Retrieve the access token of the user creating the invitation.
access_token = fast_request.headers.get("X-Access-Token")
# If the access token is not included in the request header, return an error.
if not access_token:
raise HTTPException(status_code=401, detail="Access token is missing")
# Create the parameters for the tenant invitation.
invited_user_environment_information_inner = InvitedUserEnvironmentInformationInner(
id=3, # Specify the ID of the production environment: 3.
role_names=['admin']
)
create_tenant_invitation_param = CreateTenantInvitationParam(
email=email,
access_token=access_token,
envs=[invited_user_environment_information_inner]
)
# Call the tenant invitation API.
InvitationApi(api_client=api_client).create_tenant_invitation(tenant_id=tenant_id, create_tenant_invitation_param=create_tenant_invitation_param)
return {"message": "Create tenant user invitation successfully"}
except Exception as e:
print(e)
raise HTTPException(status_code=500, detail=str(e))
try {
String email = requestBody.getEmail();
String tenantId = requestBody.getTenantId();
AuthApiClient apiClient = new Configuration().getAuthApiClient();
apiClient.setReferer(request.getHeader("Referer"));
UserInfoApi userInfoApi = new UserInfoApi(apiClient);
UserInfo userInfo = userInfoApi.getUserInfo(getIDToken(request));
// Retrieve the access token from the request header.
String accessToken = request.getHeader("X-Access-Token");
// If the access token is not included in the request header, return an error.
if (accessToken == null || accessToken.isEmpty()) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Access token is missing");
}
// Create the parameters for the tenant invitation.
InvitedUserEnvironmentInformationInner invitedUserEnvironmentInformationInner = new InvitedUserEnvironmentInformationInner()
.id(3) // Specify the ID of the production environment: 3.
.addRoleNamesItem("admin");
CreateTenantInvitationParam createTenantInvitationParam = new CreateTenantInvitationParam()
.email(email)
.accessToken(accessToken)
.addEnvsItem(invitedUserEnvironmentInformationInner);
// Call the tenant invitation API.
InvitationApi invitationApi = new InvitationApi(apiClient);
invitationApi.createTenantInvitation(tenantId, createTenantInvitationParam);
Map<String, String> successResponse = new HashMap<>();
successResponse.put("message", "Create tenant user invitation successfully");
return ResponseEntity.ok(successResponse);
} catch (ApiException e) {
System.err.println("API Exception: " + e.getMessage());
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "API Exception: " + e.getMessage(), e);
} catch (Exception e) {
System.err.println("Unexpected Exception: " + e.getMessage());
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected Exception: " + e.getMessage(), e);
}
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(requestBody);
if (!Validator.TryValidateObject(requestBody, validationContext, validationResults, true))
{
var errors = validationResults.Select(vr => new { Field = vr.MemberNames.FirstOrDefault(), Error = vr.ErrorMessage });
return Results.BadRequest(new { error = "Validation failed.", details = errors });
}
string email = requestBody.Email;
string tenantId = requestBody.TenantId;
try
{
// Retrieve the access token from the request header.
var accessToken = context.Request.Headers["X-Access-Token"].FirstOrDefault();
// If the access token is not included in the request header, return an error.
if (string.IsNullOrEmpty(accessToken))
{
return Results.Unauthorized();
}
var authApiClientConfig = CreateClientConfiguration(c => c.GetAuthApiClientConfig(), context);
var invitationApi = new InvitationApi(authApiClientConfig);
// Create an object to add to the envs.
var invitedEnv = new InvitedUserEnvironmentInformationInner(
id: 3, // Specify the ID of the production environment: 3.
roleNames: new List<string> { "admin" }
);
// Create a list of envs
var envsList = new List<InvitedUserEnvironmentInformationInner> { invitedEnv };
// Create the parameters for the tenant invitation.
var createTenantInvitationParam = new CreateTenantInvitationParam(
email,
accessToken,
envsList
);
// Call the tenant invitation API.
invitationApi.CreateTenantInvitation(tenantId, createTenantInvitationParam);
return Results.Ok(new { message = "Create tenant user invitation successfully" });
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error: {ex.Message}");
return Results.Problem(detail: ex.Message, statusCode: 500);
}
if (!ModelState.IsValid)
return BadRequest(ModelState);
string email = request.Email;
string tenantId = request.TenantId;
try
{
// Retrieve the access token from the request header.
var accessToken = HttpContext.Current.Request.Headers.Get("X-Access-Token");
var authApiClientConfig = CreateClientConfiguration(c => c.GetAuthApiClientConfig());
var invitationApi = new InvitationApi(authApiClientConfig);
// Create an object to add to the envs.
var invitedEnv = new InvitedUserEnvironmentInformationInner(
id: 3, // Specify the ID of the production environment: 3.
roleNames: new List<string> { "admin" }
);
// Create a list of envs
var envsList = new List<InvitedUserEnvironmentInformationInner> { invitedEnv };
// Create the parameters for the tenant invitation.
var createTenantInvitationParam = new CreateTenantInvitationParam(
email,
accessToken,
envsList
);
// Call the tenant invitation API.
invitationApi.CreateTenantInvitation(tenantId, createTenantInvitationParam);
return Ok(new { message = "Create tenant user invitation successfully", request });
}
catch (Exception ex)
{
return HandleApiException(ex);
}