CONTEÚDO

01. VISÃO GERAL

Disponibilizar um modelo de autenticação e autorização de acesso a recursos (endpoints), que permita abstração e flexibilidade com a utilização de um serviço de login independente (conforme modelo de arquitetura especificado na RFC000010 e RFC000015). 


02. CONCEITOS APLICADOS

OAuth2 é um protocolo que permite aos usuários ter acesso a determinados recursos sem precisar expor suas credenciais.


COMO FUNCIONA

Ao entrar em uma empresa com controle de acesso, é necessário efetuar previamente o cadastro na portaria (autenticação).

De acordo com a sua visita, a portaria garantirá a autorização de acesso temporária e você receberá um crachá para entrar em determinadas portas.

Independente da restrição que o crachá impõe a seus utilizadores, a ideia é clara: você dá a alguém acesso limitado as portas de acordo com a sua visita.



PARA QUE SERVE

Análogo ao crachá, para obter acesso limitado a recursos usando o OAuth2, utiliza-se um access token (token JWT), que permite usufruir recursos de terceiros de forma segura.



TIPOS DE CONCESSÃO DE ACESSO

A TOTVS utilizará no novo serviço de login um dos fluxos propostos pelo framework Oauth2, abaixo a sua especificidade:

  • O Grant Type para autenticação (Client Credentials ou Resource Owner Password Credentials);
  • Autorização de acesso conforme validade do token de acesso informado.



03. EXEMPLO DE UTILIZAÇÃO

Antes de iniciar a implementação, verifique qual o modelo é o mais adequado para as concessões de acesso, com base no melhor modelo de segurança,

Consulte os links: OAuth2 - Client Credentials e OAuth2 - Resource Owner Password Credentials para mais informações.

a. Configuração


As configurações para utilização dos conceitos de login conforme OAuth2 são realizadas nas telas de Propriedades, disponíveis no produto.

CFG - OAuth2Configura os modelos (Grant Type) de autenticação do usuário propostos nesta documentação.
CFG - JWTConfigura o modelo do Token JWT que será gerado caso a autenticação seja realizada com sucesso.



A seguir serão demonstrados alguns trechos de códigos em JavaScript e TypeScript para exemplificar uma utilização no produto.

Lembrando que o código pode ser alterado de acordo com a linguagem de programação utilizada, pois é necessário seguir somente os conceitos gerais de Autenticação (para gerar o token) e Autorização (validação do token).

b. Client Credentials

Geração do token

De posse com as credenciais de acesso (Id cliente e Senha cliente) cadatradas na tela de Propriedades → OAuth2 → Client Credentials 

No trecho de código abaixo, caso o token seja gerado com sucesso, seu retorno é armazenado no localStorage com a chave token-client.token. 

function token_client_credentials() {
	var http = new XMLHttpRequest();
	var url = '/totvs-login-oauth2/oauth2/token?grant_type=client_credentials';
	http.open('POST', url, true);

	http.setRequestHeader('Authorization', "Basic " + <id_cliente>":"+<senha_cliente>);

	http.onreadystatechange = function() {
		if(http.readyState == 4 && http.status == 200) {
			localStorage.setItem("token-client.token", http.responseText);
		}
	}
}

Geração dos tokens durante a utilização

Para limitar o uso indevido em caso de vazamentos, todos os tokens expiram (tempo parametrizável no ERP Datasul), por este motivo é importante que durante a utilização do portal os tokens sejam continuamente revalidados.

Com o intuito de facilitar a geração do token dentro do produto, é recomendado a utilização do refresh_token:

Método: POST

URL: http://{{host}}:{{port}}/totvs-login-oauth2/oauth2/token?grant_type=refresh_token

AuthorizationNão informar pois é considerada somente a validação do token

Body (x-www-form-urlencoded): 

  • refresh_token (Obrigatório): Enviar como x-www-form-urlencoded, o refresh_token gerado no login do produto.


  • O refresh_token não possui atributos de acesso (aud, tenantId, scope), portanto não deve ser utilizado para acessos aos recursos;
  • Todo refresh_token é vinculado a um access_token, sendo deste token os atributos considerados para a nova geração;
  • Por motivos de segurança, após a utilização do refresh_token, o mesmo é automaticamente invalidado;
  • Para considerar um novo ciclo de geração, é necessário considerar os novos tokens gerados a partir do refresh_token.


No trecho de código abaixo é demonstrado a geração contínua de um token (com base no refresh_token), sendo executada em um ciclo continuo 60 segundos antes de sua expiração:

$scope.refreshToken = function() {
	var http = new XMLHttpRequest();
	var url = '/totvs-login-oauth2/oauth2/token?grant_type=refresh_token';
	var params = `refresh_token=${$scope.getRefreshToken()}`;
	http.open('POST', url, true);
	http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
   
	http.onreadystatechange = function() {
		if(http.readyState == 4 && http.status == 200) {
			localStorage.setItem("token-client.token", http.responseText);
		}
	}
	http.send(params);
};

$scope.getExpiresInToken = function() {
	const tokenService = localStorage.getItem('token-client.token');
	let jsonTokenService = JSON.parse(tokenService);
	return jsonTokenService['expires_in'];
};

$scope.getRefreshToken = function() {
	const tokenService = localStorage.getItem('token-client.token');
	let jsonTokenService = JSON.parse(tokenService);
	return jsonTokenService['refresh_token'];
};

var expireIn = $scope.getExpiresInToken() * 1000;
/**
 * 1 minuto antes de expirar o token, tenta efetuar a renovacao
 */
setInterval(async() => {
	await $scope.refreshToken()
}, (expireIn - (60 * 1000)));

Utilização dos tokens para acessos aos recursos

Com o token disponível no localStorage e continuamente validado pelo refresh_token, para efetuar o acesso a determiados recrusos, basta resgatar o valor do access_token e envia-lo na requisição desejada.

O trecho de código abaixo demonstra o envio do token na URL devido as características deste endpoint (Smart View), porém este mesmo conceito pode ser adaptado com o modelo Authorization Bearer para acessar os endpoints do ERP Datasul.

public onIntegration(): void {
	let jwtToken = this.getAccessToken();
	... 
	<EFETUA A REQUISIÇÃO AO ENDPOINT, O TOKEN DEVE SER ENVIADO COM BEARER AUTHORIZATION>
	...
}

private getAccessToken(): String {
	let tokenService = localStorage.getItem('token-client.token');
	let jsonTokenService = JSON.parse(tokenService);
	return jsonTokenService['access_token'];
}


c. Resource Owner Password Credentials

A utilização do Resource Owner Password Credentials é similar ao Client Credentials, somente difere na autenticação, onde são utilizados outro endpoint e 

Login

Após efetuar a ação do login, o mesmo deve executar o endpoint de geração do token, onde seu resultado pode ser armazenado no localStorage do navegador para utilizações posteriores.

No trecho de código abaixo, caso o token seja gerado com sucesso, seu retorno é armazenado no localStorage com a chave token-service.token. 

function login_oauth2() {
	var http = new XMLHttpRequest();
	var url = '/totvs-login-oauth2/oauth2/token?grant_type=password';
	http.open('POST', url, true);

	http.setRequestHeader('Authorization', "Basic " + btoa($txtUsername.val()+":"+$txtPassword.val()));

	http.onreadystatechange = function() {
		if(http.readyState == 4 && http.status == 200) {
			localStorage.setItem("token-service.token", http.responseText);
		}
	}
}

Geração dos tokens durante a utilização

As etapas de atualização do token são identicas ao modelo anteriormente apresentado, pode ser utilizado os mesmos endpoints.


No trecho de código abaixo é demonstrado a geração contínua de um token (com base no refresh_token), sendo executada em um ciclo continuo 60 segundos antes de sua expiração:

$scope.refreshToken = function() {
	var http = new XMLHttpRequest();
	var url = '/totvs-login-oauth2/oauth2/token?grant_type=refresh_token';
	var params = `refresh_token=${$scope.getRefreshToken()}`;
	http.open('POST', url, true);
	http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
   
	http.onreadystatechange = function() {
		if(http.readyState == 4 && http.status == 200) {
			localStorage.setItem("token-service.token", http.responseText);
		}
	}
	http.send(params);
};

$scope.getExpiresInToken = function() {
	const tokenService = localStorage.getItem('token-service.token');
	let jsonTokenService = JSON.parse(tokenService);
	return jsonTokenService['expires_in'];
};

$scope.getRefreshToken = function() {
	const tokenService = localStorage.getItem('token-service.token');
	let jsonTokenService = JSON.parse(tokenService);
	return jsonTokenService['refresh_token'];
};

var expireIn = $scope.getExpiresInToken() * 1000;
/**
 * 1 minuto antes de expirar o token, tenta efetuar a renovacao
 */
setInterval(async() => {
	await $scope.refreshToken()
}, (expireIn - (60 * 1000)));

Utilização dos tokens para acessos aos recursos

Com o token disponível no localStorage e continuamente validado pelo refresh_token, para efetuar o acesso a determiados recrusos, basta resgatar o valor do access_token e envia-lo na requisição desejada.

O trecho de código abaixo demonstra o envio do token na URL devido as características deste endpoint (Smart View), porém este mesmo conceito pode ser adaptado com o modelo Authorization Bearer para acessar os endpoints do ERP Datasul.

public onSmartView(smLayout: SmartViewLayout): void {
	let jwtToken = this.getAccessToken();
	let urlView = `${this.urlSmartView}reports/${smLayout["id"]}/view?access_token=${jwtToken}&hidemenus=true&expires_in=0&token_type=bearer`;
	this.link = this.sanitizer.bypassSecurityTrustResourceUrl(urlView);        
	this.poPageSlide.open();
}

private getAccessToken(): String {
	let tokenService = localStorage.getItem('token-service.token');
	let jsonTokenService = JSON.parse(tokenService);
	return jsonTokenService['access_token'];
}