Writing a Custom Grant Type for WSO2 Identity Server 🔖
When we talk about OAuth2 and OpenID Connect(OIDC), we cannot talk about how those two operate by providing access to the client applications without talking about grant types.
In simple terms, grant type is a mechanism to provide access to protected sources. And with WSO2 Identity Server you can use multiple grant types like authorization code, implicit, and password grant types. You can refer to my previous articles to learn more about OAuth2, OIDC, and grant types.
Apart from the aforementioned grant types, the WSO2 Identity Server allows users to implement custom grant types to fulfill specific use cases.
In this article, we will be looking at how to implement a mobile number-based grant type for WSO2 Identity Server.
Although the latest release of WSO2 Identity Server is version 7.0 as of writing this article, I will be using WSO2 Identity Server 6.1 for our implementation. Furthermore, I will be using IntelliJ IDEA as the IDE, but you are free to use any IDE of your choice if you are following my steps to implement a custom grant type for WSO2 Identity Server.
Step 1: Create a new Maven project and add the necessary dependencies
Create a new Maven project using the below configurations for groupId and artifactId.
We don’t need the Main.java
file for our implementation. Therefore, you can delete that file. For the contents of the pom.xml
file, you can replace it with the below-given pom.xml
file. But make sure to update the groupId
and artifactId
properly.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.wso2.custom.grant.type</groupId>
<artifactId>org.wso2.custom.grant.type</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<oltu.version>1.0.2</oltu.version>
<carbon.identity.framework.version>5.25.92</carbon.identity.framework.version>
<carbon.user.core.version>4.9.0</carbon.user.core.version>
<maven.compiler.plugin.version>2.0</maven.compiler.plugin.version>
<commons.logging.version>1.2</commons.logging.version>
<carbon.identity.auth.oauth2>6.11.21</carbon.identity.auth.oauth2>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.client</artifactId>
<version>${oltu.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.carbon.identity.inbound.auth.oauth2</groupId>
<artifactId>org.wso2.carbon.identity.oauth</artifactId>
<version>${carbon.identity.auth.oauth2}</version>
</dependency>
<!--<dependency>
<groupId>org.ops4j.pax.logging</groupId>
<artifactId>pax-logging-api</artifactId>
</dependency>-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>${commons.logging.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.carbon</groupId>
<artifactId>org.wso2.carbon.user.core</artifactId>
<version>${carbon.user.core.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.carbon.identity.framework</groupId>
<artifactId>org.wso2.carbon.identity.application.authentication.framework</artifactId>
<version>${carbon.identity.framework.version}</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>wso2-nexus</id>
<name>WSO2 internal Repository</name>
<url>https://maven.wso2.org/nexus/content/groups/wso2-public/</url>
<releases>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>ignore</checksumPolicy>
</releases>
</repository>
<repository>
<id>wso2.releases</id>
<name>WSO2 internal Repository</name>
<url>https://maven.wso2.org/nexus/content/repositories/releases/</url>
<releases>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>ignore</checksumPolicy>
</releases>
</repository>
<repository>
<id>wso2.snapshots</id>
<name>WSO2 Snapshot Repository</name>
<url>https://maven.wso2.org/nexus/content/repositories/snapshots/</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.plugin.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Although we can use the configurations to make our grant type an OSGi service, it is not necessary here. That’s the reason you don’t see the OSGi dependencies here like in the previous custom components we have discussed. You can check out the previous custom component implementations from here.
Step 2: Implement the Custom Grant Type
When creating a new OAuth2 grant type for the WSO2 Identity Server, you need to write a handler and a validator for your grant type.
Grant type handler specifies how the validation must be done and how the token should be issued. This can be done in two ways. Either you can implement the AuthorizationGrantHandler
interface or you can extend the AbstractAuthorizationGrantHandler
class.
package org.wso2.custom.grant.type;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception;
import org.wso2.carbon.identity.oauth2.ResponseHeader;
import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenRespDTO;
import org.wso2.carbon.identity.oauth2.model.RequestParameter;
import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext;
import org.wso2.carbon.identity.oauth2.token.handlers.grant.AbstractAuthorizationGrantHandler;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import java.util.UUID;
public class MobileGrant extends AbstractAuthorizationGrantHandler {
private static Log log = LogFactory.getLog(MobileGrant.class);
public static final String MOBILE_GRANT_PARAM = "mobileNumber";
@Override
public boolean validateGrant(OAuthTokenReqMessageContext oAuthTokenReqMessageContext) throws IdentityOAuth2Exception {
log.info("Mobile Grant handler is hit");
boolean authStatus = false;
// extract request parameters
RequestParameter[] parameters = oAuthTokenReqMessageContext.getOauth2AccessTokenReqDTO().getRequestParameters();
String mobileNumber = null;
// find out mobile number
for(RequestParameter parameter : parameters){
if(MOBILE_GRANT_PARAM.equals(parameter.getKey())){
if(parameter.getValue() != null && parameter.getValue().length > 0){
mobileNumber = parameter.getValue()[0];
}
}
}
if(mobileNumber != null) {
//validate mobile number
authStatus = isValidMobileNumber(mobileNumber);
if(authStatus) {
// if valid set authorized mobile number as grant user
String tenantAwareUsername = MultitenantUtils.getTenantAwareUsername(mobileNumber);
/*
Please use AuthenticatedUser.createFederateAuthenticatedUserFromSubjectIdentifier() if a federated
user is involved with this custom grant.
*/
AuthenticatedUser mobileUser = AuthenticatedUser.createLocalAuthenticatedUserFromSubjectIdentifier(
tenantAwareUsername);
// Set the federated IdP name if a federated user is involved with this custom grant.
// mobileUser.setFederatedIdPName(FrameworkConstants.LOCAL_IDP_NAME);
oAuthTokenReqMessageContext.setAuthorizedUser(mobileUser);
oAuthTokenReqMessageContext.setScope(oAuthTokenReqMessageContext.getOauth2AccessTokenReqDTO().getScope());
} else{
ResponseHeader responseHeader = new ResponseHeader();
responseHeader.setKey("SampleHeader-999");
responseHeader.setValue("Provided Mobile Number is Invalid.");
oAuthTokenReqMessageContext.addProperty("RESPONSE_HEADERS", new ResponseHeader[]{responseHeader});
}
}
return authStatus;
}
@Override
public OAuth2AccessTokenRespDTO issue(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception {
OAuth2AccessTokenRespDTO tokenRespDTO = new OAuth2AccessTokenRespDTO();
tokenRespDTO.setExpiresIn(tokReqMsgCtx.getAccessTokenIssuedTime() + 10000);
tokenRespDTO.setAccessToken(UUID.randomUUID().toString());
tokenRespDTO.setRefreshToken(UUID.randomUUID().toString());
tokenRespDTO.setTokenType("mobile");
return tokenRespDTO;
}
public boolean authorizeAccessDelegation(OAuthTokenReqMessageContext tokReqMsgCtx)
throws IdentityOAuth2Exception {
// if we need to just ignore the end user's extended verification
return true;
// if we need to verify with the end user's access delegation by calling callback chain.
// However, you need to register a callback for this. Default call back just return true.
// OAuthCallback authzCallback = new OAuthCallback(
// tokReqMsgCtx.getAuthorizedUser(),
// tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId(),
// OAuthCallback.OAuthCallbackType.ACCESS_DELEGATION_TOKEN);
// authzCallback.setRequestedScope(tokReqMsgCtx.getScope());
// authzCallback.setCarbonGrantType(org.wso2.carbon.identity.oauth.common.GrantType.valueOf(tokReqMsgCtx.
// getOauth2AccessTokenReqDTO().getGrantType()));
// callbackManager.handleCallback(authzCallback);
// tokReqMsgCtx.setValidityPeriod(authzCallback.getValidityPeriod());
// return authzCallback.isAuthorized();
}
public boolean validateScope(OAuthTokenReqMessageContext tokReqMsgCtx)
throws IdentityOAuth2Exception {
// if we need to just ignore the scope verification
return true;
// if we need to verify with the scope n by calling callback chain.
// However, you need to register a callback for this. Default call back just return true.
// you can find more details on writing custom scope validator from here
// http://xacmlinfo.org/2014/10/24/authorization-for-apis-with-xacml-and-oauth-2-0/
// OAuthCallback scopeValidationCallback = new OAuthCallback(
// tokReqMsgCtx.getAuthorizedUser().toString(),
// tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId(),
// OAuthCallback.OAuthCallbackType.SCOPE_VALIDATION_TOKEN);
// scopeValidationCallback.setRequestedScope(tokReqMsgCtx.getScope());
// scopeValidationCallback.setCarbonGrantType(org.wso2.carbon.identity.oauth.common.GrantType.valueOf(tokReqMsgCtx.
// getOauth2AccessTokenReqDTO().getGrantType()));
//
// callbackManager.handleCallback(scopeValidationCallback);
// tokReqMsgCtx.setValidityPeriod(scopeValidationCallback.getValidityPeriod());
// tokReqMsgCtx.setScope(scopeValidationCallback.getApprovedScope());
// return scopeValidationCallback.isValidScope();
}
/**
* TODO
*
* You need to implement how to validate the mobile number
*
* @param mobileNumber
* @return
*/
private boolean isValidMobileNumber(String mobileNumber){
// just demo validation
if(mobileNumber.startsWith("033")){
return true;
}
return false;
}
@Override
public boolean isOfTypeApplicationUser() throws IdentityOAuth2Exception {
return true;
}
}
As you can see in the code, I am returning true for every mobile number starting with 033 to simplify the use case. However, if you want to check the mobile claim properly you can implement that logic in the code.
Grant type validator verifies and validates the token request and checks whether all the required parameters are sent with the request. This can be implemented by extending the AbstractValidator
class.
package org.wso2.custom.grant.type;
import org.apache.oltu.oauth2.common.validators.AbstractValidator;
import javax.servlet.http.HttpServletRequest;
public class MobileGrantValidator extends AbstractValidator<HttpServletRequest> {
public MobileGrantValidator() {
// mobile number must be in the request parameter
requiredParams.add(MobileGrant.MOBILE_GRANT_PARAM);
}
}
Step 3: Build and Test
To build the custom component, execute the following command in the terminal.
mvn clean install -DskipTests
After that, copy the org.wso2.custom.grant.type-1.0-SNAPSHOT.jar
from the target
directory and paste it inside the <IS_HOME>/repository/components/lib
directory of a fresh IS 6.1 pack downloaded from here.
Also, add the following configuration in the <IS_HOME>/repository/conf/deployment.toml
file.
[[oauth.custom_grant_type]]
name="mobile_grant"
grant_handler="org.wso2.custom.grant.type.MobileGrant"
grant_validator="org.wso2.custom.grant.type.MobileGrantValidator"
[oauth.custom_grant_type.properties]
IdTokenAllowed=true
Next, go to the <IS_HOME>/bin
directory and run the WSO2 Identity Server.
Linux/Mac Users →
./wso2server.sh -DosgiConsole
Windows →
wso2server.bat -DosgiConsole
Use ss <component_name>
command to check whether the custom grant type is in ACTIVE
state. This is possible due to the fact WSO2 Identity Server converts the artifacts in lib
directory to OSGi services during runtime and places them in the dropins
directory.
If it is not in ACTIVE
state use b <component_id>
and diag <component_id>
command to resolve the issues.
Next, create a new service provider using the DCR API of WSO2 IS.
curl -k -X POST -H "Authorization: Basic YWRtaW46YWRtaW4=" -H "Content-Type: application/json" -d '{ "client_name": "playground_2", "grant_types": ["password","mobile_grant"], "redirect_uris":["http://localhost:8080/playground2"], "ext_param_client_id":"provided_client_id0001","ext_param_client_secret":"provided_client_secret0001" }' "https://localhost:9443/api/identity/oauth2/dcr/v1.1/register"
Now, if you go to the management console with https://localhost:9443/carbon
you can see the created playground_2
service provider under the Service Providers
section (use the default username admin
and password admin
to login).
Go to the Inbound Authentication Configurations → OAuth/OpenID Connect Configurations to see whether you have selected the mobile_grant
grant type.
Since we have the activated themobile_grant
grant type with playground_2
application, we can get an access token using that. To get an access token use the following cURL command.
curl --user provided_client_id0001:provided_client_secret0001 -k -d "grant_type=mobile_grant&mobileNumber=0333444" -H "Content-Type: application/x-www-form-urlencoded" https://localhost:9443/oauth2/token
So this is it! This is how you can write a custom grant type for the WSO2 Identity Server. You can find more information on WSO2 grant types from the official WSO2 documentation.
You can find the implemented Custom Grant Type from the below link.
https://github.com/nipunaupeksha/wso2-custom-components-medium/tree/main/org.wso2.custom.grant.type
Subscribe to my newsletter
Read articles from Nipuna Upeksha directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by