├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── android ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── zaguiini │ └── RNPureJwt │ ├── RNPureJwtModule.java │ └── RNPureJwtPackage.java ├── ios ├── RNPureJwt.h ├── RNPureJwt.m ├── RNPureJwt.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── zaguini.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── zaguini.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── RNPureJwt.xcworkspace │ └── contents.xcworkspacedata ├── js ├── index.d.ts └── index.js ├── package.json └── react-native-pure-jwt.podspec /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Have you read the README and looked at the source code to see if you're not doing anything wrong?** 11 | If you haven't, please don't open an issue. Try to fix your problem first with what we have! 12 | 13 | **Describe the bug** 14 | A clear and concise description of what the bug is. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Desktop (please complete the following information):** 30 | - OS: [e.g. iOS] 31 | - Browser [e.g. chrome, safari] 32 | - Version [e.g. 22] 33 | 34 | **Smartphone (please complete the following information):** 35 | - Device: [e.g. iPhone6] 36 | - OS: [e.g. iOS8.1] 37 | - Browser [e.g. stock browser, safari] 38 | - Version [e.g. 22] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: It's not a bug or idea, just something that happened to you 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please describe it concisely here. 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | !**/*.xcodeproj 3 | !**/*.pbxproj 4 | !**/*.xcworkspacedata 5 | !**/*.xcsettings 6 | !**/*.xcscheme 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata 16 | *.xccheckout 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | *.xcuserstate 22 | project.xcworkspace 23 | 24 | # Gradle 25 | /android/build/ 26 | /build/ 27 | /RNTester/android/app/build/ 28 | /RNTester/android/app/gradle/ 29 | /RNTester/android/app/gradlew 30 | /RNTester/android/app/gradlew.bat 31 | /ReactAndroid/build/ 32 | 33 | # Buck 34 | .buckd 35 | buck-out 36 | /ReactAndroid/src/main/jni/prebuilt/lib/armeabi-v7a/ 37 | /ReactAndroid/src/main/jni/prebuilt/lib/x86/ 38 | /ReactAndroid/src/main/gen 39 | 40 | # Android 41 | .idea 42 | .gradle 43 | local.properties 44 | *.iml 45 | 46 | # Node 47 | node_modules 48 | *.log 49 | .nvm 50 | /danger/node_modules/ 51 | 52 | # OS X 53 | .DS_Store 54 | 55 | # Test generated files 56 | /ReactAndroid/src/androidTest/assets/AndroidTestBundle.js 57 | *.js.meta 58 | 59 | /coverage 60 | /third-party 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Luis Felipe Zaguini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-pure-jwt 2 | 3 | A React Native library that uses native modules to work with JWTs! 4 | 5 | `react-native-pure-jwt` is a library that implements the power of JWTs inside React Native! 6 | It's goal is to sign, verify and decode `JSON web tokens` in order to provide a secure way to transmit authentic messages between two parties. 7 | 8 | The difference to another libraries is that `react-native-pure-jwt` relies on the native realm in order to do JWT-related operations instead of the Javascript realm, so it's more stable (and works without hacks!). 9 | 10 | Supported algorithms: `HS256`, `HS384`, `HS512` 11 | 12 | React Native version required: `>= 0.46.0` 13 | 14 | ## What's a JSON Web Token? 15 | 16 | Don't know what a JSON Web Token is? Read on. Otherwise, jump down to the [Installation](#installation) section. 17 | 18 | JWT is a means of transmitting information between two parties in a compact, verifiable form. 19 | 20 | The bits of information encoded in the body of a JWT are called `claims`. The expanded form of the JWT is in a JSON format, so each `claim` is a key in the JSON object. 21 | 22 | The compacted representation of a signed JWT is a string that has three parts, each separated by a `.`: 23 | 24 | ``` 25 | eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.ipevRNuRP6HflG8cFKnmUPtypruRC4fb1DWtoLL62SY 26 | ``` 27 | 28 | Each section is [base 64](https://en.wikipedia.org/wiki/Base64) encoded. The first section is the header, which at a minimum needs to specify the algorithm used to sign the JWT. The second section is the body. This section has all the claims of this JWT encoded in it. The final section is the signature. It's computed by passing a combination of the header and body through the algorithm specified in the header. 29 | 30 | If you pass the first two sections through a base 64 decoder, you'll get the following (formatting added for clarity): 31 | 32 | `header` 33 | 34 | ``` 35 | { 36 | "alg": "HS256" 37 | } 38 | ``` 39 | 40 | `body` 41 | 42 | ``` 43 | { 44 | "sub": "Joe" 45 | } 46 | ``` 47 | 48 | In this case, the information we have is that the HMAC using SHA-256 algorithm was used to sign the JWT. And, the body has a single claim, `sub` with value `Joe`. 49 | 50 | There are a number of standard claims, called [Registered Claims](https://tools.ietf.org/html/rfc7519#section-4.1), in the specification and `sub` (for subject) is one of them. 51 | 52 | To compute the signature, you must know the secret that was used to sign it. In this case, it was the word `secret`. You can see the signature creation is action [here](https://jsfiddle.net/dogeared/2fy2y0yd/11/) (Note: Trailing `=` are lopped off the signature for the JWT). 53 | 54 | Now you know (just about) all you need to know about JWTs. (Credits: [jwtk/jjwt](https://github.com/jwtk/jjwt)) 55 | 56 | ## Installation 57 | 58 | Install the package with: 59 | `yarn add react-native-pure-jwt` 60 | 61 | If your React Native version supports autolinking, you should only run `pod install` on `ios` folder and you'll be good to go. 62 | 63 | If not... 64 | 65 | `react-native link react-native-pure-jwt` 66 | 67 | **The linking process on the iOS version works with Cocoapods** 68 | 69 | ### Manual Android linking 70 | 71 | - in `android/app/build.gradle`: 72 | 73 | ```diff 74 | dependencies { 75 | ... 76 | compile "com.facebook.react:react-native:+" // From node_modules 77 | + compile project(':react-native-pure-jwt') 78 | } 79 | ``` 80 | 81 | - in `android/settings.gradle`: 82 | 83 | ```diff 84 | ... 85 | include ':app' 86 | + include ':react-native-pure-jwt' 87 | + project(':react-native-pure-jwt').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-pure-jwt/android') 88 | ``` 89 | 90 | - in `MainApplication.java`: 91 | 92 | ```diff 93 | + import com.zaguiini.RNPureJwt.RNPureJwtPackage; 94 | 95 | public class MainApplication extends Application implements ReactApplication { 96 | //...... 97 | 98 | @Override 99 | protected List getPackages() { 100 | return Arrays.asList( 101 | + new RNPureJwtPackage(), 102 | new MainReactPackage() 103 | ); 104 | } 105 | 106 | ...... 107 | } 108 | ``` 109 | 110 | ### Manual iOS linking 111 | 112 | You need to use Cocoapods at the moment. Open your `Podfile` and insert the following line in your main target: 113 | 114 | ```ruby 115 | pod 'react-native-pure-jwt', :podspec => '../node_modules/react-native-pure-jwt/react-native-pure-jwt.podspec' 116 | ``` 117 | 118 | Then run `pod install` and open your `.xcworkspace` 119 | 120 | ## Usage 121 | 122 | - sign: 123 | 124 | ```js 125 | import { sign } from "react-native-pure-jwt"; 126 | 127 | sign( 128 | { 129 | iss: "luisfelipez@live.com", 130 | exp: new Date().getTime() + 3600 * 1000, // expiration date, required, in ms, absolute to 1/1/1970 131 | additional: "payload" 132 | }, // body 133 | "my-secret", // secret 134 | { 135 | alg: "HS256" 136 | } 137 | ) 138 | .then(console.log) // token as the only argument 139 | .catch(console.error); // possible errors 140 | ``` 141 | 142 | - decode: 143 | 144 | ```js 145 | import { decode } from "react-native-pure-jwt"; 146 | 147 | decode( 148 | token, // the token 149 | secret, // the secret 150 | { 151 | skipValidation: true // to skip signature and exp verification 152 | } 153 | ) 154 | .then(console.log) // already an object. read below, exp key note 155 | .catch(console.error); 156 | 157 | /* 158 | response example: 159 | { 160 | headers: { 161 | alg: 'HS256' 162 | }, 163 | payload: { 164 | iss: 'luisfelipez@live.com', 165 | exp: 'some date', // IN SECONDS 166 | } 167 | } 168 | */ 169 | ``` 170 | 171 | --- 172 | 173 | ## Troubleshooting 174 | 175 | ### haste collision. react-native/package.json collides with Pods/React/package.json 176 | 177 | Add this to your `Podfile`: 178 | 179 | ```rb 180 | post_install do |installer| 181 | installer.pods_project.targets.each do |target| 182 | if target.name == "React" 183 | target.remove_from_project 184 | end 185 | end 186 | end 187 | ``` 188 | 189 | 190 | --- 191 | 192 | Feel free to colaborate with the project! 193 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath("com.android.tools.build:gradle:7.1.1") 10 | } 11 | } 12 | 13 | apply plugin: 'com.android.library' 14 | 15 | android { 16 | compileSdkVersion 31 17 | buildToolsVersion "31.0.0" 18 | 19 | defaultConfig { 20 | minSdkVersion 21 21 | targetSdkVersion 31 22 | versionCode 3 23 | versionName "2.0.1" 24 | } 25 | lintOptions { 26 | abortOnError false 27 | } 28 | } 29 | 30 | repositories { 31 | google() 32 | mavenCentral() 33 | } 34 | 35 | dependencies { 36 | implementation 'com.facebook.react:react-native:+' 37 | implementation 'io.jsonwebtoken:jjwt:0.9.0' 38 | } 39 | -------------------------------------------------------------------------------- /android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keepattributes InnerClasses 2 | 3 | -keep class io.jsonwebtoken.** { *; } 4 | -keepnames class io.jsonwebtoken.* { *; } 5 | -keepnames interface io.jsonwebtoken.* { *; } 6 | 7 | -keep class org.bouncycastle.** { *; } 8 | -keepnames class org.bouncycastle.** { *; } 9 | -dontwarn org.bouncycastle.** 10 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/src/main/java/com/zaguiini/RNPureJwt/RNPureJwtModule.java: -------------------------------------------------------------------------------- 1 | package com.zaguiini.RNPureJwt; 2 | 3 | import android.util.Base64; 4 | 5 | import com.facebook.react.bridge.Arguments; 6 | import com.facebook.react.bridge.Promise; 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 9 | import com.facebook.react.bridge.ReactMethod; 10 | import com.facebook.react.bridge.ReadableMap; 11 | import com.facebook.react.bridge.WritableMap; 12 | import com.fasterxml.jackson.core.type.TypeReference; 13 | import com.fasterxml.jackson.databind.ObjectMapper; 14 | 15 | import java.io.IOException; 16 | import java.nio.charset.Charset; 17 | import java.util.Date; 18 | import java.util.Map; 19 | import java.util.Set; 20 | import java.util.regex.Pattern; 21 | 22 | import io.jsonwebtoken.ExpiredJwtException; 23 | import io.jsonwebtoken.Jwt; 24 | import io.jsonwebtoken.JwtBuilder; 25 | import io.jsonwebtoken.JwtParser; 26 | import io.jsonwebtoken.Jwts; 27 | import io.jsonwebtoken.MalformedJwtException; 28 | import io.jsonwebtoken.SignatureAlgorithm; 29 | import io.jsonwebtoken.SignatureException; 30 | import io.jsonwebtoken.impl.DefaultClaims; 31 | 32 | public class RNPureJwtModule extends ReactContextBaseJavaModule { 33 | 34 | public RNPureJwtModule(ReactApplicationContext reactContext) { 35 | super(reactContext); 36 | } 37 | 38 | @Override 39 | public String getName() { 40 | return "RNPureJwt"; 41 | } 42 | 43 | private String toBase64(String plainString) { 44 | return Base64.encodeToString(plainString.getBytes(Charset.forName("UTF-8")), Base64.DEFAULT); 45 | } 46 | 47 | private String base64toString(String plainString) { 48 | return new String(Base64.decode(plainString, Base64.DEFAULT)); 49 | } 50 | 51 | private void getResponse(String token, Promise callback) { 52 | ObjectMapper mapper = new ObjectMapper(); 53 | WritableMap response = Arguments.createMap(); 54 | 55 | String[] parts = token.split(Pattern.quote(".")); 56 | 57 | try { 58 | Map headers = mapper.readValue( 59 | this.base64toString(parts[0]), 60 | new TypeReference>() {} 61 | ); 62 | 63 | response.putMap("headers", Arguments.makeNativeMap(headers)); 64 | } catch(IOException e) { 65 | callback.reject("7", "Invalid header"); 66 | } 67 | 68 | try { 69 | Map payload = mapper.readValue( 70 | this.base64toString(parts[1]), 71 | new TypeReference>() {} 72 | ); 73 | 74 | response.putMap("payload", Arguments.makeNativeMap(payload)); 75 | } catch(IOException e) { 76 | callback.reject("8", "Invalid payload"); 77 | } 78 | 79 | 80 | callback.resolve(response); 81 | } 82 | 83 | private void getResponse(Jwt parsed, Promise callback) { 84 | ObjectMapper mapper = new ObjectMapper(); 85 | 86 | Map headersMap = mapper.convertValue(parsed.getHeader(), DefaultClaims.class); 87 | Map payload = mapper.convertValue(parsed.getBody(), DefaultClaims.class); 88 | 89 | WritableMap response = Arguments.createMap(); 90 | 91 | response.putMap("headers", Arguments.makeNativeMap(headersMap)); 92 | response.putMap("payload", Arguments.makeNativeMap(payload)); 93 | 94 | callback.resolve(response); 95 | } 96 | 97 | @ReactMethod 98 | public void decode(String token, String secret, ReadableMap options, Promise callback) { 99 | Boolean skipValidation = false; 100 | 101 | // Parse options to check for skipValidation 102 | Set> entries = options.toHashMap().entrySet(); 103 | for (Object entry : entries) { 104 | Map.Entry item = (Map.Entry) entry; 105 | String key = (String) item.getKey(); 106 | Object value = item.getValue(); 107 | if ("skipValidation".equals(key)) { 108 | skipValidation = (boolean) value; 109 | break; 110 | } 111 | } 112 | 113 | if (skipValidation) { 114 | // Skip validation: manually decode the JWT parts 115 | try { 116 | getResponse(token, callback); 117 | } catch (Exception e) { 118 | callback.reject("7", "Failed to decode JWT: " + e.getMessage()); 119 | } 120 | return; 121 | } 122 | 123 | // Normal validation using the JwtParser 124 | if (secret == null || secret.isEmpty()) { 125 | callback.reject("1", "Secret cannot be null or empty when validation is enabled."); 126 | return; 127 | } 128 | 129 | JwtParser parser = Jwts.parser().setSigningKey(this.toBase64(secret)); 130 | 131 | try { 132 | Jwt parsed = parser.parse(token); 133 | getResponse(parsed, callback); 134 | } catch (MalformedJwtException e) { 135 | callback.reject("2", "The JWT is invalid."); 136 | } catch (ExpiredJwtException e) { 137 | callback.reject("3", "The JWT is expired."); 138 | } catch (SignatureException e) { 139 | callback.reject("6", "Invalid signature."); 140 | } catch (Exception e) { 141 | callback.reject("0", e.getMessage()); 142 | } 143 | } 144 | 145 | @ReactMethod 146 | public void sign(ReadableMap claims, String secret, ReadableMap options, Promise callback) { 147 | String algorithm = options.hasKey("alg") ? options.getString("alg") : "HS256"; 148 | JwtBuilder constructedToken = Jwts.builder() 149 | .signWith(SignatureAlgorithm.forName(algorithm), this.toBase64(secret)) 150 | .setHeaderParam("alg", algorithm) 151 | .setHeaderParam("typ", "JWT"); 152 | 153 | Set> entries = claims.toHashMap().entrySet(); 154 | 155 | for (Object entry: entries) { 156 | Map.Entry item = (Map.Entry) entry; 157 | 158 | String key = (String) item.getKey(); 159 | Object value = item.getValue(); 160 | 161 | Double valueAsDouble; 162 | 163 | switch (key) { 164 | case "alg": 165 | break; 166 | 167 | case "exp": 168 | valueAsDouble = (double) value; 169 | constructedToken.setExpiration(new Date(valueAsDouble.longValue())); 170 | break; 171 | 172 | case "iat": 173 | valueAsDouble = (double) value; 174 | constructedToken.setIssuedAt(new Date(valueAsDouble.longValue())); 175 | break; 176 | 177 | case "nbf": 178 | valueAsDouble = (double) value; 179 | constructedToken.setNotBefore(new Date(valueAsDouble.longValue())); 180 | break; 181 | 182 | case "aud": 183 | constructedToken.setAudience(value.toString()); 184 | break; 185 | 186 | case "iss": 187 | constructedToken.setIssuer(value.toString()); 188 | break; 189 | 190 | case "sub": 191 | constructedToken.setSubject(value.toString()); 192 | break; 193 | 194 | case "jti": 195 | constructedToken.setId(value.toString()); 196 | break; 197 | 198 | default: 199 | constructedToken.claim(key, value); 200 | } 201 | } 202 | 203 | callback.resolve(constructedToken.compact()); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /android/src/main/java/com/zaguiini/RNPureJwt/RNPureJwtPackage.java: -------------------------------------------------------------------------------- 1 | package com.zaguiini.RNPureJwt; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.bridge.NativeModule; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.uimanager.ViewManager; 11 | import com.facebook.react.bridge.JavaScriptModule; 12 | 13 | public class RNPureJwtPackage implements ReactPackage { 14 | @Override 15 | public List createNativeModules(ReactApplicationContext reactContext) { 16 | return Arrays.asList(new RNPureJwtModule(reactContext)); 17 | } 18 | 19 | // Deprecated from RN 0.47 20 | public List> createJSModules() { 21 | return Collections.emptyList(); 22 | } 23 | 24 | @Override 25 | public List createViewManagers(ReactApplicationContext reactContext) { 26 | return Collections.emptyList(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ios/RNPureJwt.h: -------------------------------------------------------------------------------- 1 | 2 | #if __has_include("RCTBridgeModule.h") 3 | #import "RCTBridgeModule.h" 4 | #else 5 | #import 6 | #endif 7 | 8 | @interface RNPureJwt : NSObject 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /ios/RNPureJwt.m: -------------------------------------------------------------------------------- 1 | 2 | #import "RNPureJwt.h" 3 | #import "JWT.h" 4 | 5 | @implementation RNPureJwt 6 | 7 | - (dispatch_queue_t)methodQueue 8 | { 9 | return dispatch_get_main_queue(); 10 | } 11 | RCT_EXPORT_MODULE() 12 | 13 | RCT_REMAP_METHOD(sign, 14 | claims: (NSDictionary *) claims 15 | secret: (NSString *) secret 16 | options: (NSDictionary *) options 17 | resolver: (RCTPromiseResolveBlock) resolve 18 | rejecter: (RCTPromiseRejectBlock) reject 19 | ) { 20 | JWTClaimsSet *claimsSet = [[JWTClaimsSet alloc] init]; 21 | 22 | NSMutableDictionary *payload = [[NSMutableDictionary alloc] init]; 23 | 24 | for(id key in claims) { 25 | if([key isEqualToString: @"aud"]) { 26 | claimsSet.audience = [claims objectForKey:key]; 27 | } else if([key isEqualToString: @"jti"]) { 28 | claimsSet.identifier = [claims objectForKey:key]; 29 | } else if([key isEqualToString: @"iss"]) { 30 | claimsSet.issuer = [claims objectForKey:key]; 31 | } else if([key isEqualToString: @"sub"]) { 32 | claimsSet.subject = [claims objectForKey:key]; 33 | } else if([key isEqualToString: @"typ"]) { 34 | claimsSet.type = [claims objectForKey:key]; 35 | } else if([key isEqualToString: @"iat"]) { 36 | NSInteger issuedAt = [[claims objectForKey:key] integerValue] / 1000; 37 | claimsSet.issuedAt = [NSDate dateWithTimeIntervalSince1970: issuedAt]; 38 | } else if([key isEqualToString: @"nbf"]) { 39 | NSInteger notBeforeDate = [[claims objectForKey:key] integerValue] / 1000; 40 | claimsSet.notBeforeDate = [NSDate dateWithTimeIntervalSince1970: notBeforeDate]; 41 | } else if([key isEqualToString: @"exp"]) { 42 | NSInteger expirationDate = [[claims objectForKey:key] integerValue] / 1000; 43 | claimsSet.expirationDate = [NSDate dateWithTimeIntervalSince1970: expirationDate]; 44 | } else { 45 | [payload setObject: [claims objectForKey:key] forKey: key]; 46 | } 47 | } 48 | 49 | JWTEncodingBuilder *builder = [JWTEncodingBuilder encodePayload:payload]; 50 | 51 | NSString *algorithmName = options[@"alg"] ? options[@"alg"] : @"HS256"; 52 | id holder = [JWTAlgorithmHSFamilyDataHolder new].algorithmName(algorithmName).secret(secret); 53 | JWTCodingResultType *result = builder.claimsSet(claimsSet).addHolder(holder).result; 54 | 55 | if(result.successResult) { 56 | resolve(result.successResult.encoded); 57 | } else { 58 | reject(@"failed", @"Encoding failed", result.errorResult.error); 59 | } 60 | } 61 | 62 | RCT_REMAP_METHOD(decode, 63 | token: (NSString *) token 64 | secret: (NSString *) secret 65 | options: (NSDictionary *) options 66 | resolver: (RCTPromiseResolveBlock) resolve 67 | rejecter: (RCTPromiseRejectBlock) reject 68 | ) { 69 | NSArray *claimsKeys = @[@"audience", @"identifier", @"issuer", @"subject", @"type", @"issuedAt", @"notBeforeDate", @"expirationDate"]; 70 | 71 | JWTClaimsSet *trustedClaims = [[JWTClaimsSet alloc] init]; 72 | BOOL decodeForced = [options[@"skipValidation"] boolValue] == YES; 73 | 74 | if(!decodeForced) { 75 | trustedClaims.expirationDate = [NSDate date]; 76 | } 77 | 78 | NSNumber *decodeOptions = @(decodeForced); 79 | 80 | NSString *algorithmName = options[@"alg"] ? options[@"alg"] : @"HS256"; 81 | id holder = [JWTAlgorithmHSFamilyDataHolder new].algorithmName(algorithmName).secret(secret); 82 | 83 | JWTCodingBuilder *verifyBuilder = [JWTDecodingBuilder decodeMessage:token].claimsSet(trustedClaims).options(decodeOptions).addHolder(holder); 84 | JWTCodingResultType *result = verifyBuilder.result; 85 | 86 | if (result.successResult) { 87 | NSMutableDictionary *payload = [[NSMutableDictionary alloc] init]; 88 | [payload addEntriesFromDictionary: result.successResult.payload]; 89 | 90 | NSDictionary *claims = [result.successResult.claimsSet dictionaryWithValuesForKeys: claimsKeys]; 91 | 92 | for(id key in claims) { 93 | if([key isEqualToString: @"audience"] && claims[key] != [NSNull null]) { 94 | payload[@"aud"] = claims[key]; 95 | } 96 | 97 | if([key isEqualToString: @"identifier"] && claims[key] != [NSNull null]) { 98 | payload[@"jti"] = claims[key]; 99 | } 100 | 101 | if([key isEqualToString: @"issuer"] && claims[key] != [NSNull null]) { 102 | payload[@"iss"] = claims[key]; 103 | } 104 | 105 | if([key isEqualToString: @"subject"] && claims[key] != [NSNull null]) { 106 | payload[@"sub"] = claims[key]; 107 | } 108 | 109 | if([key isEqualToString: @"expirationDate"] && [[NSDate dateWithTimeIntervalSince1970: 0] compare: claims[key]] != NSOrderedSame) { 110 | double time = [[claims valueForKey:@"expirationDate"] timeIntervalSince1970]; 111 | [payload setValue:[NSNumber numberWithDouble: time] forKey:@"exp"]; 112 | } 113 | 114 | if([key isEqualToString: @"issuedAt"] && [[NSDate dateWithTimeIntervalSince1970: 0] compare: claims[key]] != NSOrderedSame) { 115 | double time = [[claims valueForKey:@"issuedAt"] timeIntervalSince1970]; 116 | [payload setValue:[NSNumber numberWithDouble: time] forKey:@"iat"]; 117 | } 118 | 119 | if([key isEqualToString: @"notBeforeDate"] && [[NSDate dateWithTimeIntervalSince1970: 0] compare: claims[key]] != NSOrderedSame) { 120 | double time = [[claims valueForKey:@"notBeforeDate"] timeIntervalSince1970]; 121 | [payload setValue:[NSNumber numberWithDouble: time] forKey:@"nbf"]; 122 | } 123 | } 124 | 125 | 126 | resolve(@{ 127 | @"headers": result.successResult.headers, 128 | @"payload": payload, 129 | }); 130 | } 131 | else { 132 | reject(@"failed", @"Decoding failed", result.errorResult.error); 133 | } 134 | } 135 | 136 | @end 137 | 138 | -------------------------------------------------------------------------------- /ios/RNPureJwt.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B3E7B58A1CC2AC0600A0062D /* RNPureJwt.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNPureJwt.m */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXCopyFilesBuildPhase section */ 14 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 15 | isa = PBXCopyFilesBuildPhase; 16 | buildActionMask = 2147483647; 17 | dstPath = "include/$(PRODUCT_NAME)"; 18 | dstSubfolderSpec = 16; 19 | files = ( 20 | ); 21 | runOnlyForDeploymentPostprocessing = 0; 22 | }; 23 | /* End PBXCopyFilesBuildPhase section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 134814201AA4EA6300B7C361 /* libRNPureJwt.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNPureJwt.a; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | B3E7B5881CC2AC0600A0062D /* RNPureJwt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNPureJwt.h; sourceTree = ""; }; 28 | B3E7B5891CC2AC0600A0062D /* RNPureJwt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNPureJwt.m; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 134814211AA4EA7D00B7C361 /* Products */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | 134814201AA4EA6300B7C361 /* libRNPureJwt.a */, 46 | ); 47 | name = Products; 48 | sourceTree = ""; 49 | }; 50 | 58B511D21A9E6C8500147676 = { 51 | isa = PBXGroup; 52 | children = ( 53 | B3E7B5881CC2AC0600A0062D /* RNPureJwt.h */, 54 | B3E7B5891CC2AC0600A0062D /* RNPureJwt.m */, 55 | 134814211AA4EA7D00B7C361 /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | /* End PBXGroup section */ 60 | 61 | /* Begin PBXNativeTarget section */ 62 | 58B511DA1A9E6C8500147676 /* RNPureJwt */ = { 63 | isa = PBXNativeTarget; 64 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNPureJwt" */; 65 | buildPhases = ( 66 | 58B511D71A9E6C8500147676 /* Sources */, 67 | 58B511D81A9E6C8500147676 /* Frameworks */, 68 | 58B511D91A9E6C8500147676 /* CopyFiles */, 69 | ); 70 | buildRules = ( 71 | ); 72 | dependencies = ( 73 | ); 74 | name = RNPureJwt; 75 | productName = RCTDataManager; 76 | productReference = 134814201AA4EA6300B7C361 /* libRNPureJwt.a */; 77 | productType = "com.apple.product-type.library.static"; 78 | }; 79 | /* End PBXNativeTarget section */ 80 | 81 | /* Begin PBXProject section */ 82 | 58B511D31A9E6C8500147676 /* Project object */ = { 83 | isa = PBXProject; 84 | attributes = { 85 | LastUpgradeCheck = 0830; 86 | ORGANIZATIONNAME = Facebook; 87 | TargetAttributes = { 88 | 58B511DA1A9E6C8500147676 = { 89 | CreatedOnToolsVersion = 6.1.1; 90 | }; 91 | }; 92 | }; 93 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNPureJwt" */; 94 | compatibilityVersion = "Xcode 3.2"; 95 | developmentRegion = English; 96 | hasScannedForEncodings = 0; 97 | knownRegions = ( 98 | English, 99 | en, 100 | ); 101 | mainGroup = 58B511D21A9E6C8500147676; 102 | productRefGroup = 58B511D21A9E6C8500147676; 103 | projectDirPath = ""; 104 | projectRoot = ""; 105 | targets = ( 106 | 58B511DA1A9E6C8500147676 /* RNPureJwt */, 107 | ); 108 | }; 109 | /* End PBXProject section */ 110 | 111 | /* Begin PBXSourcesBuildPhase section */ 112 | 58B511D71A9E6C8500147676 /* Sources */ = { 113 | isa = PBXSourcesBuildPhase; 114 | buildActionMask = 2147483647; 115 | files = ( 116 | B3E7B58A1CC2AC0600A0062D /* RNPureJwt.m in Sources */, 117 | ); 118 | runOnlyForDeploymentPostprocessing = 0; 119 | }; 120 | /* End PBXSourcesBuildPhase section */ 121 | 122 | /* Begin XCBuildConfiguration section */ 123 | 58B511ED1A9E6C8500147676 /* Debug */ = { 124 | isa = XCBuildConfiguration; 125 | buildSettings = { 126 | ALWAYS_SEARCH_USER_PATHS = NO; 127 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 128 | CLANG_CXX_LIBRARY = "libc++"; 129 | CLANG_ENABLE_MODULES = YES; 130 | CLANG_ENABLE_OBJC_ARC = YES; 131 | CLANG_WARN_BOOL_CONVERSION = YES; 132 | CLANG_WARN_CONSTANT_CONVERSION = YES; 133 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 134 | CLANG_WARN_EMPTY_BODY = YES; 135 | CLANG_WARN_ENUM_CONVERSION = YES; 136 | CLANG_WARN_INFINITE_RECURSION = YES; 137 | CLANG_WARN_INT_CONVERSION = YES; 138 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 139 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 140 | CLANG_WARN_UNREACHABLE_CODE = YES; 141 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 142 | COPY_PHASE_STRIP = NO; 143 | CURRENT_PROJECT_VERSION = 2.0.0; 144 | ENABLE_STRICT_OBJC_MSGSEND = YES; 145 | ENABLE_TESTABILITY = YES; 146 | GCC_C_LANGUAGE_STANDARD = gnu99; 147 | GCC_DYNAMIC_NO_PIC = NO; 148 | GCC_NO_COMMON_BLOCKS = YES; 149 | GCC_OPTIMIZATION_LEVEL = 0; 150 | GCC_PREPROCESSOR_DEFINITIONS = ( 151 | "DEBUG=1", 152 | "$(inherited)", 153 | ); 154 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 155 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 156 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 157 | GCC_WARN_UNDECLARED_SELECTOR = YES; 158 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 159 | GCC_WARN_UNUSED_FUNCTION = YES; 160 | GCC_WARN_UNUSED_VARIABLE = YES; 161 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 162 | MTL_ENABLE_DEBUG_INFO = YES; 163 | ONLY_ACTIVE_ARCH = YES; 164 | SDKROOT = iphoneos; 165 | }; 166 | name = Debug; 167 | }; 168 | 58B511EE1A9E6C8500147676 /* Release */ = { 169 | isa = XCBuildConfiguration; 170 | buildSettings = { 171 | ALWAYS_SEARCH_USER_PATHS = NO; 172 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 173 | CLANG_CXX_LIBRARY = "libc++"; 174 | CLANG_ENABLE_MODULES = YES; 175 | CLANG_ENABLE_OBJC_ARC = YES; 176 | CLANG_WARN_BOOL_CONVERSION = YES; 177 | CLANG_WARN_CONSTANT_CONVERSION = YES; 178 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 179 | CLANG_WARN_EMPTY_BODY = YES; 180 | CLANG_WARN_ENUM_CONVERSION = YES; 181 | CLANG_WARN_INFINITE_RECURSION = YES; 182 | CLANG_WARN_INT_CONVERSION = YES; 183 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 184 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 185 | CLANG_WARN_UNREACHABLE_CODE = YES; 186 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 187 | COPY_PHASE_STRIP = YES; 188 | CURRENT_PROJECT_VERSION = 2.0.0; 189 | ENABLE_NS_ASSERTIONS = NO; 190 | ENABLE_STRICT_OBJC_MSGSEND = YES; 191 | GCC_C_LANGUAGE_STANDARD = gnu99; 192 | GCC_NO_COMMON_BLOCKS = YES; 193 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 194 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 195 | GCC_WARN_UNDECLARED_SELECTOR = YES; 196 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 197 | GCC_WARN_UNUSED_FUNCTION = YES; 198 | GCC_WARN_UNUSED_VARIABLE = YES; 199 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 200 | MTL_ENABLE_DEBUG_INFO = NO; 201 | SDKROOT = iphoneos; 202 | VALIDATE_PRODUCT = YES; 203 | }; 204 | name = Release; 205 | }; 206 | 58B511F01A9E6C8500147676 /* Debug */ = { 207 | isa = XCBuildConfiguration; 208 | buildSettings = { 209 | HEADER_SEARCH_PATHS = ( 210 | "$(inherited)", 211 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 212 | "$(SRCROOT)/../../../React/**", 213 | "$(SRCROOT)/../../react-native/React/**", 214 | ); 215 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 216 | OTHER_LDFLAGS = "-ObjC"; 217 | PRODUCT_NAME = RNPureJwt; 218 | SKIP_INSTALL = YES; 219 | }; 220 | name = Debug; 221 | }; 222 | 58B511F11A9E6C8500147676 /* Release */ = { 223 | isa = XCBuildConfiguration; 224 | buildSettings = { 225 | HEADER_SEARCH_PATHS = ( 226 | "$(inherited)", 227 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 228 | "$(SRCROOT)/../../../React/**", 229 | "$(SRCROOT)/../../react-native/React/**", 230 | ); 231 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 232 | OTHER_LDFLAGS = "-ObjC"; 233 | PRODUCT_NAME = RNPureJwt; 234 | SKIP_INSTALL = YES; 235 | }; 236 | name = Release; 237 | }; 238 | /* End XCBuildConfiguration section */ 239 | 240 | /* Begin XCConfigurationList section */ 241 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNPureJwt" */ = { 242 | isa = XCConfigurationList; 243 | buildConfigurations = ( 244 | 58B511ED1A9E6C8500147676 /* Debug */, 245 | 58B511EE1A9E6C8500147676 /* Release */, 246 | ); 247 | defaultConfigurationIsVisible = 0; 248 | defaultConfigurationName = Release; 249 | }; 250 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNPureJwt" */ = { 251 | isa = XCConfigurationList; 252 | buildConfigurations = ( 253 | 58B511F01A9E6C8500147676 /* Debug */, 254 | 58B511F11A9E6C8500147676 /* Release */, 255 | ); 256 | defaultConfigurationIsVisible = 0; 257 | defaultConfigurationName = Release; 258 | }; 259 | /* End XCConfigurationList section */ 260 | }; 261 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 262 | } 263 | -------------------------------------------------------------------------------- /ios/RNPureJwt.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/RNPureJwt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/RNPureJwt.xcodeproj/project.xcworkspace/xcuserdata/zaguini.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaguiini/react-native-pure-jwt/b30b62176fdca6edd755c6490ce667037e274992/ios/RNPureJwt.xcodeproj/project.xcworkspace/xcuserdata/zaguini.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ios/RNPureJwt.xcodeproj/xcuserdata/zaguini.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | RNPureJwt.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ios/RNPureJwt.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | 3 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /js/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface DecodeResponse { 2 | headers: object 3 | payload: object 4 | } 5 | 6 | export interface SignOptions { 7 | alg: 'HS256' | 'HS384' | 'HS512' 8 | } 9 | 10 | export interface DecodeOptions { 11 | skipValidation?: boolean 12 | } 13 | 14 | export function sign( 15 | payload: object, 16 | secret: string, 17 | options: SignOptions 18 | ): Promise 19 | 20 | export function decode( 21 | token: string, 22 | secret: string, 23 | options?: DecodeOptions 24 | ): Promise 25 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | import { NativeModules } from 'react-native' 2 | 3 | const { RNPureJwt } = NativeModules 4 | 5 | export const sign = (token, secret, options = {}) => 6 | RNPureJwt.sign(token, secret, options) 7 | 8 | export const decode = (token, secret, options = {}) => 9 | RNPureJwt.decode(token, secret, options) 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-pure-jwt", 3 | "version": "3.0.3", 4 | "description": "React Native Pure JWT implementation", 5 | "main": "js/index.js", 6 | "typings": "js/index.d.ts", 7 | "author": "Luis Felipe Zaguini", 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/zaguiini/react-native-pure-jwt.git" 12 | }, 13 | "peerDependencies": { 14 | "react-native": ">=0.41.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /react-native-pure-jwt.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = package['name'] 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.license = package['license'] 10 | s.authors = package['author'] 11 | s.homepage = package['repository']['url'] 12 | s.platform = :ios, "9.0" 13 | s.ios.deployment_target = '9.0' 14 | 15 | s.source = { :git => package['repository']['url'], :tag => "#{s.version}" } 16 | s.source_files = "ios/**/*.{h,m}" 17 | 18 | s.dependency 'React' 19 | s.dependency 'JWT', '3.0.0-beta.12' 20 | end 21 | 22 | --------------------------------------------------------------------------------